[PLUGINS] +clavettes et dependances
authorLudovic CHEVALIER <ludovic@beurresarrasin.net>
Mon, 9 Feb 2015 15:36:33 +0000 (16:36 +0100)
committerLudovic CHEVALIER <ludovic@beurresarrasin.net>
Mon, 9 Feb 2015 15:36:33 +0000 (16:36 +0100)
665 files changed:
.gitmodules [new file with mode: 0644]
www/plugins/agenda_3_5/action/activer_agenda_rubrique.php [new file with mode: 0644]
www/plugins/agenda_3_5/action/editer_evenement.php [new file with mode: 0644]
www/plugins/agenda_3_5/action/supprimer_evenement.php [new file with mode: 0644]
www/plugins/agenda_3_5/action/supprimer_evenement_participant.php [new file with mode: 0644]
www/plugins/agenda_3_5/agenda_administrations.php [new file with mode: 0644]
www/plugins/agenda_3_5/agenda_autoriser.php [new file with mode: 0644]
www/plugins/agenda_3_5/agenda_fonctions.php [new file with mode: 0644]
www/plugins/agenda_3_5/agenda_options.php [new file with mode: 0644]
www/plugins/agenda_3_5/agenda_pipelines.php [new file with mode: 0644]
www/plugins/agenda_3_5/base/agenda_evenements.php [new file with mode: 0644]
www/plugins/agenda_3_5/css/spip.agenda.css [new file with mode: 0644]
www/plugins/agenda_3_5/demo/agenda_calendrier_mini.html [new file with mode: 0644]
www/plugins/agenda_3_5/demo/exemple_navigation_jours.html [new file with mode: 0644]
www/plugins/agenda_3_5/demo/test/test_boucle_evenements.html [new file with mode: 0644]
www/plugins/agenda_3_5/demo/test/testagenda.html [new file with mode: 0644]
www/plugins/agenda_3_5/formulaires/configurer_agenda.html [new file with mode: 0644]
www/plugins/agenda_3_5/formulaires/editer_evenement.html [new file with mode: 0644]
www/plugins/agenda_3_5/formulaires/editer_evenement.php [new file with mode: 0644]
www/plugins/agenda_3_5/formulaires/migrer_agenda.html [new file with mode: 0644]
www/plugins/agenda_3_5/formulaires/migrer_agenda.php [new file with mode: 0644]
www/plugins/agenda_3_5/formulaires/participer_evenement.html [new file with mode: 0644]
www/plugins/agenda_3_5/formulaires/participer_evenement.php [new file with mode: 0644]
www/plugins/agenda_3_5/inc/agenda_filtres.php [new file with mode: 0644]
www/plugins/agenda_3_5/inc/date_gestion.php [new file with mode: 0644]
www/plugins/agenda_3_5/inc/un-evenement-ical.html [new file with mode: 0644]
www/plugins/agenda_3_5/inclure/agenda-vue-calendrier.html [new file with mode: 0644]
www/plugins/agenda_3_5/inclure/liste_participants_evenement.html [new file with mode: 0644]
www/plugins/agenda_3_5/javascript/jquery-ui.multidatespicker.js [new file with mode: 0755]
www/plugins/agenda_3_5/lang/agenda.xml [new file with mode: 0644]
www/plugins/agenda_3_5/lang/agenda_de.php [new file with mode: 0644]
www/plugins/agenda_3_5/lang/agenda_en.php [new file with mode: 0644]
www/plugins/agenda_3_5/lang/agenda_es.php [new file with mode: 0644]
www/plugins/agenda_3_5/lang/agenda_fr.php [new file with mode: 0644]
www/plugins/agenda_3_5/lang/agenda_nl.php [new file with mode: 0644]
www/plugins/agenda_3_5/lang/agenda_sk.php [new file with mode: 0644]
www/plugins/agenda_3_5/lang/configureragenda.xml [new file with mode: 0644]
www/plugins/agenda_3_5/lang/configureragenda_fr.php [new file with mode: 0644]
www/plugins/agenda_3_5/lang/migreragenda.xml [new file with mode: 0644]
www/plugins/agenda_3_5/lang/migreragenda_fr.php [new file with mode: 0644]
www/plugins/agenda_3_5/lang/paquet-agenda.xml [new file with mode: 0644]
www/plugins/agenda_3_5/lang/paquet-agenda_en.php [new file with mode: 0644]
www/plugins/agenda_3_5/lang/paquet-agenda_es.php [new file with mode: 0644]
www/plugins/agenda_3_5/lang/paquet-agenda_fr.php [new file with mode: 0644]
www/plugins/agenda_3_5/lang/paquet-agenda_nl.php [new file with mode: 0644]
www/plugins/agenda_3_5/lang/paquet-agenda_sk.php [new file with mode: 0644]
www/plugins/agenda_3_5/lang/paquet-albums.xml [new file with mode: 0644]
www/plugins/agenda_3_5/modeles/evenement_vevent.html [new file with mode: 0644]
www/plugins/agenda_3_5/paquet.xml [new file with mode: 0644]
www/plugins/agenda_3_5/prive/objets/contenu/article-evenements.html [new file with mode: 0644]
www/plugins/agenda_3_5/prive/objets/contenu/evenement.html [new file with mode: 0644]
www/plugins/agenda_3_5/prive/objets/infos/evenement.html [new file with mode: 0644]
www/plugins/agenda_3_5/prive/objets/liste/evenement_participants.html [new file with mode: 0755]
www/plugins/agenda_3_5/prive/objets/liste/evenements-post.html [new file with mode: 0644]
www/plugins/agenda_3_5/prive/objets/liste/evenements.html [new file with mode: 0644]
www/plugins/agenda_3_5/prive/squelettes/contenu/agenda_inscriptions.html [new file with mode: 0755]
www/plugins/agenda_3_5/prive/squelettes/contenu/configurer_agenda.html [new file with mode: 0644]
www/plugins/agenda_3_5/prive/squelettes/contenu/evenement_edit.html [new file with mode: 0644]
www/plugins/agenda_3_5/prive/squelettes/contenu/evenements.html [new file with mode: 0644]
www/plugins/agenda_3_5/prive/squelettes/extra/agenda_inscriptions.html [new file with mode: 0644]
www/plugins/agenda_3_5/prive/squelettes/inclure/agenda-navigation-mois.html [new file with mode: 0644]
www/plugins/agenda_3_5/prive/squelettes/inclure/agenda-rubriques.html [new file with mode: 0644]
www/plugins/agenda_3_5/prive/squelettes/navigation/agenda_inscriptions.html [new file with mode: 0755]
www/plugins/agenda_3_5/prive/squelettes/navigation/evenement.html [new file with mode: 0644]
www/plugins/agenda_3_5/prive/squelettes/navigation/evenements.html [new file with mode: 0644]
www/plugins/agenda_3_5/prive/style_prive_plugin_agenda.html [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/agenda-16.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/agenda-24.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/agenda-32.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/agenda-add-16.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/agenda-add-24.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/agenda-add-32.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/agenda-del-16.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/agenda-del-24.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/agenda-del-32.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/agenda-edit-16.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/agenda-edit-24.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/agenda-edit-32.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/agenda-new-16.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/agenda-new-24.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/agenda-new-32.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/agenda-non-32.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/agenda-ok-32.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/evenement-16.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/evenement-24.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/evenement-32.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/evenement-add-16.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/evenement-add-24.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/evenement-add-32.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/evenement-del-16.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/evenement-del-24.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/evenement-del-32.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/evenement-edit-16.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/evenement-edit-24.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/evenement-edit-32.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/evenement-new-16.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/evenement-new-24.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/evenement-new-32.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/repetition-16.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/repetition-24.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/themes/spip/images/repetition-32.png [new file with mode: 0644]
www/plugins/agenda_3_5/prive/transmettre/evenement_participants.html [new file with mode: 0755]
www/plugins/agenda_3_5/prive/transmettre/evenements_participants.html [new file with mode: 0755]
www/plugins/agenda_3_5/public/agenda.php [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/agenda-ical.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/agenda-rss.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/agenda-zpip.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/aside/agenda.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/breadcrumb/agenda.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/calendrier_mini_event.json.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/calendrier_mini_event.json_fonctions.php [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/content/agenda.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/content/article-evenement.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/content/evenement.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/content/jour.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/content/rubrique-agenda.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/contenu/page-agenda.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/contenu/page-jour.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/extra/agenda.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/extra/evenement.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/extra/jour.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/extra/rubrique-agenda.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/extra1/agenda.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/extra1/evenement.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/extra1/jour.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/extra1/rubrique-agenda.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/ical-agenda.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/inc-rss-item-evenement.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/inclure/agenda-evenements-meme-article.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/inclure/agenda-liste.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/inclure/resume/evenement.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/navigation/page-agenda.html [new file with mode: 0644]
www/plugins/agenda_3_5/squelettes/style_public_plugin_agenda.html [new file with mode: 0644]
www/plugins/agenda_3_5/svn.revision [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/balise/calendrier_mini.php [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/calendrier_mini.json.html [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/calendrier_mini.json_fonctions.php [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/calendriermini_fonctions.php [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/css/img/month_next.png [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/css/img/month_prev.png [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/css/img/month_prev_next-32x16.png [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/css/minical.css [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/demos/minical_demo.html [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/formulaires/calendrier_mini.html [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/formulaires/configurer_calendriermini.html [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/javascript/calendrier_mini.js.html [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/javascript/jquery-ui.multidatespicker.js [new file with mode: 0755]
www/plugins/calendrier_mini-2.0/lang/minical.xml [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/lang/minical_ar.php [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/lang/minical_en.php [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/lang/minical_es.php [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/lang/minical_fa.php [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/lang/minical_fr.php [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/lang/minical_nl.php [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/lang/minical_sk.php [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/lang/minical_sl.php [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini.xml [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini_de.php [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini_en.php [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini_es.php [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini_fr.php [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini_nl.php [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini_sk.php [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/minical-32.png [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/modeles/archives_mensuelles.html [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/modeles/rubrique_calendrier.html [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/modeles/select_archives_mensuelles.html [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/paquet.xml [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/prive/squelettes/contenu/configurer_calendriermini.html [new file with mode: 0644]
www/plugins/calendrier_mini-2.0/svn.revision [new file with mode: 0644]
www/plugins/champs_extras3/base/cextras.php [new file with mode: 0644]
www/plugins/champs_extras3/cextras_fonctions.php [new file with mode: 0644]
www/plugins/champs_extras3/cextras_options.php [new file with mode: 0644]
www/plugins/champs_extras3/cextras_pipelines.php [new file with mode: 0644]
www/plugins/champs_extras3/images/cextras-64.png [new file with mode: 0644]
www/plugins/champs_extras3/inc/cextras.php [new file with mode: 0644]
www/plugins/champs_extras3/inc/cextras_autoriser.php [new file with mode: 0644]
www/plugins/champs_extras3/lang/cextras.xml [new file with mode: 0644]
www/plugins/champs_extras3/lang/cextras_ar.php [new file with mode: 0644]
www/plugins/champs_extras3/lang/cextras_en.php [new file with mode: 0644]
www/plugins/champs_extras3/lang/cextras_es.php [new file with mode: 0644]
www/plugins/champs_extras3/lang/cextras_fr.php [new file with mode: 0644]
www/plugins/champs_extras3/lang/cextras_it.php [new file with mode: 0644]
www/plugins/champs_extras3/lang/cextras_nl.php [new file with mode: 0644]
www/plugins/champs_extras3/lang/cextras_ru.php [new file with mode: 0644]
www/plugins/champs_extras3/lang/cextras_sk.php [new file with mode: 0644]
www/plugins/champs_extras3/lang/paquet-cextras.xml [new file with mode: 0644]
www/plugins/champs_extras3/lang/paquet-cextras_de.php [new file with mode: 0644]
www/plugins/champs_extras3/lang/paquet-cextras_en.php [new file with mode: 0644]
www/plugins/champs_extras3/lang/paquet-cextras_es.php [new file with mode: 0644]
www/plugins/champs_extras3/lang/paquet-cextras_fr.php [new file with mode: 0644]
www/plugins/champs_extras3/lang/paquet-cextras_it.php [new file with mode: 0644]
www/plugins/champs_extras3/lang/paquet-cextras_nl.php [new file with mode: 0644]
www/plugins/champs_extras3/lang/paquet-cextras_ru.php [new file with mode: 0644]
www/plugins/champs_extras3/lang/paquet-cextras_sk.php [new file with mode: 0644]
www/plugins/champs_extras3/paquet.xml [new file with mode: 0644]
www/plugins/champs_extras3/svn.revision [new file with mode: 0644]
www/plugins/clavettes [new submodule]
www/plugins/couleur_rubrique/formulaires/configurer_pb_couleur_rubrique.html [new file with mode: 0644]
www/plugins/couleur_rubrique/formulaires/couleur_rubrique.html [new file with mode: 0644]
www/plugins/couleur_rubrique/formulaires/couleur_rubrique.php [new file with mode: 0644]
www/plugins/couleur_rubrique/img_pack/pb_couleur_rubrique-24.png [new file with mode: 0644]
www/plugins/couleur_rubrique/img_pack/pb_couleur_rubrique.png [new file with mode: 0644]
www/plugins/couleur_rubrique/inclure/couleur_rubrique.html [new file with mode: 0644]
www/plugins/couleur_rubrique/javascript/pb_couleur_rubrique.js [new file with mode: 0644]
www/plugins/couleur_rubrique/lang/paquet-pb_couleur_rubrique_fr.php [new file with mode: 0644]
www/plugins/couleur_rubrique/lang/pb_couleur_rubrique_fr.php [new file with mode: 0644]
www/plugins/couleur_rubrique/paquet.xml [new file with mode: 0644]
www/plugins/couleur_rubrique/pb_couleur_rubrique_options.php [new file with mode: 0644]
www/plugins/couleur_rubrique/pb_couleur_rubrique_pipelines.php [new file with mode: 0644]
www/plugins/couleur_rubrique/prive/squelettes/contenu/configurer_pb_couleur_rubrique.html [new file with mode: 0644]
www/plugins/couleur_rubrique/svn.revision [new file with mode: 0644]
www/plugins/gis/action/editer_gis.php [new file with mode: 0644]
www/plugins/gis/action/editer_lien_gis.php [new file with mode: 0644]
www/plugins/gis/action/gis_geocoder_rechercher.php [new file with mode: 0644]
www/plugins/gis/action/kml_infos.php [new file with mode: 0644]
www/plugins/gis/action/supprimer_gis.php [new file with mode: 0644]
www/plugins/gis/base/gis.php [new file with mode: 0644]
www/plugins/gis/crud/gis.php [new file with mode: 0644]
www/plugins/gis/embed/kml.html [new file with mode: 0644]
www/plugins/gis/embed/kml_fonctions.php [new file with mode: 0644]
www/plugins/gis/formulaires/configurer_gis.html [new file with mode: 0755]
www/plugins/gis/formulaires/configurer_gis.php [new file with mode: 0644]
www/plugins/gis/formulaires/editer_gis.html [new file with mode: 0755]
www/plugins/gis/formulaires/editer_gis.php [new file with mode: 0644]
www/plugins/gis/formulaires/gis_inserer_modeles_traiter.php [new file with mode: 0644]
www/plugins/gis/formulaires/rechercher_gis.html [new file with mode: 0755]
www/plugins/gis/formulaires/rechercher_gis.php [new file with mode: 0755]
www/plugins/gis/gis_administrations.php [new file with mode: 0644]
www/plugins/gis/gis_autoriser.php [new file with mode: 0644]
www/plugins/gis/gis_download.html [new file with mode: 0644]
www/plugins/gis/gis_fonctions.php [new file with mode: 0755]
www/plugins/gis/gis_json.html [new file with mode: 0644]
www/plugins/gis/gis_kml.html [new file with mode: 0755]
www/plugins/gis/gis_options.php [new file with mode: 0755]
www/plugins/gis/gis_pipelines.php [new file with mode: 0755]
www/plugins/gis/icones_barre/gis.png [new file with mode: 0644]
www/plugins/gis/images/gis-16.png [new file with mode: 0644]
www/plugins/gis/images/gis-24.png [new file with mode: 0755]
www/plugins/gis/images/gis.png [new file with mode: 0755]
www/plugins/gis/inc/gis_xmlrpc.php [new file with mode: 0644]
www/plugins/gis/inc/iptc.php [new file with mode: 0644]
www/plugins/gis/inc/kml_infos.php [new file with mode: 0644]
www/plugins/gis/inclure/download_gpx.html [new file with mode: 0644]
www/plugins/gis/inclure/download_kml.html [new file with mode: 0644]
www/plugins/gis/inclure/gpx-item.html [new file with mode: 0755]
www/plugins/gis/inclure/kml-item.html [new file with mode: 0755]
www/plugins/gis/javascript/gis.js.html [new file with mode: 0644]
www/plugins/gis/javascript/gis_geocoder.js [new file with mode: 0644]
www/plugins/gis/javascript/gis_utils.js [new file with mode: 0644]
www/plugins/gis/javascript/leaflet.gis.js [new file with mode: 0644]
www/plugins/gis/json/gis.html [new file with mode: 0644]
www/plugins/gis/json/gis_articles.html [new file with mode: 0644]
www/plugins/gis/json/gis_articles_branche.html [new file with mode: 0644]
www/plugins/gis/json/gis_articles_plus_sites.html [new file with mode: 0644]
www/plugins/gis/json/gis_auteurs.html [new file with mode: 0644]
www/plugins/gis/json/gis_documents.html [new file with mode: 0644]
www/plugins/gis/json/gis_evenements.html [new file with mode: 0644]
www/plugins/gis/json/gis_mots.html [new file with mode: 0644]
www/plugins/gis/json/gis_point_libre.html [new file with mode: 0644]
www/plugins/gis/json/gis_rubriques.html [new file with mode: 0644]
www/plugins/gis/json/gis_sites.html [new file with mode: 0644]
www/plugins/gis/json/gis_tous_avec_liens_espace_prive.html [new file with mode: 0644]
www/plugins/gis/lang/gis.xml [new file with mode: 0644]
www/plugins/gis/lang/gis_en.php [new file with mode: 0644]
www/plugins/gis/lang/gis_es.php [new file with mode: 0644]
www/plugins/gis/lang/gis_fr.php [new file with mode: 0644]
www/plugins/gis/lang/gis_nl.php [new file with mode: 0644]
www/plugins/gis/lang/gis_ru.php [new file with mode: 0644]
www/plugins/gis/lang/gis_sk.php [new file with mode: 0644]
www/plugins/gis/lang/paquet-gis.xml [new file with mode: 0644]
www/plugins/gis/lang/paquet-gis_en.php [new file with mode: 0644]
www/plugins/gis/lang/paquet-gis_es.php [new file with mode: 0644]
www/plugins/gis/lang/paquet-gis_fr.php [new file with mode: 0644]
www/plugins/gis/lang/paquet-gis_nl.php [new file with mode: 0644]
www/plugins/gis/lang/paquet-gis_ru.php [new file with mode: 0644]
www/plugins/gis/lang/paquet-gis_sk.php [new file with mode: 0644]
www/plugins/gis/lib/leaflet/LICENSE [new file with mode: 0644]
www/plugins/gis/lib/leaflet/README.md [new file with mode: 0644]
www/plugins/gis/lib/leaflet/dist/images/layers-2x.png [new file with mode: 0644]
www/plugins/gis/lib/leaflet/dist/images/layers.png [new file with mode: 0644]
www/plugins/gis/lib/leaflet/dist/images/marker-icon-2x.png [new file with mode: 0644]
www/plugins/gis/lib/leaflet/dist/images/marker-icon.png [new file with mode: 0644]
www/plugins/gis/lib/leaflet/dist/images/marker-shadow.png [new file with mode: 0644]
www/plugins/gis/lib/leaflet/dist/leaflet-src.js [new file with mode: 0644]
www/plugins/gis/lib/leaflet/dist/leaflet.css [new file with mode: 0644]
www/plugins/gis/lib/leaflet/dist/leaflet.js [new file with mode: 0644]
www/plugins/gis/lib/leaflet/plugins/Bing.js [new file with mode: 0755]
www/plugins/gis/lib/leaflet/plugins/Control.FullScreen.js [new file with mode: 0644]
www/plugins/gis/lib/leaflet/plugins/Control.MiniMap.js [new file with mode: 0644]
www/plugins/gis/lib/leaflet/plugins/GPX.Speed.js [new file with mode: 0755]
www/plugins/gis/lib/leaflet/plugins/GPX.js [new file with mode: 0755]
www/plugins/gis/lib/leaflet/plugins/Google.js [new file with mode: 0755]
www/plugins/gis/lib/leaflet/plugins/KML.js [new file with mode: 0755]
www/plugins/gis/lib/leaflet/plugins/Marker.Rotate.js [new file with mode: 0755]
www/plugins/gis/lib/leaflet/plugins/images/icon-fullscreen-2x.png [new file with mode: 0644]
www/plugins/gis/lib/leaflet/plugins/images/icon-fullscreen.png [new file with mode: 0755]
www/plugins/gis/lib/leaflet/plugins/images/toggle.png [new file with mode: 0755]
www/plugins/gis/lib/leaflet/plugins/leaflet-plugins.css [new file with mode: 0755]
www/plugins/gis/lib/leaflet/plugins/leaflet-providers.js [new file with mode: 0644]
www/plugins/gis/lib/leaflet/plugins/leaflet.markercluster-src.js [new file with mode: 0644]
www/plugins/gis/lib/leaflet/plugins/leaflet.markercluster.css [new file with mode: 0755]
www/plugins/gis/modeles/carte_gis.html [new file with mode: 0644]
www/plugins/gis/modeles/carte_gis.yaml [new file with mode: 0644]
www/plugins/gis/modeles/carte_gis_objet.html [new file with mode: 0644]
www/plugins/gis/modeles/carte_gis_preview.html [new file with mode: 0755]
www/plugins/gis/paquet.xml [new file with mode: 0644]
www/plugins/gis/prive/contenu/gis_objet.html [new file with mode: 0644]
www/plugins/gis/prive/inclure/gis_objet_formulaires.html [new file with mode: 0644]
www/plugins/gis/prive/objets/contenu/gis.html [new file with mode: 0644]
www/plugins/gis/prive/objets/infos/gis.html [new file with mode: 0644]
www/plugins/gis/prive/objets/liste/gis.html [new file with mode: 0644]
www/plugins/gis/prive/objets/liste/gis_associer.html [new file with mode: 0644]
www/plugins/gis/prive/objets/liste/gis_associer_fonctions.php [new file with mode: 0644]
www/plugins/gis/prive/objets/liste/gis_lies.html [new file with mode: 0644]
www/plugins/gis/prive/objets/liste/gis_lies_fonctions.php [new file with mode: 0644]
www/plugins/gis/prive/objets/liste/objets_gis.html [new file with mode: 0644]
www/plugins/gis/prive/objets/liste/objets_gis_simple.html [new file with mode: 0644]
www/plugins/gis/prive/squelettes/contenu/configurer_gis.html [new file with mode: 0644]
www/plugins/gis/prive/squelettes/contenu/gis.html [new file with mode: 0644]
www/plugins/gis/prive/squelettes/contenu/gis_edit.html [new file with mode: 0644]
www/plugins/gis/prive/squelettes/contenu/gis_tous.html [new file with mode: 0644]
www/plugins/gis/prive/squelettes/extra/gis.html [new file with mode: 0644]
www/plugins/gis/prive/squelettes/hierarchie/gis.html [new file with mode: 0644]
www/plugins/gis/prive/squelettes/hierarchie/gis_edit.html [new file with mode: 0644]
www/plugins/gis/prive/squelettes/navigation/gis_edit.html [new file with mode: 0644]
www/plugins/gis/prive/squelettes/top/gis_tous.html [new file with mode: 0644]
www/plugins/gis/prive/style_prive_plugin_gis.html [new file with mode: 0644]
www/plugins/gis/prive/themes/spip/images/gis-16.png [new file with mode: 0644]
www/plugins/gis/prive/themes/spip/images/gis-24.png [new file with mode: 0755]
www/plugins/gis/prive/themes/spip/images/gis-new-16.png [new file with mode: 0644]
www/plugins/gis/saisies/carte.html [new file with mode: 0644]
www/plugins/gis/svn.revision [new file with mode: 0644]
www/plugins/gis/tests/gis_connect_sql.php [new file with mode: 0644]
www/plugins/icalendar/demo/iter_icalendar.html [new file with mode: 0644]
www/plugins/icalendar/http/ical.php [new file with mode: 0644]
www/plugins/icalendar/http/ical/inc-event.html [new file with mode: 0644]
www/plugins/icalendar/icalendar.png [new file with mode: 0644]
www/plugins/icalendar/inc/ics_to_array.php [new file with mode: 0644]
www/plugins/icalendar/lang/paquet-icalendar_fr.php [new file with mode: 0644]
www/plugins/icalendar/lib/iCalcreator.class.php [new file with mode: 0644]
www/plugins/icalendar/modeles/prochainement.html [new file with mode: 0644]
www/plugins/icalendar/paquet.xml [new file with mode: 0644]
www/plugins/icalendar/plugin.xml [new file with mode: 0644]
www/plugins/icalendar/svn.revision [new file with mode: 0644]
www/plugins/import_ics/action/supprimer_almanach.php [new file with mode: 0644]
www/plugins/import_ics/action/supprimer_evenements_almanach.php [new file with mode: 0644]
www/plugins/import_ics/action/synchro_almanach.php [new file with mode: 0644]
www/plugins/import_ics/base/import_ics.php [new file with mode: 0644]
www/plugins/import_ics/fabrique_diff.diff [new file with mode: 0644]
www/plugins/import_ics/fabrique_import_ics.php [new file with mode: 0644]
www/plugins/import_ics/formulaires/editer_almanach.html [new file with mode: 0644]
www/plugins/import_ics/formulaires/editer_almanach.php [new file with mode: 0644]
www/plugins/import_ics/formulaires/editer_almanach_2.html [new file with mode: 0644]
www/plugins/import_ics/genie/import_ics_synchro.php [new file with mode: 0644]
www/plugins/import_ics/genie/synchro.php [new file with mode: 0644]
www/plugins/import_ics/import_ics_administrations.php [new file with mode: 0644]
www/plugins/import_ics/import_ics_autorisations.php [new file with mode: 0644]
www/plugins/import_ics/import_ics_fonctions.php [new file with mode: 0644]
www/plugins/import_ics/import_ics_pipelines.php [new file with mode: 0644]
www/plugins/import_ics/lang/almanach_fr.php [new file with mode: 0644]
www/plugins/import_ics/lang/import_ics_fr.php [new file with mode: 0644]
www/plugins/import_ics/lang/paquet-import_ics_fr.php [new file with mode: 0644]
www/plugins/import_ics/paquet.xml [new file with mode: 0644]
www/plugins/import_ics/prive/objets/contenu/almanach.html [new file with mode: 0644]
www/plugins/import_ics/prive/objets/liste/almanachs.html [new file with mode: 0644]
www/plugins/import_ics/prive/themes/spip/images/almanach-12.png [new file with mode: 0644]
www/plugins/import_ics/prive/themes/spip/images/almanach-16.png [new file with mode: 0644]
www/plugins/import_ics/prive/themes/spip/images/almanach-24.png [new file with mode: 0644]
www/plugins/import_ics/prive/themes/spip/images/almanach-32.png [new file with mode: 0644]
www/plugins/import_ics/prive/themes/spip/images/almanach-add-16.png [new file with mode: 0644]
www/plugins/import_ics/prive/themes/spip/images/almanach-add-24.png [new file with mode: 0644]
www/plugins/import_ics/prive/themes/spip/images/almanach-add-32.png [new file with mode: 0644]
www/plugins/import_ics/prive/themes/spip/images/almanach-del-16.png [new file with mode: 0644]
www/plugins/import_ics/prive/themes/spip/images/almanach-del-24.png [new file with mode: 0644]
www/plugins/import_ics/prive/themes/spip/images/almanach-del-32.png [new file with mode: 0644]
www/plugins/import_ics/prive/themes/spip/images/almanach-edit-16.png [new file with mode: 0644]
www/plugins/import_ics/prive/themes/spip/images/almanach-edit-24.png [new file with mode: 0644]
www/plugins/import_ics/prive/themes/spip/images/almanach-edit-32.png [new file with mode: 0644]
www/plugins/import_ics/prive/themes/spip/images/almanach-new-16.png [new file with mode: 0644]
www/plugins/import_ics/prive/themes/spip/images/almanach-new-24.png [new file with mode: 0644]
www/plugins/import_ics/prive/themes/spip/images/almanach-new-32.png [new file with mode: 0644]
www/plugins/import_ics/prive/themes/spip/images/import_ics-128.png [new file with mode: 0644]
www/plugins/import_ics/prive/themes/spip/images/import_ics-32.png [new file with mode: 0644]
www/plugins/import_ics/prive/themes/spip/images/import_ics-64.png [new file with mode: 0644]
www/plugins/import_ics/svn.revision [new file with mode: 0644]
www/plugins/pages/base/pages_tables.php [new file with mode: 0644]
www/plugins/pages/content/articles-resume.html [new file with mode: 0644]
www/plugins/pages/demo/inc-articles.html [new file with mode: 0644]
www/plugins/pages/demo/pages.html [new file with mode: 0644]
www/plugins/pages/formulaires/editer_identifiant_page.html [new file with mode: 0644]
www/plugins/pages/formulaires/editer_identifiant_page.php [new file with mode: 0644]
www/plugins/pages/images/page-128.png [new file with mode: 0644]
www/plugins/pages/lang/pages.xml [new file with mode: 0644]
www/plugins/pages/lang/pages_ar.php [new file with mode: 0644]
www/plugins/pages/lang/pages_en.php [new file with mode: 0644]
www/plugins/pages/lang/pages_es.php [new file with mode: 0644]
www/plugins/pages/lang/pages_fa.php [new file with mode: 0644]
www/plugins/pages/lang/pages_fr.php [new file with mode: 0644]
www/plugins/pages/lang/pages_nl.php [new file with mode: 0644]
www/plugins/pages/lang/pages_ru.php [new file with mode: 0644]
www/plugins/pages/lang/pages_sk.php [new file with mode: 0644]
www/plugins/pages/lang/paquet-pages.xml [new file with mode: 0644]
www/plugins/pages/lang/paquet-pages_ar.php [new file with mode: 0644]
www/plugins/pages/lang/paquet-pages_en.php [new file with mode: 0644]
www/plugins/pages/lang/paquet-pages_es.php [new file with mode: 0644]
www/plugins/pages/lang/paquet-pages_fa.php [new file with mode: 0644]
www/plugins/pages/lang/paquet-pages_fr.php [new file with mode: 0644]
www/plugins/pages/lang/paquet-pages_nl.php [new file with mode: 0644]
www/plugins/pages/lang/paquet-pages_ru.php [new file with mode: 0644]
www/plugins/pages/lang/paquet-pages_sk.php [new file with mode: 0644]
www/plugins/pages/pages_administrations.php [new file with mode: 0644]
www/plugins/pages/pages_autorisations.php [new file with mode: 0644]
www/plugins/pages/pages_fonctions.php [new file with mode: 0644]
www/plugins/pages/pages_pipelines.php [new file with mode: 0644]
www/plugins/pages/paquet.xml [new file with mode: 0644]
www/plugins/pages/prive/objets/editer/identifiant_page.html [new file with mode: 0644]
www/plugins/pages/prive/squelettes/contenu/pages.html [new file with mode: 0644]
www/plugins/pages/prive/themes/spip/images/page-16.png [new file with mode: 0644]
www/plugins/pages/prive/themes/spip/images/page-24.png [new file with mode: 0644]
www/plugins/pages/prive/themes/spip/images/page-32.png [new file with mode: 0644]
www/plugins/pages/prive/themes/spip/images/page-new-16.png [new file with mode: 0644]
www/plugins/pages/saisies-vues/pages_uniques.html [new file with mode: 0644]
www/plugins/pages/saisies/pages_uniques.html [new file with mode: 0644]
www/plugins/pages/svn.revision [new file with mode: 0644]
www/plugins/ressource/inc/ressource.php [new file with mode: 0644]
www/plugins/ressource/modeles/album_flickr.html [new file with mode: 0644]
www/plugins/ressource/modeles/application.html [new file with mode: 0644]
www/plugins/ressource/modeles/ressource.html [new file with mode: 0644]
www/plugins/ressource/modeles/ressource_embed.html [new file with mode: 0644]
www/plugins/ressource/paquet.xml [new file with mode: 0644]
www/plugins/ressource/plugin.xml [new file with mode: 0644]
www/plugins/ressource/svn.revision [new file with mode: 0644]
www/plugins/rss_article_3_0/base/rssarticle.php [new file with mode: 0644]
www/plugins/rss_article_3_0/exec/rss_article.php [new file with mode: 0644]
www/plugins/rss_article_3_0/formulaires/configurer_rssarticle.html [new file with mode: 0644]
www/plugins/rss_article_3_0/formulaires/editer_rssarticle.html [new file with mode: 0644]
www/plugins/rss_article_3_0/formulaires/editer_rssarticle.php [new file with mode: 0644]
www/plugins/rss_article_3_0/genie/rssarticle_copie.php [new file with mode: 0644]
www/plugins/rss_article_3_0/lang/paquet-rssarticle_fr.php [new file with mode: 0644]
www/plugins/rss_article_3_0/lang/rssarticle_ar.php [new file with mode: 0644]
www/plugins/rss_article_3_0/lang/rssarticle_fr.php [new file with mode: 0644]
www/plugins/rss_article_3_0/paquet.xml [new file with mode: 0644]
www/plugins/rss_article_3_0/prive/contenu/rssarticle.html [new file with mode: 0644]
www/plugins/rss_article_3_0/prive/squelettes/contenu/configurer_rssarticle.html [new file with mode: 0644]
www/plugins/rss_article_3_0/prive/themes/spip/images/rssarticle-128.png [new file with mode: 0644]
www/plugins/rss_article_3_0/prive/themes/spip/images/rssarticle-16.png [new file with mode: 0644]
www/plugins/rss_article_3_0/prive/themes/spip/images/rssarticle-32.png [new file with mode: 0644]
www/plugins/rss_article_3_0/prive/themes/spip/images/rssarticle-64.png [new file with mode: 0644]
www/plugins/rss_article_3_0/rssarticle_administrations.php [new file with mode: 0644]
www/plugins/rss_article_3_0/rssarticle_pipelines.php [new file with mode: 0644]
www/plugins/rss_article_3_0/svn.revision [new file with mode: 0644]
www/plugins/saisies/action/deplacer_saisie.php [new file with mode: 0644]
www/plugins/saisies/aide/saisies.html [new file with mode: 0644]
www/plugins/saisies/balise/configurer_saisie.php [new file with mode: 0644]
www/plugins/saisies/balise/generer_saisies.php [new file with mode: 0644]
www/plugins/saisies/balise/saisie.php [new file with mode: 0644]
www/plugins/saisies/balise/voir_saisie.php [new file with mode: 0644]
www/plugins/saisies/balise/voir_saisies.php [new file with mode: 0644]
www/plugins/saisies/contenu/page-saisies_cvt.html [new file with mode: 0644]
www/plugins/saisies/css/formulaires_constructeur.css [new file with mode: 0644]
www/plugins/saisies/extra-vues/pays.html [new file with mode: 0644]
www/plugins/saisies/formulaires/construire_formulaire.html [new file with mode: 0644]
www/plugins/saisies/formulaires/construire_formulaire.php [new file with mode: 0644]
www/plugins/saisies/formulaires/inc-construire_formulaire-actions.html [new file with mode: 0644]
www/plugins/saisies/formulaires/inc-generer_saisies_configurables.html [new file with mode: 0644]
www/plugins/saisies/formulaires/inc-saisies-cvt.html [new file with mode: 0644]
www/plugins/saisies/formulaires/saisies_cvt.html [new file with mode: 0644]
www/plugins/saisies/formulaires/saisies_cvt.php [new file with mode: 0644]
www/plugins/saisies/images/formulaire-annuler-16.png [new file with mode: 0644]
www/plugins/saisies/images/formulaire-configurer-16.png [new file with mode: 0644]
www/plugins/saisies/images/formulaire-deplacer-16.png [new file with mode: 0644]
www/plugins/saisies/images/formulaire-dupliquer-16.png [new file with mode: 0644]
www/plugins/saisies/images/formulaire-enregistrer-16.png [new file with mode: 0644]
www/plugins/saisies/images/formulaire-reinitialiser-24.png [new file with mode: 0644]
www/plugins/saisies/images/formulaire-saisie-defaut.png [new file with mode: 0644]
www/plugins/saisies/images/formulaire-supprimer-16.png [new file with mode: 0644]
www/plugins/saisies/images/logo_saisie_48.png [new file with mode: 0644]
www/plugins/saisies/images/saisies_auteurs.png [new file with mode: 0644]
www/plugins/saisies/images/saisies_case.png [new file with mode: 0644]
www/plugins/saisies/images/saisies_checkbox.png [new file with mode: 0644]
www/plugins/saisies/images/saisies_date.png [new file with mode: 0644]
www/plugins/saisies/images/saisies_explication.png [new file with mode: 0644]
www/plugins/saisies/images/saisies_fieldset.png [new file with mode: 0644]
www/plugins/saisies/images/saisies_hidden.png [new file with mode: 0644]
www/plugins/saisies/images/saisies_input.png [new file with mode: 0644]
www/plugins/saisies/images/saisies_oui_non.png [new file with mode: 0644]
www/plugins/saisies/images/saisies_radio.png [new file with mode: 0644]
www/plugins/saisies/images/saisies_selecteur_article.png [new file with mode: 0644]
www/plugins/saisies/images/saisies_selecteur_rubrique.png [new file with mode: 0644]
www/plugins/saisies/images/saisies_selecteur_rubrique_article.png [new file with mode: 0644]
www/plugins/saisies/images/saisies_selection.png [new file with mode: 0644]
www/plugins/saisies/images/saisies_selection_multiple.png [new file with mode: 0644]
www/plugins/saisies/images/saisies_textarea.png [new file with mode: 0644]
www/plugins/saisies/inc/saisies.php [new file with mode: 0644]
www/plugins/saisies/inc/saisies_afficher.php [new file with mode: 0644]
www/plugins/saisies/inc/saisies_lister.php [new file with mode: 0644]
www/plugins/saisies/inc/saisies_manipuler.php [new file with mode: 0644]
www/plugins/saisies/inclure/configurer_saisie.html [new file with mode: 0644]
www/plugins/saisies/inclure/configurer_saisie_fonctions.php [new file with mode: 0644]
www/plugins/saisies/inclure/generer_saisies.html [new file with mode: 0644]
www/plugins/saisies/inclure/js_afficher_si.html [new file with mode: 0644]
www/plugins/saisies/inclure/saisies_aide.html [new file with mode: 0644]
www/plugins/saisies/inclure/voir_saisies.html [new file with mode: 0644]
www/plugins/saisies/javascript/saisies.js [new file with mode: 0644]
www/plugins/saisies/lang/paquet-saisies.xml [new file with mode: 0644]
www/plugins/saisies/lang/paquet-saisies_ar.php [new file with mode: 0644]
www/plugins/saisies/lang/paquet-saisies_de.php [new file with mode: 0644]
www/plugins/saisies/lang/paquet-saisies_en.php [new file with mode: 0644]
www/plugins/saisies/lang/paquet-saisies_es.php [new file with mode: 0644]
www/plugins/saisies/lang/paquet-saisies_fr.php [new file with mode: 0644]
www/plugins/saisies/lang/paquet-saisies_fr_tu.php [new file with mode: 0644]
www/plugins/saisies/lang/paquet-saisies_nl.php [new file with mode: 0644]
www/plugins/saisies/lang/paquet-saisies_ru.php [new file with mode: 0644]
www/plugins/saisies/lang/paquet-saisies_sk.php [new file with mode: 0644]
www/plugins/saisies/lang/saisies.xml [new file with mode: 0644]
www/plugins/saisies/lang/saisies_ca.php [new file with mode: 0644]
www/plugins/saisies/lang/saisies_de.php [new file with mode: 0644]
www/plugins/saisies/lang/saisies_en.php [new file with mode: 0644]
www/plugins/saisies/lang/saisies_es.php [new file with mode: 0644]
www/plugins/saisies/lang/saisies_fa.php [new file with mode: 0644]
www/plugins/saisies/lang/saisies_fr.php [new file with mode: 0644]
www/plugins/saisies/lang/saisies_fr_tu.php [new file with mode: 0644]
www/plugins/saisies/lang/saisies_it.php [new file with mode: 0644]
www/plugins/saisies/lang/saisies_nl.php [new file with mode: 0644]
www/plugins/saisies/lang/saisies_ru.php [new file with mode: 0644]
www/plugins/saisies/lang/saisies_sk.php [new file with mode: 0644]
www/plugins/saisies/paquet.xml [new file with mode: 0644]
www/plugins/saisies/prive/exec/construire_formulaire.html [new file with mode: 0644]
www/plugins/saisies/prive/listes/articles_originaux_recursifs.html [new file with mode: 0644]
www/plugins/saisies/prive/listes/rubriques_recursives.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/_base.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/auteurs.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/case.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/checkbox.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/date.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/destinataires.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/explication.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/fieldset.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/groupe_mots.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/mot.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/oui_non.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/radio.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/secteur.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/selecteur.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/selecteur_article.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/selecteur_article_fonctions.php [new file with mode: 0644]
www/plugins/saisies/saisies-vues/selecteur_document.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/selecteur_rubrique.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/selecteur_rubrique_article.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/selecteur_site.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/selection.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/selection_multiple.html [new file with mode: 0644]
www/plugins/saisies/saisies.css.html [new file with mode: 0644]
www/plugins/saisies/saisies/_base.html [new file with mode: 0644]
www/plugins/saisies/saisies/articles_originaux.html [new file with mode: 0644]
www/plugins/saisies/saisies/auteurs.html [new file with mode: 0644]
www/plugins/saisies/saisies/auteurs.yaml [new file with mode: 0644]
www/plugins/saisies/saisies/case.html [new file with mode: 0644]
www/plugins/saisies/saisies/case.yaml [new file with mode: 0644]
www/plugins/saisies/saisies/checkbox.html [new file with mode: 0644]
www/plugins/saisies/saisies/checkbox.yaml [new file with mode: 0644]
www/plugins/saisies/saisies/choisir_objet.html [new file with mode: 0644]
www/plugins/saisies/saisies/choisir_objets.html [new file with mode: 0644]
www/plugins/saisies/saisies/couleur.html [new file with mode: 0644]
www/plugins/saisies/saisies/date.html [new file with mode: 0644]
www/plugins/saisies/saisies/date.yaml [new file with mode: 0644]
www/plugins/saisies/saisies/date_jour_mois_annee.html [new file with mode: 0644]
www/plugins/saisies/saisies/destinataires.html [new file with mode: 0644]
www/plugins/saisies/saisies/destinataires.yaml [new file with mode: 0644]
www/plugins/saisies/saisies/email.html [new file with mode: 0644]
www/plugins/saisies/saisies/explication.html [new file with mode: 0644]
www/plugins/saisies/saisies/explication.yaml [new file with mode: 0644]
www/plugins/saisies/saisies/fieldset.html [new file with mode: 0644]
www/plugins/saisies/saisies/fieldset.yaml [new file with mode: 0644]
www/plugins/saisies/saisies/groupe_mots.html [new file with mode: 0644]
www/plugins/saisies/saisies/hidden.html [new file with mode: 0644]
www/plugins/saisies/saisies/hidden.yaml [new file with mode: 0644]
www/plugins/saisies/saisies/input.html [new file with mode: 0644]
www/plugins/saisies/saisies/input.yaml [new file with mode: 0644]
www/plugins/saisies/saisies/mot.html [new file with mode: 0644]
www/plugins/saisies/saisies/mot.yaml [new file with mode: 0644]
www/plugins/saisies/saisies/oui_non.html [new file with mode: 0644]
www/plugins/saisies/saisies/oui_non.yaml [new file with mode: 0644]
www/plugins/saisies/saisies/pays.html [new file with mode: 0644]
www/plugins/saisies/saisies/police.html [new file with mode: 0644]
www/plugins/saisies/saisies/position_construire_formulaire.html [new file with mode: 0644]
www/plugins/saisies/saisies/radio.html [new file with mode: 0644]
www/plugins/saisies/saisies/radio.yaml [new file with mode: 0644]
www/plugins/saisies/saisies/recherche.html [new file with mode: 0644]
www/plugins/saisies/saisies/secteur.html [new file with mode: 0644]
www/plugins/saisies/saisies/selecteur.html [new file with mode: 0644]
www/plugins/saisies/saisies/selecteur_article.html [new file with mode: 0644]
www/plugins/saisies/saisies/selecteur_article.yaml [new file with mode: 0644]
www/plugins/saisies/saisies/selecteur_document.html [new file with mode: 0644]
www/plugins/saisies/saisies/selecteur_langue.html [new file with mode: 0644]
www/plugins/saisies/saisies/selecteur_rubrique.html [new file with mode: 0644]
www/plugins/saisies/saisies/selecteur_rubrique.yaml [new file with mode: 0644]
www/plugins/saisies/saisies/selecteur_rubrique_article.html [new file with mode: 0644]
www/plugins/saisies/saisies/selecteur_rubrique_article.yaml [new file with mode: 0644]
www/plugins/saisies/saisies/selecteur_site.html [new file with mode: 0644]
www/plugins/saisies/saisies/selection.html [new file with mode: 0644]
www/plugins/saisies/saisies/selection.yaml [new file with mode: 0644]
www/plugins/saisies/saisies/selection_multiple.html [new file with mode: 0755]
www/plugins/saisies/saisies/selection_multiple.yaml [new file with mode: 0644]
www/plugins/saisies/saisies/selection_par_groupe.html [new file with mode: 0644]
www/plugins/saisies/saisies/statuts_auteurs.html [new file with mode: 0644]
www/plugins/saisies/saisies/telephone.html [new file with mode: 0644]
www/plugins/saisies/saisies/textarea.html [new file with mode: 0644]
www/plugins/saisies/saisies/textarea.yaml [new file with mode: 0644]
www/plugins/saisies/saisies/true_false.html [new file with mode: 0644]
www/plugins/saisies/saisies/url.html [new file with mode: 0644]
www/plugins/saisies/saisies_fonctions.php [new file with mode: 0644]
www/plugins/saisies/saisies_options.php [new file with mode: 0644]
www/plugins/saisies/saisies_pipelines.php [new file with mode: 0644]
www/plugins/saisies/svn.revision [new file with mode: 0644]
www/plugins/saisies/test/configurer_saisie.html [new file with mode: 0644]
www/plugins/saisies/test/generer_saisies.html [new file with mode: 0644]
www/plugins/saisies/test/saisie.html [new file with mode: 0644]
www/plugins/saisies/test/voir_saisie.html [new file with mode: 0644]
www/plugins/saisies/test/voir_saisies.html [new file with mode: 0644]
www/plugins/seminaire/agenda.json.html [new file with mode: 0644]
www/plugins/seminaire/agenda/inc-agenda.html [new file with mode: 0644]
www/plugins/seminaire/article_corps.html [new file with mode: 0644]
www/plugins/seminaire/base/seminaire.php [new file with mode: 0644]
www/plugins/seminaire/calendrier.html [new file with mode: 0644]
www/plugins/seminaire/contenu/calendrier.html [new file with mode: 0644]
www/plugins/seminaire/contenu/page-agenda.html [new file with mode: 0644]
www/plugins/seminaire/contenu/page-evenement.html [new file with mode: 0644]
www/plugins/seminaire/contenu/page-jour.html [new file with mode: 0644]
www/plugins/seminaire/evenement.html [new file with mode: 0644]
www/plugins/seminaire/export_xml.html [new file with mode: 0644]
www/plugins/seminaire/extra/page-agenda.html [new file with mode: 0644]
www/plugins/seminaire/fabrique_seminaire.php [new file with mode: 0644]
www/plugins/seminaire/formulaires/configurer_seminaire.html [new file with mode: 0644]
www/plugins/seminaire/formulaires/editer_evenement.html [new file with mode: 0644]
www/plugins/seminaire/formulaires/editer_evenement.php [new file with mode: 0644]
www/plugins/seminaire/head/page-agenda.html [new file with mode: 0644]
www/plugins/seminaire/images/calendrier.png [new file with mode: 0644]
www/plugins/seminaire/images/logo_seminaire.png [new file with mode: 0644]
www/plugins/seminaire/inc/inc-p-calendrier.html [new file with mode: 0755]
www/plugins/seminaire/inc/inc-p-evenement.html [new file with mode: 0755]
www/plugins/seminaire/inc/inc-p-jour.html [new file with mode: 0755]
www/plugins/seminaire/inclure/agenda_article.html [new file with mode: 0644]
www/plugins/seminaire/inclure/evenement-ical.html [new file with mode: 0755]
www/plugins/seminaire/inclure/evenement-xml.html [new file with mode: 0644]
www/plugins/seminaire/javascript/rechargement.html [new file with mode: 0644]
www/plugins/seminaire/javascript/toggle.js [new file with mode: 0644]
www/plugins/seminaire/jour.html [new file with mode: 0644]
www/plugins/seminaire/lang/paquet-seminaire_fr.php [new file with mode: 0644]
www/plugins/seminaire/lang/seminaire_fr.php [new file with mode: 0755]
www/plugins/seminaire/modeles/connexion.html [new file with mode: 0644]
www/plugins/seminaire/modeles/evenement_vevent.html [new file with mode: 0644]
www/plugins/seminaire/navigation/dist.html [new file with mode: 0644]
www/plugins/seminaire/navigation/page-agenda.html [new file with mode: 0644]
www/plugins/seminaire/paquet.xml [new file with mode: 0644]
www/plugins/seminaire/prive/squelettes/contenu/configurer_seminaire.html [new file with mode: 0644]
www/plugins/seminaire/prive/themes/spip/images/seminaire-128.png [new file with mode: 0644]
www/plugins/seminaire/prive/themes/spip/images/seminaire-32.png [new file with mode: 0644]
www/plugins/seminaire/prive/themes/spip/images/seminaire-64.png [new file with mode: 0644]
www/plugins/seminaire/seminaire_administrations.php [new file with mode: 0644]
www/plugins/seminaire/seminaire_ical.html [new file with mode: 0755]
www/plugins/seminaire/seminaire_pipelines.php [new file with mode: 0644]
www/plugins/seminaire/styles/calendrier-seminaire.css [new file with mode: 0644]
www/plugins/seminaire/svn.revision [new file with mode: 0644]

diff --git a/.gitmodules b/.gitmodules
new file mode 100644 (file)
index 0000000..b71ef4e
--- /dev/null
@@ -0,0 +1,3 @@
+[submodule "www/plugins/clavettes"]
+       path = www/plugins/clavettes
+       url = git://git.heureux-cyclage.org/lhc/web/clavettes
diff --git a/www/plugins/agenda_3_5/action/activer_agenda_rubrique.php b/www/plugins/agenda_3_5/action/activer_agenda_rubrique.php
new file mode 100644 (file)
index 0000000..0fb08c7
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Plugin Agenda 4 pour Spip 3.0
+ * Licence GPL 3
+ *
+ * 2006-2011
+ * Auteurs : cf paquet.xml
+ */
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+
+function action_activer_agenda_rubrique_dist()
+{
+       $securiser_action = charger_fonction('securiser_action', 'inc');
+       $arg = $securiser_action();
+       
+       // A-t-on vraiment le droit de modifier la rubrique en question ?
+       if (!autoriser('modifier', 'rubrique', abs(intval($arg)))){
+               include_spip('inc/minipres');
+               echo minipres(_T('info_acces_interdit'));
+               exit;
+       }
+       
+       if (intval($arg)!=0) {
+               if (intval($arg)>0)
+                       sql_updateq('spip_rubriques',array('agenda'=>1),'id_rubrique='.intval($arg));
+               else
+                       sql_updateq('spip_rubriques',array('agenda'=>0),'id_rubrique='.(-intval($arg)));
+       }
+}
+
+?>
diff --git a/www/plugins/agenda_3_5/action/editer_evenement.php b/www/plugins/agenda_3_5/action/editer_evenement.php
new file mode 100644 (file)
index 0000000..d192815
--- /dev/null
@@ -0,0 +1,383 @@
+<?php
+/**
+ * Plugin Agenda 4 pour Spip 3.0
+ * Licence GPL 3
+ *
+ * 2006-2011
+ * Auteurs : cf paquet.xml
+ */
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+/**
+ * Edition d'un evenement
+ * @param string $arg
+ * @return array
+ */
+function action_editer_evenement_dist($arg=null){
+
+       if (is_null($arg)){
+               $securiser_action = charger_fonction('securiser_action', 'inc');
+               $arg = $securiser_action();
+       }
+
+       // si id_evenement n'est pas un nombre, c'est une creation
+       // mais on verifie qu'on a toutes les donnees qu'il faut.
+       if (!$id_evenement = intval($arg)) {
+               $id_parent = _request('id_parent');
+               if (!$id_evenement = agenda_action_insert_evenement($id_parent))
+                       return array(false,_L('echec'));
+       }
+
+       $err = action_evenement_set($id_evenement);
+       return array($id_evenement,$err);
+}
+
+/**
+ * Creer un nouvel evenement
+ *
+ * @param int $id_article
+ * @param int $id_evenement_source
+ * @return int
+ */
+function evenement_inserer($id_article,$id_evenement_source = 0){
+       include_spip('inc/autoriser');
+       if (!autoriser('creerevenementdans','article',$id_article)){
+               spip_log("agenda action formulaire article : auteur ".$GLOBALS['visiteur_session']['id_auteur']." n'a pas le droit de creer un evenement dans article $id_article",'agenda');
+               return false;
+       }
+
+       $champs = array(
+               "id_evenement_source"=>intval($id_evenement_source),
+               'id_article'=>intval($id_article),
+               'statut' => 'prop',
+       );
+
+       // Envoyer aux plugins
+       $champs = pipeline('pre_insertion',
+               array(
+                       'args' => array(
+                               'table' => 'spip_evenements',
+                       ),
+                       'data' => $champs
+               )
+       );
+
+       // nouvel evenement
+       $id_evenement = sql_insertq("spip_evenements", $champs);
+
+       pipeline('post_insertion',
+               array(
+                       'args' => array(
+                               'table' => 'spip_evenements',
+                               'id_objet' => $id_evenement
+                       ),
+                       'data' => $champs
+               )
+       );
+
+       if (!$id_evenement){
+               spip_log("agenda action formulaire evenement : impossible d'ajouter un evenement",'agenda');
+               return false;
+       }
+       return $id_evenement;
+}
+
+/**
+ * Modifier un evenement existant
+ *
+ * @param int $id_evenement
+ * @param array $set
+ * @return bool|string
+ */
+function evenement_modifier($id_evenement, $set=null){
+
+       include_spip('inc/modifier');
+       include_spip('inc/filtres');
+       $c = collecter_requests(
+               // white list
+               objet_info('evenement','champs_editables'),
+               // black list
+               array('statut','id_article'),
+               // donnees eventuellement fournies
+               $set
+       );
+
+       // Si l'evenement est publie, invalider les caches et demander sa reindexation
+       $t = sql_getfetsel("statut", "spip_evenements", "id_evenement=".intval($id_evenement));
+       $invalideur = $indexation = false;
+       if ($t == 'publie') {
+               $invalideur = "id='evenement/$id_evenement'";
+               $indexation = true;
+       }
+
+       if ($err = objet_modifier_champs('evenement', $id_evenement,
+               array(
+                       'nonvide' => array('titre' => _T('info_nouvel_evenement')." "._T('info_numero_abbreviation').$id_evenement),
+                       'invalideur' => $invalideur,
+                       'indexation' => $indexation,
+               ),
+               $c))
+               return $err;
+
+       if (!is_null($repetitions = _request('repetitions', $set)))
+               agenda_action_revision_evenement_repetitions($id_evenement, $repetitions);
+
+       // Modification de statut, changement de parent ?
+       $c = collecter_requests(array('statut', 'id_parent'), array(), $set);
+       $err = evenement_instituer($id_evenement, $c);
+
+       return $err;
+}
+
+
+function agenda_action_revision_evenement_repetitions($id_evenement, $repetitions=""){
+       include_spip('inc/filtres');
+       $repetitions = preg_split(",[^0-9\-\/],",$repetitions);
+       // gestion des repetitions
+       $rep = array();
+       foreach($repetitions as $date){
+               if (strlen($date)){
+                       $date = recup_date($date);
+                       if ($date=mktime(0,0,0,$date[1],$date[2],$date[0]))
+                               $rep[] = $date;
+               }
+       }
+       agenda_action_update_repetitions($id_evenement, $rep);
+}
+
+function agenda_action_update_repetitions($id_evenement, $repetitions){
+       // evenement source
+       if ($row = sql_fetsel("*", "spip_evenements","id_evenement=".intval($id_evenement))){
+               // Si ce n'est pas un événement source, on n'a rien à faire ici
+               if ($row['id_evenement_source'] != 0){ return; }
+               
+               // On ne garde que les données correctes pour une modification
+               $c = collecter_requests(
+                       // white list
+                       objet_info('evenement','champs_editables'),
+                       // black list
+                       array('id_evenement', 'id_evenement_source'),
+                       // donnees fournies
+                       $row
+               );
+               
+               // Savoir si la source était publiée ou pas
+               $publie = ($row['statut'] == 'publie');
+               
+               // On calcule la durée en secondes de l'événement source pour la reporter telle quelle aux répétitions
+               $date_debut = strtotime($row['date_debut']);
+               $date_fin = strtotime($row['date_fin']);
+               $duree = $date_fin - $date_debut;
+               
+               $repetitions_updated = array();
+               // mettre a jour toutes les repetitions deja existantes ou les supprimer si plus lieu
+               $res = sql_select("id_evenement,date_debut","spip_evenements","id_evenement_source=".intval($id_evenement));
+               while ($row = sql_fetch($res)){
+                       $date = strtotime(date('Y-m-d', strtotime($row['date_debut'])));
+                       if (in_array($date, $repetitions)){
+                               // Cette répétition existe déjà, on la met à jour
+                               $repetitions_updated[] = $date;
+                               
+                               // On calcule les nouvelles dates/heures en reportant la durée de la source
+                               $update_date_debut = date('Y-m-d', $date).' '.date('H:i:s', $date_debut);
+                               $update_date_fin = date('Y-m-d H:i:s', strtotime($update_date_debut)+$duree);
+                               
+                               // Seules les dates sont changées dans les champs de la source
+                               // TODO : prendre en charge la mise a jour uniquement si conforme a l'original
+                               $c['date_debut'] = $update_date_debut;
+                               $c['date_fin'] = $update_date_fin;
+
+                               // mettre a jour l'evenement
+                               sql_updateq(
+                                       'spip_evenements',
+                                       $c,
+                                       'id_evenement = '.$row['id_evenement']
+                               );
+                       }
+                       else {
+                               // il est supprime
+                               sql_delete("spip_evenements","id_evenement=".$row['id_evenement']);
+                       }
+               }
+               
+               // regarder les repetitions a ajouter
+               foreach($repetitions as $date){
+                       if (!in_array($date, $repetitions_updated)){
+                               // On calcule les dates/heures en reportant la durée de la source
+                               $update_date_debut = date('Y-m-d', $date)." ".date('H:i:s', $date_debut);
+                               $update_date_fin = date('Y-m-d H:i:s', strtotime($update_date_debut)+$duree);
+                               
+                               // Seules les dates sont changées dans les champs de la source
+                               $c['date_debut'] = $update_date_debut;
+                               $c['date_fin'] = $update_date_fin;
+                               
+                               // On crée la nouvelle répétition
+                               if ($id_evenement_new = agenda_action_insert_evenement($c['id_article'], $id_evenement)) {
+                                       // Si c'est bon, on ajoute tous les champs
+                                       sql_updateq(
+                                               'spip_evenements',
+                                               $c,
+                                               'id_evenement = '.$id_evenement_new
+                                       );
+                                       
+                                       // Pour les créations il ne faut pas oublier de dupliquer les liens
+                                       // En effet, sinon les documents insérés avant la création (0-id_auteur) ne seront pas dupliqués
+                                       include_spip('action/editer_liens');
+                                       objet_dupliquer_liens('evenement', $id_evenement, $id_evenement_new);
+                               }
+                       }
+               }
+       }
+}
+
+/**
+ * Instituer un evenement
+ *
+ * @param int $id_evenement
+ * @param array $c
+ * @return bool|string
+ */
+function evenement_instituer($id_evenement, $c) {
+
+       include_spip('inc/autoriser');
+       include_spip('inc/modifier');
+
+       $row = sql_fetsel("id_article, statut", "spip_evenements", "id_evenement=".intval($id_evenement));
+       $id_parent  = $id_parent_ancien = $row['id_article'];
+       $statut = $statut_ancien = $row['statut'];
+
+       $champs = array();
+
+       if (!autoriser('modifier', 'article', $id_parent)
+         OR (isset($c['id_parent']) AND !autoriser('modifier', 'article', $c['id_parent']))){
+               spip_log("editer_evenement $id_evenement refus " . join(' ', $c));
+               return false;
+       }
+
+       // Verifier que l'article demande existe et est different
+       // de l'article actuel
+       if ($c['id_parent']
+         AND $c['id_parent'] != $id_parent
+         AND (sql_countsel("spip_articles", "id_article=".intval($c['id_parent'])))) {
+               $id_parent = $champs['id_article'] = $c['id_parent'];
+       }
+
+       $sa = sql_getfetsel('statut','spip_articles','id_article='.intval($id_parent));
+       if ($id_parent
+         AND (
+                       $id_parent!==$id_parent_ancien OR $statut=='0'
+               )){
+               switch($sa){
+                       case 'publie':
+                               // statut par defaut si besoin
+                               if ($statut=='0')
+                                       $champs['statut'] = $statut = 'publie';
+                               break;
+                       case 'poubelle':
+                               // si article a la poubelle, evenement aussi
+                               $champs['statut'] = $statut = 'poubelle';
+                               break;
+                       default:
+                               // pas de publie ni 0 si article pas publie
+                               if (in_array($statut,array('publie','0')))
+                                       $champs['statut'] = $statut = 'prop';
+                               break;
+               }
+       }
+
+       // si pas d'article lie, et statut par defaut
+       // on met en propose
+       if ($statut=='0')
+               $champs['statut'] = $statut = 'prop';
+
+       if (isset($c['statut'])
+               AND $s = $c['statut']
+               AND $s != $statut) {
+               // pour instituer un evenement il faut avoir le droit d'instituer l'article associe avec le meme statut
+               if (autoriser('instituer', 'article', $id_parent, null, array('statut'=>$s))
+                 AND ($sa=='publie' OR $s!=='publie'))
+                       $champs['statut'] = $statut = $s;
+               else
+                       spip_log("editer_evenement $id_evenement refus " . join(' ', $c));
+       }
+
+       // Envoyer aux plugins
+       $champs = pipeline('pre_edition',
+               array(
+                       'args' => array(
+                               'table' => 'spip_evenements',
+                               'action'=>'instituer',
+                               'id_objet' => $id_evenement,
+                               'id_parent_ancien' => $id_parent_ancien,
+                               'statut_ancien' => $statut_ancien,
+                       ),
+                       'data' => $champs
+               )
+       );
+
+       if (!count($champs)) return;
+
+       // Envoyer les modifs sur l'evenement et toutes ses repetitons
+       $ids = array_map('reset', sql_allfetsel('id_evenement', 'spip_evenements', 'id_evenement_source='.intval($id_evenement)));
+       $ids[] = intval($id_evenement);
+       sql_updateq('spip_evenements', $champs, sql_in('id_evenement', $ids));
+
+       // Invalider les caches
+       include_spip('inc/invalideur');
+       suivre_invalideur("id='id_article/$id_parent_ancien'");
+       suivre_invalideur("id='id_article/$id_parent'");
+
+       // Pipeline
+       pipeline('post_edition',
+               array(
+                       'args' => array(
+                               'table' => 'spip_evenements',
+                               'action'=>'instituer',
+                               'id_objet' => $id_evenement,
+                               'id_parent_ancien' => $id_parent_ancien,
+                               'statut_ancien' => $statut_ancien,
+                       ),
+                       'data' => $champs
+               )
+       );
+
+       // Notifications
+       if ($notifications = charger_fonction('notifications', 'inc')) {
+               $notifications('instituerevenement', $id_evenement,
+                       array('id_parent' => $id_parent, 'statut' => $statut, 'id_parent_ancien' => $id_parent, 'statut_ancien' => $statut_ancien)
+               );
+       }
+
+       return ''; // pas d'erreur
+}
+
+/*
+function agenda_action_supprime_repetitions($supp_evenement){
+       $res = sql_select("id_evenement", "spip_evenements", "id_evenement_source=".intval($supp_evenement));
+       while ($row = sql_fetch($res)){
+               $id_evenement = $row['id_evenement'];
+               sql_delete("spip_evenements", "id_evenement=".intval($id_evenement));
+       }
+}
+*/
+/*
+function agenda_action_supprime_evenement($id_article,$supp_evenement){
+       $id_evenement = sql_getfetsel("id_evenement", "spip_evenements", array(
+               "id_article=" . intval($id_article),
+               "id_evenement=" . intval($supp_evenement)));
+       if (intval($id_evenement) AND $id_evenement == $supp_evenement){
+               sql_delete("spip_evenements", "id_evenement=".intval($id_evenement));
+               agenda_action_supprime_repetitions($id_evenement);
+       }
+       include_spip('inc/invalideur');
+       suivre_invalideur("article/$id_article");
+       $id_evenement = 0;
+       return $id_evenement;
+}*/
+
+
+function agenda_action_insert_evenement($id_article,$id_evenement_source = 0){return evenement_inserer($id_article,$id_evenement_source);}
+function action_evenement_set($id_evenement, $set=null){return evenement_modifier($id_evenement, $set);}
+function agenda_action_instituer_evenement($id_evenement, $c) {return evenement_instituer($id_evenement,$c);}
+?>
diff --git a/www/plugins/agenda_3_5/action/supprimer_evenement.php b/www/plugins/agenda_3_5/action/supprimer_evenement.php
new file mode 100644 (file)
index 0000000..dc0b8c9
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Plugin Agenda 4 pour Spip 3.0
+ * Licence GPL 3
+ *
+ * 2006-2011
+ * Auteurs : cf paquet.xml
+ */
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+
+function action_supprimer_evenement_dist()
+{
+       $securiser_action = charger_fonction('securiser_action', 'inc');
+       $arg = $securiser_action();
+       
+       
+       list($id_evenement,$id_article) = preg_split(',[^0-9],',$arg);
+       include_spip('inc/autoriser');
+       if (intval($id_article) AND intval($id_evenement) AND autoriser('supprimer','evenement',$id_evenement,null,array('id_article'=>$id_article))){
+               include_spip("action/editer_evenement");
+               evenement_modifier($id_evenement,array('statut'=>'poubelle'));
+       }
+}
+
+?>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/action/supprimer_evenement_participant.php b/www/plugins/agenda_3_5/action/supprimer_evenement_participant.php
new file mode 100644 (file)
index 0000000..fad3ec0
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Plugin Agenda 4 pour Spip 3.0
+ * Licence GPL 3
+ *
+ * 2006-2011
+ * Auteurs : cf paquet.xml
+ */
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+
+function action_supprimer_evenement_participant_dist()
+{
+       $securiser_action = charger_fonction('securiser_action', 'inc');
+       $arg = $securiser_action();
+       list($id_evenement,$id_evenement_participant) = explode('-',$arg);
+       include_spip('inc/autoriser');
+       if (intval($id_evenement) AND autoriser('modifier','evenement',$id_evenement)){
+               if(intval($id_evenement_participant) ){
+            sql_delete('spip_evenements_participants','id_evenement='.intval($id_evenement).' AND id_evenement_participant='.intval($id_evenement_participant));
+        } else if($id_evenement_participant=='tous'){
+            sql_delete('spip_evenements_participants','id_evenement='.intval($id_evenement));
+        }
+       } 
+    return true;
+}
+
+
+?>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/agenda_administrations.php b/www/plugins/agenda_3_5/agenda_administrations.php
new file mode 100644 (file)
index 0000000..2e02f7c
--- /dev/null
@@ -0,0 +1,117 @@
+<?php
+/**
+ * Plugin Agenda 4 pour Spip 3.0
+ * Licence GPL 3
+ *
+ * 2006-2011
+ * Auteurs : cf paquet.xml
+ */
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+/**
+ * Installation/maj des tables evenements et participants...
+ *
+ * @param string $nom_meta_base_version
+ * @param string $version_cible
+ */
+function agenda_upgrade($nom_meta_base_version,$version_cible){
+
+
+       $maj = array();
+       $maj['create'] = array(
+               array('maj_tables',array('spip_evenements','spip_evenements_participants')),
+               array('sql_alter',"TABLE spip_rubriques ADD agenda tinyint(1) DEFAULT 0 NOT NULL"),
+       );
+       $maj['0.11'] = array(
+               array('sql_alter',"TABLE spip_evenements ADD `horaire` ENUM('oui','non') DEFAULT 'oui' NOT NULL AFTER `lieu`"),
+       );
+       $maj['0.12'] = array(
+               array('sql_alter',"TABLE spip_evenements ADD `id_article`  bigint(21) DEFAULT '0' NOT NULL AFTER `id_evenement`"),
+               array('sql_alter',"TABLE spip_evenements ADD ADD INDEX ( `id_article` )"),              
+               array('upgrade_evenements_articles_012'),
+               array('sql_drop_table',"spip_evenements_articles"),
+       );
+       
+       $maj['0.13'] = array(
+               array('maj_tables',array('spip_evenements','spip_evenements_participants')),
+       );
+       $maj['0.18'] = array(
+               array('maj_tables',array('spip_evenements','spip_evenements_participants')),
+               array('sql_update',"spip_groupes_mots", array('tables_liees'=>"concat(tables_liees,'evenements,')"), "evenements='oui'"),
+               array('sql_alter',"TABLE spip_groupes_mots DROP evenements"),   
+       );      
+
+       $maj['0.20'] = array(
+               array('sql_alter',"TABLE spip_rubriques ADD agenda tinyint(1) DEFAULT 0 NOT NULL"),
+       );      
+       
+       $maj['0.21'] = array(
+               array('sql_alter',"TABLE spip_evenements ADD adresse text NOT NULL"),
+               array('sql_alter',"TABLE spip_evenements ADD inscription text NOT NULL"),
+               array('sql_alter',"TABLE spip_evenements ADD places text NOT NULL"),
+       );      
+       
+       $maj['0.22'] = array(
+               array('maj_tables',array('spip_evenements_participants')),
+       );      
+       
+       $maj['0.23'] = array(
+               array('sql_alter',"TABLE spip_evenements CHANGE titre titre text NOT NULL DEFAULT ''"),
+               array('sql_alter',"TABLE spip_evenements CHANGE descriptif descriptif text NOT NULL DEFAULT ''"),
+               array('sql_alter',"TABLE spip_evenements CHANGE lieu lieu text NOT NULL DEFAULT ''"),
+               array('sql_alter',"TABLE spip_evenements CHANGE adresse adresse text NOT NULL DEFAULT ''"),
+       );              
+       include_spip('maj/svn10000');
+       $maj['0.24.0'] = array(
+               array('maj_liens','mot','evenement'),
+               array('sql_drop_table',"spip_mots_evenements"),
+               array('sql_alter',"TABLE spip_evenements ADD statut varchar(10) DEFAULT 0 NOT NULL"),
+       );
+       
+       $maj['0.25.0'] = array(
+               array('upgrade_evenements_statut_025'),
+       );
+       
+       $maj['0.26.0'] = array(
+               array('maj_tables',array('spip_evenements')),
+               array('sql_update',"spip_evenements", array('date_creation'=>'maj')),
+       );
+
+    $maj['0.27.0'] = array(
+        // modification de la cle primaire (id_evenement,id_auteur) : les participants peuvent ne pas être des auteurs
+        // ajout d'une clé primaire "neutre" auto-incrémentée
+        array('sql_alter','TABLE spip_evenements_participants DROP PRIMARY KEY'),
+        array('sql_alter','TABLE spip_evenements_participants ADD id_evenement_participant BIGINT(21) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST'),
+        array('maj_tables',array('spip_evenements_participants')),
+    );
+
+       include_spip('base/upgrade');
+       maj_plugin($nom_meta_base_version, $version_cible, $maj);
+}
+
+function agenda_vider_tables($nom_meta_base_version) {
+       sql_drop_table("spip_evenements");
+       #sql_drop_table("spip_mots_evenements"); // au cas ou ?
+       sql_alter("TABLE spip_rubriques DROP COLUMN agenda");
+       effacer_meta($nom_meta_base_version);
+}
+
+function upgrade_evenements_articles_012(){
+       $res = sql_select("*", "spip_evenements_articles");
+       while ($row = sql_fetch($res)){
+               $id_article = $row['id_article'];
+               $id_evenement = $row['id_evenement'];
+               sql_update("spip_evenements", array('id_article'=>$id_article),'id_evenement='.intval($id_evenement));
+       }
+}
+
+function upgrade_evenements_statut_025(){
+       include_spip('action/editer_evenement');
+       $res = sql_select('id_evenement','spip_evenements',"statut='0'");
+       while ($row = sql_fetch($res)){
+               evenement_modifier($row['id_evenement'],array());
+       }
+}
+
+?>
diff --git a/www/plugins/agenda_3_5/agenda_autoriser.php b/www/plugins/agenda_3_5/agenda_autoriser.php
new file mode 100644 (file)
index 0000000..5a944c9
--- /dev/null
@@ -0,0 +1,146 @@
+<?php
+/**
+ * Plugin Agenda 4 pour Spip 3.0
+ * Licence GPL 3
+ *
+ * 2006-2011
+ * Auteurs : cf paquet.xml
+ */
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+/* pour que le pipeline ne rale pas ! */
+function agenda_autoriser(){}
+
+function autoriser_evenementcreer_menu_dist($faire, $type='', $id=0, $qui = NULL, $opt = NULL){
+       autoriser('creer','evenement',$id,$qui,$opt);
+}
+
+function autoriser_evenements_menu_dist($faire, $type='', $id=0, $qui = NULL, $opt = NULL){
+       return true;
+}
+
+/**
+ * Autorisation d'ajout d'un evenement a un article
+ *
+ * @param string $faire
+ * @param string $quoi
+ * @param int $id
+ * @param int $qui
+ * @param array $options
+ * @return bool
+ */
+function autoriser_article_creerevenementdans_dist($faire,$quoi,$id,$qui,$options){
+       if (!$id) return false; // interdit de creer un evenement sur un article vide !
+       // si on a le droit de modifier l'article alors on a peut-etre le droit d'y creer un evenement
+       $afficher = false;
+       if (autoriser('modifier','article',$id,$qui)) {
+               $afficher = true;
+               // un article avec des evenements a toujours le droit
+               if (!sql_countsel('spip_evenements','id_article='.intval($id))){
+                       // si au moins une rubrique a le flag agenda
+                       if (sql_countsel('spip_rubriques','agenda=1')){
+                               // alors il faut le flag agenda dans cette branche !
+                               $afficher = false;
+                               include_spip('inc/rubriques');
+                               $in = calcul_hierarchie_in(sql_getfetsel('id_rubrique','spip_articles','id_article='.intval($id)));
+                               $afficher = sql_countsel('spip_rubriques',sql_in('id_rubrique',$in)." AND agenda=1");
+                       }
+               }
+       }
+       return $afficher;
+}
+
+// Autorisation pour créer un événement n'importe où (article ou autre ou rien)
+// Par défaut : comme pour créer dans un article si on l'a dans les options, sinon être admin complet
+function autoriser_evenement_creer_dist($faire,$quoi,$id,$qui,$options){
+       if (isset($options['id_article']) and $options['id_article'] > 0){
+               return autoriser('creerevenementdans', 'article', $options['id_article'], $qui, $options);
+       }
+       else{
+               return ($qui['statut'] == '0minirezo' and !$qui['restreint']);
+       }
+}
+
+/**
+ * Autorisation de modifier un evenement : autorisations de l'article parent
+ *
+ * @param string $faire
+ * @param string $quoi
+ * @param int $id
+ * @param int $qui
+ * @param array $options
+ * @return bool
+ */
+function autoriser_evenement_modifier_dist($faire,$quoi,$id,$qui,$options){
+       if (!isset($options['id_article']) OR !$id_article=$options['id_article'])
+               $id_article = sql_getfetsel('id_article','spip_evenements','id_evenement='.intval($id));
+       if (!$id_article) return false;
+       return autoriser('modifier','article',$id_article,$qui);
+}
+
+/**
+ * Autorisation d'instituer un evenement : voir si l'article est publie ou non
+ * @param string $faire
+ * @param string $quoi
+ * @param int $id
+ * @param int $qui
+ * @param array $options
+ * @return bool
+ */
+function autoriser_evenement_instituer_dist($faire,$quoi,$id,$qui,$options){
+       if (!isset($options['id_article']) OR !$id_article=$options['id_article'])
+               $id_article = sql_getfetsel('id_article','spip_evenements','id_evenement='.intval($id));
+       if (!$id_article) return false;
+       $statut = sql_getfetsel('statut','spip_articles','id_article='.intval($id_article));
+       // interdit de publier un evenement sur un article non publie
+       if ($statut!=='publie'
+         AND isset($options['statut'])
+               AND $options['statut']=='publie')
+               return false;
+       $options['id_article']=$id_article;
+       return autoriser('modifier','evenement',$id,$qui,$options);
+}
+
+/**
+ * Autorisation de voir un evenement : autorisations de l'article parent
+ *
+ * @param string $faire
+ * @param string $quoi
+ * @param int $id
+ * @param int $qui
+ * @param array $options
+ * @return bool
+ */
+function autoriser_evenement_voir_dist($faire,$quoi,$id,$qui,$options){
+       if (!isset($options['id_article']) OR !$id_article=$options['id_article'])
+               $id_article = sql_getfetsel('id_article','spip_evenements','id_evenement='.intval($id));
+       if (!$id_article) return false;
+       return autoriser('voir','article',$id_article,$qui);
+}
+
+
+/**
+ * Autorisation de supprimer un evenement : autorisations de l'article parent
+ *
+ * @param string $faire
+ * @param string $quoi
+ * @param int $id
+ * @param int $qui
+ * @param array $options
+ * @return bool
+ */
+function autoriser_evenement_supprimer_dist($faire,$quoi,$id,$qui,$options){
+       if (!isset($options['id_article']) OR !$id_article=$options['id_article'])
+               $id_article = sql_getfetsel('id_article','spip_evenements','id_evenement='.intval($id));
+       if (!$id_article) {
+               if ($qui['statut']=='0minirezo')
+                       return true;
+               else
+                       return false;
+       }
+       return autoriser('modifier','article',$id_article,$qui);
+}
+
+
+?>
diff --git a/www/plugins/agenda_3_5/agenda_fonctions.php b/www/plugins/agenda_3_5/agenda_fonctions.php
new file mode 100644 (file)
index 0000000..2060d58
--- /dev/null
@@ -0,0 +1,197 @@
+<?php
+/**
+ * Plugin Agenda 4 pour Spip 3.0
+ * Licence GPL 3
+ *
+ * 2006-2011
+ * Auteurs : cf paquet.xml
+ */
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+include_spip('public/agenda');
+include_spip('inc/agenda_filtres'); // deprecies mais encore supportes pour le moment
+
+/**
+ * Ajout d'un offset a une date
+ *
+ * @param string $date
+ * @param int $secondes
+ *   peut etre une expression math : 24*60*60
+ * @param string $format
+ *   format de sortie de la date
+ * @return string
+ */
+function agenda_dateplus($date,$secondes,$format="Y-m-d H:i:s"){
+       $date = strtotime($date)+eval("return $secondes;"); // permet de passer une expression
+       return date($format,$date);
+}
+
+/**
+ * decale les mois de la date.
+ * cette fonction peut raboter le jour si le nouveau mois ne les contient pas
+ * exemple 31/01/2007 + 1 mois => 28/02/2007
+ *
+ * @param string $date
+ * @param int $decalage
+ * @param string $format
+ * @return string
+ */
+function agenda_moisdecal($date,$decalage,$format="Y-m-d H:i:s"){
+       include_spip('inc/filtres');
+       $date_array = recup_date($date);
+       if ($date_array) list($annee, $mois, $jour) = $date_array;
+       if (!$jour) $jour=1;
+       if (!$mois) $mois=1;
+       $mois2 = $mois + $decalage;
+       $date2 = mktime(1, 1, 1, $mois2, $jour, $annee);
+       // mois normalement attendu
+       $mois3 = date('m', mktime(1, 1, 1, $mois2, 1, $annee));
+       // et si le mois de la nouvelle date a moins de jours...
+       $mois2 = date('m', $date2);
+       if ($mois2 - $mois3) $date2 = mktime(1, 1, 1, $mois2, 0, $annee);
+       return date($format, $date2);
+}
+
+
+/**
+ * decale les jours de la date.
+ *
+ * @param string $date
+ * @param int $decalage
+ * @param string $format
+ * @return string
+ */
+function agenda_jourdecal($date,$decalage,$format="Y-m-d H:i:s"){
+       include_spip('inc/filtres');
+       $date_array = recup_date($date);
+       if ($date_array) list($annee, $mois, $jour) = $date_array;
+       if (!$jour) $jour=1;
+       if (!$mois) $mois=1;
+       $jour2 = $jour + $decalage;
+       $date2 = mktime(1, 1, 1, $mois, $jour2, $annee);
+       return date($format, $date2);
+}
+
+/**
+ * Filtre pour tester si une date est dans le futur
+ * [(#DATE|agenda_date_a_venir) Dans le futur...]
+ *
+ * @param string $date_test
+ * @param string $date_ref
+ *   date de reference, par defaut celle du serveur (argument utile pour les tests unitaires)
+ * @return string
+ */
+function agenda_date_a_venir($date_test,$date_ref=null){
+       if (is_null($date_ref))
+               $date_ref = $_SERVER['REQUEST_TIME'];
+       else
+               $date_ref = strtotime($date_ref);
+
+       return (strtotime($date_test)>$date_ref)?' ':'';
+}
+
+
+/**
+ * Filtre pour tester si une date est dans le passe
+ * [(#DATE|agenda_date_passee) Dans le passe...]
+ *
+ * @param string $date_test
+ * @param string $date_ref
+ *   date de reference, par defaut celle du serveur (argument utile pour les tests unitaires)
+ * @return string
+ */
+function agenda_date_passee($date_test,$date_ref=null){
+       if (is_null($date_ref))
+               $date_ref = $_SERVER['REQUEST_TIME'];
+       else
+               $date_ref = strtotime($date_ref);
+
+       return (strtotime($date_test)<$date_ref)?' ':'';
+}
+
+/**
+ * Determiner la date de debut de l'affichage de la liste des evenements
+ * en fonction du mode demande et de la date courante
+ * @param string $date
+ * @param string $affichage_debut
+ * @return string
+ */
+function agenda_date_debut_liste($date,$affichage_debut='date_jour'){
+       switch($affichage_debut){
+               case 'date_jour' :
+                       break;
+               case 'date_veille' :
+                       $date = agenda_jourdecal($date, -1);
+                       break;
+               case 'debut_semaine' :
+                       $t = strtotime($date);
+                       $date = agenda_jourdecal($date, -(date('N', $t)-1));
+                       break;
+               case 'debut_semaine_prec' :
+                       $t = strtotime($date);
+                       $date = agenda_jourdecal($date, -(date('N', $t)-1+7));
+                       break;
+               case 'debut_mois' :
+                       $t = strtotime($date);
+                       $date = agenda_jourdecal($date, -(date('j', $t)-1));
+                       break;
+               case 'debut_mois_prec' :
+                       $t = strtotime($date);
+                       $date = agenda_jourdecal($date, -(date('j', $t)-1)); // debut de mois
+                       $date = agenda_moisdecal($date, -1); // precedent
+                       break;
+               case 'debut_mois_1' :
+               case 'debut_mois_2' :
+               case 'debut_mois_3' :
+               case 'debut_mois_4' :
+               case 'debut_mois_5' :
+               case 'debut_mois_6' :
+               case 'debut_mois_7' :
+               case 'debut_mois_8' :
+               case 'debut_mois_9' :
+               case 'debut_mois_10' :
+               case 'debut_mois_11' :
+               case 'debut_mois_12' :
+                       $t = strtotime($date);
+                       $mdebut = intval(substr($affichage_debut,strlen('debut_mois_')));
+                       $mcourant = date('n', $t);
+                       $offset = ($mcourant-$mdebut+12)%12;
+                       $date = agenda_jourdecal($date, -(date('j', $t)-1)); // debut de mois
+                       $date = agenda_moisdecal($date, -$offset);
+                       break;
+       }
+       return $date;
+}
+/**
+ * Afficher la periode de l'agenda :
+ * Le nom du mois si nb_mois = 1
+ * L'annee si nb_mois=12 et debut du mois = janvier
+ * sinon : mois annee - mois annee (xxx 12 - yyy 13)
+ * si le debut de la periode est fixe (debut d'un mois donnee), on precede de
+ * "Annee" ou "Saison" la periode
+ *
+ * @param string $date
+ * @param int $nb_mois
+ * @param string $affichage_debut
+ * @return string
+ */
+function affdate_periode($date,$nb_mois,$affichage_debut='date_jour'){
+       $fixe = in_array($affichage_debut,array('debut_mois_1','debut_mois_2','debut_mois_3','debut_mois_4','debut_mois_5','debut_mois_6','debut_mois_7','debut_mois_8','debut_mois_9','debut_mois_10','debut_mois_11','debut_mois_12'));
+       if ($nb_mois==1)
+               return affdate_mois_annee($date);
+       if ($nb_mois==12 AND mois($date)==1)
+               return ($fixe?_T('agenda:label_annee').' ':'').annee($date);
+
+       return ($fixe?_T('agenda:label_periode_saison').' ':'').affdate_mois_annee($date)." - ".affdate_mois_annee(agenda_moisdecal($date, $nb_mois-1));
+}
+
+/**
+ * Raccourcis [->evenement12] et [->evt12]
+ */
+/*
+function generer_url_evenement($id, $args='', $ancre='') {
+       return array('evenement', $id);
+}*/
+
+?>
diff --git a/www/plugins/agenda_3_5/agenda_options.php b/www/plugins/agenda_3_5/agenda_options.php
new file mode 100644 (file)
index 0000000..601a787
--- /dev/null
@@ -0,0 +1,6 @@
+<?php
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+// brancher le plugin sur nospam
+$GLOBALS['formulaires_no_spam'][] = 'participer_evenement';
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/agenda_pipelines.php b/www/plugins/agenda_3_5/agenda_pipelines.php
new file mode 100644 (file)
index 0000000..72dbb18
--- /dev/null
@@ -0,0 +1,276 @@
+<?php
+/**
+ * Plugin Agenda 4 pour Spip 3.0
+ * Licence GPL 3
+ *
+ * 2006-2011
+ * Auteurs : cf paquet.xml
+ */
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+/**
+ * Inserer la CSS de l'agenda si config cochee
+ * forcee par define('_AGENDA_INSERT_HEAD_CSS',false|true) par le squelette si besoin
+ *
+ * @param $flux
+ * @return mixed
+ */
+function agenda_insert_head_css($flux){
+       if (!defined('_AGENDA_INSERT_HEAD_CSS') OR !_AGENDA_INSERT_HEAD_CSS){
+               include_spip("inc/config");
+               $cfg = (defined('_AGENDA_INSERT_HEAD_CSS')?_AGENDA_INSERT_HEAD_CSS:lire_config("agenda/insert_head_css"));
+               if ($cfg){
+                       $flux .= '<link rel="stylesheet" type="text/css" href="'.find_in_path("css/spip.agenda.css").'" />';
+               }
+       }
+       return $flux;
+}
+
+/**
+ * Inserer les infos d'agenda sur les articles et rubriques
+ *
+ * @param array $flux
+ * @return array
+ */
+function agenda_affiche_milieu($flux) {
+       $e = trouver_objet_exec($flux['args']['exec']);
+       $out = "";
+       if ($e['type']=='rubrique'
+         AND autoriser('configurer')
+         AND $e['edition']==false
+         AND $id_rubrique = intval($flux['args']['id_rubrique'])
+         AND autoriser('modifier', 'rubrique', $id_rubrique)){
+               $activer = true;
+               $res = "";
+               $actif = sql_getfetsel('agenda','spip_rubriques','id_rubrique='.intval($id_rubrique));
+               $statut="-32";
+               $alt = "";
+               $voir = "";
+               if (!sql_countsel('spip_rubriques','agenda=1'))
+                       $res .= "<span class='small'>" . _T('agenda:aucune_rubrique_mode_agenda') . "</span><br />";
+               else {
+                       include_spip('inc/rubriques');
+                       if (sql_countsel('spip_rubriques',sql_in('id_rubrique',calcul_hierarchie_in($id_rubrique))." AND agenda=1 AND id_rubrique<>".intval($id_rubrique))){
+                               $alt = _T('agenda:rubrique_dans_une_rubrique_mode_agenda');
+                               $activer = false;
+                               $statut="-ok-32";
+                               $voir = _T('agenda:voir_evenements_rubrique');
+                       }
+                       elseif(!$actif) {
+                               $alt = _T('agenda:rubrique_sans_gestion_evenement').'<br />';
+                               $statut="-non-32";
+                       }
+                       if ($actif){
+                               $alt = _T('agenda:rubrique_mode_agenda').'<br />';
+                               $statut="-ok-32";
+                               $voir = _T('agenda:voir_evenements_rubrique');
+                       }
+               }
+
+               if (!$actif){
+                       if($activer){
+                               $res .= bouton_action(_T('agenda:rubrique_activer_agenda'),generer_action_auteur('activer_agenda_rubrique',$id_rubrique,self()),'ajax');
+                       }
+               }
+               else
+                       $res .= bouton_action(_T('agenda:rubrique_desactiver_agenda'),generer_action_auteur('activer_agenda_rubrique',"-$id_rubrique",self()),'ajax');
+               if ($voir)
+                       $res .= " | <a href='".generer_url_ecrire('evenements',"id_rubrique=$id_rubrique")."'>$voir</a>";
+               if ($res)
+                       $out .= boite_ouvrir(_T('agenda:agenda').http_img_pack("agenda$statut.png",$alt,"class='statut'",$alt),'simple agenda-statut')
+                         . $res
+                         . boite_fermer();
+       }
+       elseif ($e['type']=='article'
+         AND $e['edition']==false){
+               $id_article = $flux['args']['id_article'];
+               $out .= recuperer_fond('prive/objets/contenu/article-evenements',$flux['args']);
+       }
+
+       if ($out){
+               if ($p=strpos($flux['data'],'<!--affiche_milieu-->'))
+                       $flux['data'] = substr_replace($flux['data'],$out,$p,0);
+               else
+                       $flux['data'] .= $out;
+       }
+       return $flux;
+}
+
+/**
+ * Optimiser la base (evenements a la poubelle, lies a des articles disparus, ou liens mots sur evenements disparus)
+ *
+ * @param array $flux
+ * @return array
+ */
+function agenda_optimiser_base_disparus($flux){
+
+       # passer a la poubelle
+       # les evenements lies a un article inexistant
+       $res = sql_select("DISTINCT evenements.id_article","spip_evenements AS evenements
+                       LEFT JOIN spip_articles AS articles
+                       ON evenements.id_article=articles.id_article","articles.id_article IS NULL");
+       while ($row = sql_fetch($res))
+               sql_updateq("spip_evenements",array('statut'=>'poubelle'),"id_article=".$row['id_article']);
+
+       // Evenements a la pouvelle
+       sql_delete("spip_evenements", "statut='poubelle' AND maj < ".$flux['args']['date']);
+
+       include_spip('action/editer_liens');
+       // optimiser les liens de tous les mots vers des objets effaces
+       // et depuis des mots effaces
+       $flux['data'] += objet_optimiser_liens(array('mot'=>'*'),array('evenement'=>'*'));
+
+       return $flux;
+}
+
+
+/**
+ * Lister les evenements dans le calendrier de l'espace prive (extension organiseur)
+ *
+ * @param array $flux
+ * @return array
+ */
+function agenda_quete_calendrier_prive($flux){
+       $quoi = $flux['args']['quoi'];
+       if (!$quoi OR $quoi=='evenements'){
+               $start = sql_quote($flux['args']['start']);
+               $end = sql_quote($flux['args']['end']);
+               $res = sql_select('*','spip_evenements AS E',"((E.date_fin >= $start OR E.date_debut >= $start) AND E.date_debut <= $end)");
+               while ($row = sql_fetch($res)){
+                       $flux['data'][] = array(
+                               'id' => $row['id_evenement'],
+                               'title' => $row['titre'],
+                               'allDay' => false,
+                               'start' => $row['date_debut'],
+                               'end' => $row['date_fin'],
+                               'url' => str_replace("&amp;","&",generer_url_entite($row['id_evenement'],'evenement')),
+                               'className' => "calendrier-event evenement calendrier-couleur5",
+                               'description' => $row['descriptif'],
+                       );
+               }
+       }
+       return $flux;
+}
+
+/**
+ * Synchroniser le statut des evenements lorsqu'on publie/depublie un article
+ * @param array $flux
+ * @return array
+ */
+function agenda_post_edition($flux){
+       if ($flux['args']['table']=='spip_articles'
+         AND $flux['args']['action'] == 'instituer'
+         AND $id_article = $flux['args']['id_objet']
+         AND isset($flux['data']['statut'])
+         AND $statut = $flux['data']['statut']
+         AND $statut_ancien = $flux['args']['statut_ancien']
+         AND $statut!=$statut_ancien){
+
+               $set = array();
+               // les evenements principaux, associes a cet article
+               $where = array('id_article='.intval($id_article),'id_evenement_source=0');
+               switch($statut){
+                       case 'poubelle':
+                               // on passe aussi tous les evenements associes a la poubelle, sans distinction
+                               $set['statut'] = 'poubelle';
+                               break;
+                       case 'publie':
+                               // on passe aussi tous les evenements prop en publie
+                               $set['statut'] = 'publie';
+                               $where[] = "statut='prop'";
+                               break;
+                       default:
+                               if ($statut_ancien=='publie'){
+                                       // on depublie aussi tous les evenements publie
+                                       $set['statut'] = 'prop';
+                                       $where[] = "statut='publie'";
+                               }
+                               break;
+               }
+               if (count($set)){
+                       include_spip('action/editer_evenement');
+                       $res = sql_select('id_evenement','spip_evenements',$where);
+                       // et on applique a tous les evenements lies a l'article
+                       while ($row = sql_fetch($res)){
+                               evenement_modifier($row['id_evenement'],$set);
+                       }
+               }
+       }
+       return $flux;
+}
+
+/*
+ * Synchroniser les liaisons (mots, docs, gis, etc) de l'événement édité avec ses répétitions s'il en a
+ * @param array $flux
+ * @param array
+ */
+function agenda_post_edition_lien($flux){
+       // Si on est en train de lier ou délier quelque chose a un événement
+       if ($flux['args']['objet'] == 'evenement'){
+               // On cherche si cet événement a des répétitions
+               if ($id_evenement = $flux['args']['id_objet']
+                       and $id_evenement > 0
+                       and $repetitions = sql_allfetsel('id_evenement', 'spip_evenements', 'id_evenement_source = '.$id_evenement)
+                       and is_array($repetitions)
+               ){
+                       include_spip('action/editer_liens');
+                       
+                       // On a la liste des ids des répétitions
+                       $repetitions = array_map('reset', $repetitions);
+                       
+                       // Si c'est un ajout de lien, on l'ajoute à toutes les répétitions
+                       if ($flux['args']['action'] == 'insert'){
+                               objet_associer(
+                                       array($flux['args']['objet_source'] => $flux['args']['id_objet_source']),
+                                       array('evenement' => $repetitions)
+                               );
+                       }
+                       // Si c'est une suppression de lien, on le supprime à toutes les répétitions
+                       elseif ($flux['args']['action'] == 'delete'){
+                               objet_dissocier(
+                                       array($flux['args']['objet_source'] => $flux['args']['id_objet_source']),
+                                       array('evenement' => $repetitions)
+                               );
+                       }
+               }
+       }
+       
+       return $flux;
+}
+
+/**
+ * Les evenements peuvent heriter des compositions des articles
+ * @param array $heritages
+ * @return array
+ */
+function agenda_compositions_declarer_heritage($heritages) {
+       $heritages['evenement'] = 'article';
+       return $heritages;
+}
+
+/**
+ * Insertion dans le pipeline revisions_chercher_label (Plugin révisions)
+ * Trouver le bon label à afficher sur les champs dans les listes de révisions
+ * 
+ * Si un champ est un champ extra, son label correspond au label défini du champs extra
+ * 
+ * @pipeline revisions_chercher_label
+ * @param array $flux Données du pipeline
+ * @return array      Données du pipeline
+**/ 
+function agenda_revisions_chercher_label($flux){
+       foreach(array('date_debut', 'date_fin','horaire','lieu') as $champ){
+               if($flux['args']['champ'] == $champ){
+                       $flux['data'] = _T('agenda:evenement_'.$champ);
+                       return $flux;
+               }
+       }
+       
+       if($flux['args']['champ'] == 'id_article')
+               $flux['data'] = _T('agenda:evenement_article');
+       
+       return $flux;
+}
+
+?>
diff --git a/www/plugins/agenda_3_5/base/agenda_evenements.php b/www/plugins/agenda_3_5/base/agenda_evenements.php
new file mode 100644 (file)
index 0000000..0573b30
--- /dev/null
@@ -0,0 +1,154 @@
+<?php\r
+/**\r
+ * Plugin Agenda 4 pour Spip 3.0\r
+ * Licence GPL 3\r
+ *\r
+ * 2006-2011\r
+ * Auteurs : cf paquet.xml\r
+ */\r
+\r
+if (!defined("_ECRIRE_INC_VERSION")) return;\r
+\r
+\r
+/**\r
+ * Interfaces du compilateur\r
+ *\r
+ * @param array $interface\r
+ * @return array\r
+ */\r
+function agenda_declarer_tables_interfaces($interface){\r
+       // 'spip_' dans l'index de $tables_principales\r
+       $interface['table_des_tables']['evenements']='evenements';\r
+       \r
+       $interface['table_des_traitements']['LIEU'][]= 'expanser_liens('._TRAITEMENT_TYPO.')';\r
+       $interface['table_des_traitements']['ADRESSE'][]= _TRAITEMENT_RACCOURCIS;\r
+       \r
+       // permet d'utiliser les criteres racine, meme_parent, id_parent\r
+       $interface['exceptions_des_tables']['evenements']['id_parent']='id_evenement_source';\r
+       $interface['exceptions_des_tables']['evenements']['id_rubrique']=array('spip_articles', 'id_rubrique');\r
+               \r
+       return $interface;\r
+}\r
+\r
+/**\r
+ * Tables auxiliaires de liens\r
+ * @param array $tables_auxiliaires\r
+ * @return array\r
+ */\r
+function agenda_declarer_tables_auxiliaires($tables_auxiliaires){\r
+\r
+       //-- Table des participants ----------------------\r
+       $spip_evenements_participants = array(\r
+                       "id_evenement_participant" => "BIGINT(21) NOT NULL AUTO_INCREMENT",\r
+                       "id_evenement"  => "BIGINT (21) DEFAULT '0' NOT NULL",\r
+                       "id_auteur"     => "BIGINT (21) DEFAULT '0' NOT NULL",\r
+                       "nom"   => "text NOT NULL DEFAULT ''",\r
+                       "email" => "tinytext NOT NULL DEFAULT ''",\r
+                       "date" => "datetime DEFAULT '0000-00-00 00:00:00' NOT NULL",\r
+                       "reponse" => "char(3) default '?' NOT NULL", // oui, non, ?\r
+                       );\r
+       \r
+       $spip_evenements_participants_key = array(\r
+                       "PRIMARY KEY"   => "id_evenement_participant",\r
+                       "KEY id_evenement"      => "id_evenement",\r
+                       "KEY id_auteur" => "id_auteur");\r
+       \r
+       $tables_auxiliaires['spip_evenements_participants'] = array(\r
+               'field' => &$spip_evenements_participants,\r
+               'key' => &$spip_evenements_participants_key);\r
+\r
+       return $tables_auxiliaires;\r
+}\r
+\r
+/**\r
+ * Declarer la table objet evenement\r
+ *\r
+ * @param array $tables\r
+ * @return array\r
+ */\r
+function agenda_declarer_tables_objets_sql($tables){\r
+       $tables['spip_evenements'] = array(\r
+               'page'=>'evenement',\r
+               'texte_retour' => 'icone_retour',\r
+               'texte_objets' => 'agenda:info_evenements',\r
+               'texte_objet' => 'agenda:info_evenement',\r
+               'texte_modifier' => 'agenda:icone_modifier_evenement',\r
+               'texte_creer' => 'agenda:titre_cadre_ajouter_evenement',\r
+               'texte_logo_objet' => 'agenda:texte_logo_objet',\r
+               'info_aucun_objet'=> 'agenda:info_aucun_evenement',\r
+               'info_1_objet' => 'agenda:info_un_evenement',\r
+               'info_nb_objets' => 'agenda:info_nombre_evenements',\r
+               'titre' => 'titre, "" AS lang',\r
+               'date' => 'date_debut',\r
+               'principale' => 'oui',\r
+               'champs_editables' => array('date_debut', 'date_fin', 'titre', 'descriptif','lieu', 'adresse', 'inscription', 'places', 'horaire'),\r
+               'field'=> array(\r
+                       "id_evenement"  => "bigint(21) NOT NULL",\r
+                       "id_article"    => "bigint(21) DEFAULT '0' NOT NULL",\r
+                       "date_debut"    => "datetime DEFAULT '0000-00-00 00:00:00' NOT NULL",\r
+                       "date_fin"      => "datetime DEFAULT '0000-00-00 00:00:00' NOT NULL",\r
+                       "titre" => "text NOT NULL DEFAULT ''",\r
+                       "descriptif"    => "text NOT NULL DEFAULT ''",\r
+                       "lieu"  => "text NOT NULL DEFAULT ''",\r
+                       "adresse"       => "text NOT NULL DEFAULT ''",\r
+                       "inscription" => "tinyint(1) DEFAULT 0 NOT NULL",\r
+                       "places" => "int(11) DEFAULT 0 NOT NULL",\r
+                       "horaire" => "varchar(3) DEFAULT 'oui' NOT NULL",\r
+                       "id_evenement_source"   => "bigint(21) NOT NULL",\r
+                       "statut"        => "varchar(10) DEFAULT '0' NOT NULL",\r
+                       "maj"   => "TIMESTAMP",\r
+                       "date_creation" => "datetime DEFAULT '0000-00-00 00:00:00' NOT NULL"\r
+               ),\r
+               'key' => array(\r
+                       "PRIMARY KEY"   => "id_evenement",\r
+                       "KEY date_debut"        => "date_debut",\r
+                       "KEY date_fin"  => "date_fin",\r
+                       "KEY id_article"        => "id_article"\r
+               ),\r
+               'join' => array(\r
+                       "id_evenement"=>"id_evenement",\r
+                       "id_article"=>"id_article"\r
+               ),\r
+               'tables_jointures' => array(\r
+                       'articles',\r
+                       'evenements_participants',\r
+               ),\r
+               'rechercher_champs' => array(\r
+                 'titre' => 8, 'descriptif' => 5, 'lieu' => 5, 'adresse' => 3\r
+               ),\r
+               'rechercher_jointures' => array(\r
+                       'document' => array('titre' => 2, 'descriptif' => 1)\r
+               ),\r
+               'statut' => array(\r
+                       array(\r
+                               'champ' => 'statut',\r
+                               'publie' => 'publie',\r
+                               'previsu' => '!',\r
+                               'exception' => array('statut','tout')\r
+                       ),\r
+               ),\r
+               'statut_titres' => array(\r
+                       'prop'=>'agenda:info_evenement_propose',\r
+                       'publie'=>'agenda:info_evenement_publie',\r
+                       'poubelle'=>'agenda:info_evenement_supprime'\r
+               ),\r
+               'statut_textes_instituer' =>    array(\r
+                       'prop' => 'texte_statut_propose_evaluation',\r
+                       'publie' => 'texte_statut_publie',\r
+                       'poubelle' => 'texte_statut_poubelle',\r
+               ),\r
+               'texte_changer_statut' => 'agenda:texte_evenement_statut',\r
+               'champs_versionnes' => array('id_article', 'titre', 'descriptif', 'lieu', 'adresse', 'date_debut', 'date_fin', 'horaire'),\r
+\r
+       );\r
+\r
+       //-- Jointures ----------------------------------------------------\r
+       $tables['spip_articles']['tables_jointures'][] = 'evenements';\r
+       $tables['spip_auteurs']['tables_jointures'][] = 'evenements_participants';\r
+       $tables['spip_rubriques']['field']['agenda'] = 'tinyint(1) DEFAULT 0 NOT NULL';\r
+\r
+       return $tables;\r
+}\r
+\r
+\r
+?>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/css/spip.agenda.css b/www/plugins/agenda_3_5/css/spip.agenda.css
new file mode 100644 (file)
index 0000000..795e0c5
--- /dev/null
@@ -0,0 +1,36 @@
+
+.evenement.one {padding: 0.75em;background: #eee;margin-bottom: 1.5em;}
+.evenement.one .lire-la-suite {display: none;}
+
+.long .liste-items .evenement {padding-left: 7.5em;}
+.long .liste-items .evenement .banner {display:block;float: left;margin-left: -7.5em;width: 5.5em;text-align: center;overflow: hidden;opacity: 0.7;}
+.long .liste-items .evenement .banner .label {display: block;padding:0.75em 0;}
+.long .liste-items .evenement .banner .day {display: block;text-align: center;font-size: 2em;line-height: 1;}
+.long .liste-items .evenement .banner .month {display: block;text-align: center;text-transform: uppercase;font-size: 0.85em;}
+.long .liste-items .evenement .banner .year {display: block;text-align: center;font-size: 0.85em;}
+
+/*.long .liste-items.evenements .item.month, .long .liste-items.evenements .item.month {padding: 0.75em;background: #e4e4e4; text-transform: uppercase;}*/
+.liste-items.evenements .item.fini  .entry-title a {color:#ccc;}
+.liste-items.evenements .item.fini  .entry-content {margin-bottom: 0;max-height: 4.5em;overflow: hidden;}
+.liste-items.evenements .item.fini  .meta-publi {display: none;}
+.liste-items.evenements .item.fini  .lire-la-suite {display: none;}
+.liste-items.evenements .item.fini  .evenement .banner {opacity: 0.5;}
+
+.liste-items.evenements.short .info-publi, .short .liste-items.evenements .info-publi {display:block;margin: 0;}
+.liste-items.evenements .meta-publi {left:7.5em;}
+.liste-items.evenements .lire-la-suite {left:7.5em;}
+.liste-items.evenements .entry-title {margin-top: 0;}
+
+.page_jour .liste-items.evenements .evenement .banner {display: none;}
+.page_jour .long .liste-items .evenement{padding-left: 0;}
+.page_jour .liste-items.evenements .meta-publi {left:0;}
+.page_jour .liste-items.evenements .lire-la-suite {left:0;}
+
+/*
+.liste-items.evenements.short .evenement {padding-left: 0;background-image: none;}
+.liste-items.evenements.short .entry-title {margin: 0;font-size: inherit;}
+.liste-items.evenements.short .banner {display: none;}
+.liste-items.evenements.short .entry-content {display: none;}
+.liste-items.evenements.short .lire-la-suite {display: none;}
+.liste-items.evenements.short .meta-publi {display: none;}
+*/
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/demo/agenda_calendrier_mini.html b/www/plugins/agenda_3_5/demo/agenda_calendrier_mini.html
new file mode 100644 (file)
index 0000000..34e8c5b
--- /dev/null
@@ -0,0 +1,173 @@
+#CACHE{86400}
+<BOUCLE_article_principal(ARTICLES) {id_article}><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html dir="#LANG_DIR" lang="#LANG">
+<head>
+       <title>[(#TITRE|textebrut)][ - (#NOM_SITE_SPIP|textebrut)]</title>
+       <meta http-equiv="Content-Type" content="text/html; charset=#CHARSET" />
+       [<meta name="description" content="(#INTRODUCTION|couper{150}|attribut_html)" />]
+       <meta name="generator" content="SPIP" />
+       [<link rel="shortcut icon" href="(#CHEMIN{favicon.ico})" />]
+
+       <!-- Lien vers le backend pour navigateurs eclaires -->
+       <link rel="alternate" type="application/rss+xml" title="<:syndiquer_site:>" href="#URL_PAGE{backend}" />
+
+       <!-- Ceci est la feuille de style par defaut pour le code genere par SPIP -->
+       <link rel="stylesheet" href="[(#CHEMIN{spip_style.css}|direction_css)]" type="text/css" media="all" />
+       <!-- Feuille de styles CSS pour l'affichage du site sur ecran -->
+       <link rel="stylesheet" href="[(#CHEMIN{habillage.css}|direction_css)]" type="text/css" media="projection, screen, tv" />
+       <!-- Feuille de styles CSS pour l'impression -->
+       <link rel="stylesheet" href="[(#CHEMIN{impression.css}|direction_css)]" type="text/css" media="print" />
+</head>
+
+<body class="page_article">
+<div id="page">
+
+       [(#REM) Entete de la page + titre du site ]
+       <INCLURE{fond=inc-entete}{lang}>
+
+       [(#REM) Fil d'Ariane ]
+       <div id="hierarchie"><a href="#URL_SITE_SPIP/"><:accueil_site:></a><BOUCLE_ariane(HIERARCHIE){id_article}> &gt; <a href="#URL_RUBRIQUE">[(#TITRE|couper{80})]</a></BOUCLE_ariane>[ &gt; (#TITRE|couper{80})]</div>
+
+
+       <div id="conteneur">
+
+
+               [(#REM) Contenu principal : contenu de l'article ]
+
+               <div id="contenu">
+<style type='text/css'>
+               td.occupe a {font-weight:bold;text-decoration:underline;}
+               td.horsperiode {color:#878787;font-size:.9em;}
+</style>
+[(#REM) mini calendrier : 
+1er arg la date, 
+2eme le nom de la variable date (optionel : 'date' par defaut)
+3eme l'url sur laquelle boucler (optionel : url courante par defaut) ]
+#CALENDRIER_MINI{#ENV{date},'date',#SELF}
+
+               <div class="cartouche">
+                               #DEBUT_SURLIGNE
+                               [(#LOGO_ARTICLE||image_reduire{200,200})]
+                               [<p class="surtitre">(#SURTITRE)</p>]
+                               <h1 class="titre">#TITRE</h1>
+                               [<p class="soustitre">(#SOUSTITRE)</p>]
+                               #FIN_SURLIGNE
+
+                               <p><small>[(#DATE|nom_jour) ][(#DATE|affdate)][, <:par_auteur:> (#LESAUTEURS)]</small></p>
+
+                               [(#REM) Traductions de l'article ]
+                               <B_traductions>
+                               <div class="traductions">
+                                       <p><:trad_article_traduction:></p>
+                                       <ul>
+                                               <li[ class="(#EXPOSE)"]>[(#LANG|traduire_nom_langue)]</li>
+                                               <BOUCLE_traductions(ARTICLES){traduction}{exclus}>
+                                               <li><a[ href="(#URL_ARTICLE|url_absolue)"] rel="alternate" hreflang="#LANG"[ title="(#TITRE|couper{80}|texte_backend)"]><span lang="#LANG" xml:lang="#LANG" dir="#LANG_DIR">[(#LANG|traduire_nom_langue)]</span></a></li>
+                                               </BOUCLE_traductions>
+                                       </ul>
+                               </div>
+                               </B_traductions>
+                       </div>
+
+
+                       #DEBUT_SURLIGNE
+                       [<div class="chapo">(#CHAPO)</div>]
+                       <br class="nettoyeur" />
+                       [<p class="lien"><:voir_en_ligne:> : <a href="(#URL_SITE)" class="spip_out">[(#NOM_SITE|sinon{[(#URL_SITE|couper{80})]})]</a></p>]
+                       [<div class="texte">(#TEXTE|image_reduire{520,0})</div>]
+                       #FIN_SURLIGNE
+
+                       [(#REM) Portfolio : album d'images ]
+                       <B_documents_portfolio>
+                       <div id="documents_portfolio">
+                               <h2><:info_portfolio:></h2>
+                               <BOUCLE_documents_portfolio(DOCUMENTS) {id_article}
+                               {mode=document} {extension IN png,jpg,gif} {par date}
+                               {doublons}>
+                               [<a href="#URL_DOCUMENT" type="#MIME_TYPE"
+                               onclick="this.href='[(#URL_ARTICLE|parametre_url{id_document,#ID_DOCUMENT})]#documents_portfolio';return true;"[title="(#TITRE|couper{80}|texte_backend)"]>(#LOGO_DOCUMENT||image_reduire{0,60}|inserer_attribut{alt,[(#TITRE|couper{80}|texte_backend)]})</a>]
+                               </BOUCLE_documents_portfolio>
+                       </div>
+                       </B_documents_portfolio>
+                       <BOUCLE_afficher_document(DOCUMENTS) {id_document}
+                       {mode=document}{extension IN png,jpg,gif}>
+                       <div class="spip_documents spip_documents_center" id="document_actif">
+                               #EMBED_DOCUMENT
+                               [<div class="spip_doc_titre">(#TITRE)</div>]
+                               [<div class="spip_doc_descriptif">(#DESCRIPTIF)</div>]
+                       </div>
+                       </BOUCLE_afficher_document>
+
+                       [<div class="ps"><h2><:info_ps:></h2>#DEBUT_SURLIGNE(#PS)#FIN_SURLIGNE</div>]
+
+                       [(#REM) Autres documents joints a l'article ]
+                       <B_documents_joints>
+                       <div id="documents_joints">
+                               <h2><:titre_documents_joints:></h2>
+                               <ul>
+                                       <BOUCLE_documents_joints(DOCUMENTS) {id_article} {mode=document} {par date} {doublons}>
+                                       <li>
+                                       <div class="spip_doc_titre"><a href="#URL_DOCUMENT" title="<:bouton_telecharger:>" type="#MIME_TYPE">[(#TITRE|sinon{<:info_document:>})]</a> <small>(#TYPE_DOCUMENT[ - (#TAILLE|taille_en_octets)])</small></div>
+                                       [<div class="spip_doc_descriptif">(#DESCRIPTIF)</div>]</li>
+                                       <br class="nettoyeur" />
+                                       </BOUCLE_documents_joints>
+                               </ul>
+                       </div>
+                       </B_documents_joints>
+
+                       [(#REM) Petition :
+                       La petition ayant une PAGINATION il faut absolument lui passer SELF]
+                       [ #REM Conserver cet invalideur invisible : (#PETITION|?{'',''}) ]
+                       <INCLURE{fond=inc-petition}{id_article}{self=#SELF}>
+
+                       [<div class="notes"><h2><:info_notes:></h2>#DEBUT_SURLIGNE(#NOTES)#FIN_SURLIGNE</div>]
+
+                       [(#REM) Forum de l'article ]
+                       [<h2 class="forum-repondre"><a href="(#PARAMETRES_FORUM|url_reponse_forum)"><:repondre_article:></a></h2>]
+                       <INCLURE{fond=inc-forum}{id_article}>
+
+
+               </div><!-- fin contenu -->
+
+
+               [(#REM) Menu de navigation laterale ]
+               <div id="navigation">
+                       <h1 class="invisible"><:navigation:></h1>
+
+                       [(#REM) Menu de navigation par rubriques ]
+                       <INCLURE{fond=inc-rubriques}{lang}{id_rubrique}>
+
+                       [(#REM) Articles dans la meme rubrique ]
+                       <B_articles_rubrique>
+                       <div class="breves">
+                               <h2 class="menu-titre"><a href="#URL_RUBRIQUE"><:meme_rubrique:></a></h2>
+                               <ul>
+                                       <BOUCLE_articles_rubrique(ARTICLES) {id_rubrique} {par date}{inverse} {0,10}>
+                                       <li><a href="#URL_ARTICLE"[ class="(#EXPOSE)"]>#TITRE</a></li>
+                                       </BOUCLE_articles_rubrique>
+                               </ul>
+                       </div>
+                       </B_articles_rubrique>
+
+                       [(#REM) Menu de navigation mots-cles ]
+                       <B_mots>
+                       <div class="divers">
+                               <h2 class="menu-titre"><:mots_clefs:></h2>
+                       <ul>
+                               <BOUCLE_mots(MOTS) {id_article} {par titre}>
+                               <li><a href="#URL_MOT" rel="tag">#TITRE</a></li>
+                               </BOUCLE_mots>
+                       </ul>
+                       </div>
+                       </B_mots>
+               </div><!-- fin navigation -->
+
+       </div><!-- fin conteneur -->
+
+       [(#REM) Pied de page ]
+       <INCLURE{fond=inc-pied}{lang}>
+
+</div><!-- fin page -->
+</body>
+</html>
+</BOUCLE_article_principal>
diff --git a/www/plugins/agenda_3_5/demo/exemple_navigation_jours.html b/www/plugins/agenda_3_5/demo/exemple_navigation_jours.html
new file mode 100644 (file)
index 0000000..b3b318b
--- /dev/null
@@ -0,0 +1,8 @@
+\r
+[<a href="(#URL_PAGE{jour}|parametre_url{date,[(#VAL{Y-m-d}|date{#VAL{-1 day}|strtotime{#ENV{date}|affdate{U}}})]})" title="jour précédent">\r
+[(#VAL{Y-m-d}|date{#VAL{-1 day}|strtotime{#ENV{date}|affdate{U}}}|nom_jour)]&nbsp;&#8249;</a>]\r
+       \r
+[(#EVAL{' '}|concat{[(#ENV{date}|nom_jour) ],#ENV{date}|jour})]\r
+\r
+[<a href="(#URL_PAGE{jour}|parametre_url{date,[(#VAL{Y-m-d}|date{#VAL{+1 day}|strtotime{#ENV{date}|affdate{U}}})]})"\r
+title="jour suivant">&#8250;&nbsp; [(#VAL{Y-m-d}|date{#VAL{+1 day}|strtotime{#ENV{date}|affdate{U}}}|nom_jour)]</a>]
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/demo/test/test_boucle_evenements.html b/www/plugins/agenda_3_5/demo/test/test_boucle_evenements.html
new file mode 100644 (file)
index 0000000..91769a1
--- /dev/null
@@ -0,0 +1,9 @@
+<BOUCLE_eve(EVENEMENTS)>
+#ID_EVENEMENT:
+</BOUCLE_eve>
+
+<BOUCLE_rub1(RUBRIQUES){id_rubrique=1}>
+<BOUCLE_eve1(EVENEMENTS){branche}>
+plop
+</BOUCLE_eve1>
+</BOUCLE_rub1>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/demo/test/testagenda.html b/www/plugins/agenda_3_5/demo/test/testagenda.html
new file mode 100644 (file)
index 0000000..8e994db
--- /dev/null
@@ -0,0 +1,36 @@
+<h1>{id_mot}</h1>
+<ul>
+<BOUCLE_ListeMots(MOTS)>
+<li>#TITRE
+       <B_Evenements>
+       <ul>
+       <BOUCLE_Evenements(EVENEMENTS){id_mot}>
+               <li>#TITRE</li>
+       </BOUCLE_Evenements>
+       </ul>
+       </B_Evenements>
+</li>
+</BOUCLE_ListeMots>
+</ul>
+
+<h1>{titre_mot}</h1>
+<ul>
+<BOUCLE_ListeEvenements(EVENEMENTS){titre_mot=mc1}>
+       <li>#TITRE</li>
+</BOUCLE_ListeEvenements>
+</ul>
+
+<h1>{branche}</h1>
+<ul>
+<BOUCLE_ListeSecteurs(RUBRIQUES){racine}>
+<li>#TITRE
+       <B_EvenementsSecteur>
+       <ul>
+       <BOUCLE_EvenementsSecteur(EVENEMENTS){branche}>
+               <li>#TITRE</li>
+       </BOUCLE_EvenementsSecteur>
+       </ul>
+       </B_EvenementsSecteur>
+</li>
+</BOUCLE_ListeSecteurs>
+</ul>
diff --git a/www/plugins/agenda_3_5/formulaires/configurer_agenda.html b/www/plugins/agenda_3_5/formulaires/configurer_agenda.html
new file mode 100644 (file)
index 0000000..7d61bbd
--- /dev/null
@@ -0,0 +1,105 @@
+<div class="ajax formulaire_spip formulaire_configurer formulaire_#FORM formulaire_#FORM-#ENV{id,nouveau}">
+       <h3 class="titrem"><:configureragenda:titre_configuration:></h3>
+       [<p class="reponse_formulaire reponse_formulaire_ok">(#ENV**{message_ok})</p>]
+       [<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV**{message_erreur})</p>]
+
+       <form method='post' action='#ENV{action}'><div>
+               [(#REM) déclarer les hidden qui déclencheront le service du formulaire
+               paramêtre : url d'action ]
+               #ACTION_FORMULAIRE{#ENV{action}}
+               #SET{fl,configureragenda}
+                               <ul>
+                               <!--EX01-->
+                               #SET{name,titre}#SET{obli,''}[(#SET{defaut,<:agenda:titre_sur_l_agenda:>})]#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}}
+                               <li class="editer editer_[(#GET{name})][ (#GET{obli})][ (#GET{erreurs}|oui)erreur]">
+                                       <label for="#GET{name}">[(#GET{fl}|concat{':label_',#GET{name}}|_T)]</label>[
+                                       <span class='erreur_message'>(#GET{erreurs})</span>
+                                       ]<input type="text" name="#GET{name}" class="text" value="#ENV*{#GET{name},#GET{defaut}}" id="#GET{name}" [(#HTML5|et{#GET{obli}})required='required']/>
+                               </li>
+                               #SET{name,descriptif}#SET{obli,''}#SET{defaut,''}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}}
+                               <li class="editer editer_[(#GET{name})][ (#GET{obli})][ (#GET{erreurs}|oui)erreur]">
+                                       <label for="#GET{name}">[(#GET{fl}|concat{':label_',#GET{name}}|_T)]</label>[
+                                       <span class='erreur_message'>(#GET{erreurs})</span>
+                                       ]<textarea name="#GET{name}" class="textarea">
+#ENV*{#GET{name},#GET{defaut}}</textarea>
+                               </li>
+                               #SET{name,url_evenement}#SET{obli,''}#SET{defaut,'evenement'}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}}
+                               <li class="editer editer_[(#GET{name})][ (#GET{obli})][ (#GET{erreurs}|oui)erreur]">
+                                       <label>[(#GET{fl}|concat{':label_',#GET{name}}|_T)]</label>[
+                                       <span class='erreur_message'>(#GET{erreurs})</span>
+                                       ]
+                                       #SET{val,evenement}
+                                       <div class="choix">
+                                               <input type="radio" name="#GET{name}" class="radio" id="#GET{name}_#GET{val}" value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)checked="checked"] />
+                                               <label for="#GET{name}_#GET{val}">[(#GET{fl}|concat{':label_',#GET{name},'_',#GET{val}}|_T)]</label>
+                                       </div>
+                                       #SET{val,article}
+                                       <div class="choix">
+                                               <input type="radio" name="#GET{name}" class="radio" id="#GET{name}_#GET{val}" value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)checked="checked"] />
+                                               <label for="#GET{name}_#GET{val}">[(#GET{fl}|concat{':label_',#GET{name},'_',#GET{val}}|_T)]</label>
+                                       </div>
+                               </li>
+                               [(#VAL{_AGENDA_INSERT_HEAD_CSS}|defined|non)
+                               #SET{name,insert_head_css}#SET{obli,''}#SET{defaut,1}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}}
+                               <li class="editer editer_[(#GET{name})][ (#GET{obli})][ (#GET{erreurs}|oui)erreur]">[
+                                       <span class='erreur_message'>(#GET{erreurs})</span>
+                                       ]
+                                       #SET{val,1}
+                                       <div class="choix">
+                                               <input type="checkbox" name="#GET{name}" class="checkbox" id="#GET{name}_#GET{val}" value="#GET{val}"[(#GET{val}|=={#ENV{#GET{name},#GET{defaut}}}|oui)checked="checked"] />
+                                               <label for="#GET{name}_#GET{val}">[(#GET{fl}|concat{':label_',#GET{name},'_',#GET{val}}|_T)]</label>
+                                       </div>
+                               </li>
+                               ]
+                               <li class="fieldset">
+                                       <fieldset>
+                                               <legend><:configureragenda:legend_presentation_agenda:></legend>
+                                               <ul>
+                                                       #SET{name,affichage_debut}#SET{obli,''}#SET{defaut,''}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}}
+                                                       <li class="editer long_label editer_[(#GET{name})][ (#GET{obli})][ (#GET{erreurs}|oui)erreur]">
+                                                               <label for="#GET{name}">[(#GET{fl}|concat{':label_',#GET{name}}|_T)]</label>[
+                                                               <span class='erreur_message'>(#GET{erreurs})</span>
+                                                               ]<select name="#GET{name}" class="select" id="#GET{name}">
+                                                                       #SET{val,date_jour}
+                                                                       <option value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)selected="selected"]>[(#GET{fl}|concat{':label_',#GET{name},'_',#GET{val}}|_T)]</option>
+                                                                       #SET{val,date_veille}
+                                                                       <option value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)selected="selected"]>[(#GET{fl}|concat{':label_',#GET{name},'_',#GET{val}}|_T)]</option>
+                                                                       #SET{val,debut_semaine}
+                                                                       <option value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)selected="selected"]>[(#GET{fl}|concat{':label_',#GET{name},'_',#GET{val}}|_T)]</option>
+                                                                       #SET{val,debut_semaine_prec}
+                                                                       <option value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)selected="selected"]>[(#GET{fl}|concat{':label_',#GET{name},'_',#GET{val}}|_T)]</option>
+                                                                       #SET{val,debut_mois}
+                                                                       <option value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)selected="selected"]>[(#GET{fl}|concat{':label_',#GET{name},'_',#GET{val}}|_T)]</option>
+                                                                       #SET{val,debut_mois_prec}
+                                                                       <option value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)selected="selected"]>[(#GET{fl}|concat{':label_',#GET{name},'_',#GET{val}}|_T)]</option>
+                                                                       #SET{val,debut_mois_1}
+                                                                       <option value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)selected="selected"]>[(#GET{fl}|concat{':label_',#GET{name},'_',#GET{val}}|_T)]</option>
+                                                                       <BOUCLE_dmois(DATA){enum 2,12}>
+                                                                               #SET{val,debut_mois_#VALEUR}
+                                                                               <option value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)selected="selected"]>[(#GET{fl}|concat{':label_',#GET{name},'_mois_passe'}|_T{[(#ARRAY{mois,[(#VAL{date_mois_}|concat{#VALEUR}|_T|ucfirst)]})]})]</option>
+                                                                       </BOUCLE_dmois>
+                                                               </select>
+                                                       </li>
+                                                       #SET{name,affichage_duree}#SET{obli,''}#SET{defaut,1}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}}
+                                                       <li class="editer long_label editer_[(#GET{name})][ (#GET{obli})][ (#GET{erreurs}|oui)erreur]">
+                                                               <label for="#GET{name}">[(#GET{fl}|concat{':label_',#GET{name}}|_T)]</label>[
+                                                               <span class='erreur_message'>(#GET{erreurs})</span>
+                                                               ]<select name="#GET{name}" class="select" id="#GET{name}">
+                                                                       <BOUCLE_nmois(DATA){enum 1,12}>
+                                                                               #SET{val,#VALEUR}
+                                                                               <option value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)selected="selected"]>[(#GET{val}|singulier_ou_pluriel{agenda:info_1_mois,agenda:info_nb_mois})]</option>
+                                                                       </BOUCLE_nmois>
+                                                               </select>
+                                                       </li>
+                                               </ul>
+                                       </fieldset>
+                               </li>
+                       </ul>
+
+         [(#REM) ajouter les saisies supplémentaires : extra et autre, à cet endroit ]
+         <!--extra-->
+         <p class='boutons'><span class='image_loading'>&nbsp;</span>
+                       <input type='submit' name="cancel" class='submit' value='<:bouton_annuler|attribut_html:>' />
+                       <input type='submit' class='submit' value='<:bouton_enregistrer|attribut_html:>' /></p>
+       </div></form>
+</div>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/formulaires/editer_evenement.html b/www/plugins/agenda_3_5/formulaires/editer_evenement.html
new file mode 100644 (file)
index 0000000..0ef9273
--- /dev/null
@@ -0,0 +1,113 @@
+<div class="formulaire_spip formulaire_editer formulaire_editer_evenement formulaire_editer_evenemnt-#ENV{id_evenement,nouveau}">
+       [<p class="reponse_formulaire reponse_formulaire_ok">(#ENV**{message_ok})</p>]
+       [<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV**{message_erreur})</p>]
+       [(#ENV{editable})
+       <form method='post' action='#ENV{action}' enctype='multipart/form-data'><div>
+               [(#REM) declarer les hidden qui declencheront le service du formulaire 
+               parametre : url d'action ]
+               #ACTION_FORMULAIRE{#ENV{action}}
+               <input type='hidden' name='id_evenement' value='#ID_EVENEMENT' />
+               <input type='hidden' name='id_parent' value='#ENV{id_parent}' />
+               <ul>
+                       <li class="editer editer_titre obligatoire[ (#ENV**{erreurs}|table_valeur{titre}|oui)erreur]">
+                               <label for="titre"><:agenda:evenement_titre:></label>[
+                               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{titre})</span>
+                               ]<input type='text' class='text' name='titre' id='titre' value="#ENV{titre}" />
+                       </li>
+                       <li class='editer editer_parent[ (#ENV**{erreurs}|table_valeur{id_parent}|oui)erreur]'>
+                               <label for="id_parent"><:agenda:evenement_article:></label>[
+                               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{id_parent})</span>
+                               ]<INCLURE{fond=formulaires/selecteur/articles}{selected=#ENV{parents_id}}{id_article=#ENV{id_parent}}{name=parents_id}{select=1}{rubriques=0}>
+                       </li>
+                       <li class='editer editer_date fieldset'><fieldset><legend><:agenda:evenement_date:></legend>
+                               <ul>
+                                       <li class="editer editer_horaire[ (#ENV**{erreurs}|table_valeur{horaire}|oui)erreur]">
+                                               <label for="horaire"><:agenda:evenement_horaire:></label>[
+                                               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{horaire})</span>
+                                               ]<input type='checkbox' name='horaire' id='horaire' value='non' [(#ENV{horaire}|=={oui}|non)checked='checked']
+                                                       onclick="if (this.checked==false) { $('span.afficher_horaire').show();} else {$('span.afficher_horaire').hide();}" />
+                                       </li>
+                                       <li class="editer editer_date_debut_fin[ (#ENV**{erreurs}|table_valeur{date_debut}|oui)erreur][ (#ENV**{erreurs}|table_valeur{date_fin}|oui)erreur]">
+                                               [
+                                               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{date_debut})</span>][
+                                               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{date_fin})</span>
+                                               ]<label for="date_debut"><:agenda:evenement_date_de:></label><input type='text' class='text date' name='date_debut' id='date_debut' size='10' maxlength='10' value="[(#ENV{date_debut})]" />
+                                               <span class='afficher_horaire[(#ENV{horaire}|=={oui}|non)none]'>
+                                               <label for='heure_debut' class='heure'><:agenda:evenement_date_a_immediat:></label>
+                                               <input type='text' class='text heure' name='heure_debut' id='heure_debut' size='4' maxlength='5' value="[(#ENV{heure_debut})]"
+                                               /></span>
+                                               <label for="date_fin" class='date_fin'><:agenda:evenement_date_a:></label>
+                                               <span class='afficher_horaire[(#ENV{horaire}|=={oui}|non)none]'><label for='heure_fin' class='heure'><:agenda:evenement_date_a_immediat:></label>
+                                               <input type='text' class='text heure' name='heure_fin' id='heure_fin' size='4' maxlength='5' value="[(#ENV{heure_fin})]"
+                                               /></span><input type='text' class='text date' name='date_fin' id='date_fin' size='10' maxlength='10' value="[(#ENV{date_fin})]" />
+                                       </li>
+                               </ul>
+                       </fieldset></li>
+                       <li class="editer editer_repetitions[ (#ENV**{erreurs}|table_valeur{repetitions}|oui)erreur]">
+                               <label for="repetitions"><:agenda:evenement_repetitions:></label>[
+                               (#ENV{repetitions}|non)<a href='#' onclick="$(this).hide().next().show('fast');return false;"><:agenda:ajouter_repetition:></a>
+                               <div class='ajouter_repetitions none'>][
+                               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{repetitions})</span>
+                               ]<div id='repetitions_picker'></div>
+                               <textarea name='repetitions' id='repetitions'>#ENV{repetitions}</textarea>[
+                               (#ENV{repetitions}|non)</div>]
+                       </li>
+                       <li class="editer editer_descriptif[ (#ENV**{erreurs}|table_valeur{descriptif}|oui)erreur]">
+                               <label for="descriptif"><:agenda:evenement_descriptif:></label>[
+                               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{descriptif})</span>
+                               ]<textarea name='descriptif' rows='5' id='descriptif' class="inserer_barre_edition">[(#ENV{descriptif})]</textarea>
+                       </li>[
+                       (#ENV{affiche_inscription,oui}|=={oui}|oui)
+                       <li class="editer editer_inscription[ (#ENV**{erreurs}|table_valeur{inscription}|oui)erreur]">
+                               <div class='choix inscription'>
+                                               [<span class='erreur_message'>(#ENV**{erreurs}|table_valeur{inscription})</span>]
+                                               <input type='checkbox' class='checkbox' name='inscription' id='inscription' value="1"[ (#ENV{inscription}|oui)checked="checked"] />
+                                               <label for="inscription"><:agenda:label_inscription:></label>
+                                       </div><div class='choix places'>
+                                       <label for="places"><:agenda:label_places:></label>[
+                                       <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{places})</span>
+                                       ]<input type='text' class='text' name='places' id='places' value="[(#ENV{places})]" />
+                               </div>
+                       </li>]
+                       <li class="editer editer_lieu[ (#ENV**{erreurs}|table_valeur{lieu}|oui)erreur]">
+                               <label for="lieu"><:agenda:evenement_lieu:></label>[
+                               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{lieu})</span>
+                               ]<input type='text' class='text' name='lieu' id='lieu' value="[(#ENV{lieu})]" />
+                       </li>
+                       <li class="editer editer_adresse[ (#ENV**{erreurs}|table_valeur{adresse}|oui)erreur]">
+                               <label for="adresse"><:agenda:evenement_adresse:></label>[
+                               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{adresse})</span>
+                               ]<textarea name='adresse' rows='3' id='adresse'>[(#ENV{adresse})]</textarea>
+                       </li>
+               </ul>
+               [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ]
+               <!--extra-->
+               <p class='boutons'><input type='submit' class='submit' value='[(#ENV{id_evenement}|?{<:bouton_enregistrer:>,<:bouton_ajouter:>})]' /></p>
+       </div></form>
+       #INCLURE{fond=formulaires/dateur/inc-dateur}
+       ]
+</div>
+<script type="text/javascript">
+       var repetitions_done = false;
+       jQuery(document).bind('datePickerLoaded',function(){
+               if (!repetitions_done){
+                       repetitions_done = true;
+                       jQuery.getScript('#CHEMIN{javascript/jquery-ui.multidatespicker.js}',function(){
+                               var multidate_picker_options = {altField: '#repetitions'};
+                               /**
+                                * Multidatepicker n'aime pas un array vide apparemment
+                                */
+                               if(jQuery('#repetitions').html() != ""){
+                                       multidate_picker_options.addDates = jQuery('#repetitions').html().split(',');
+                               }
+                               jQuery('#repetitions_picker')
+                                       .multiDatesPicker(jQuery.extend(
+                                                               date_picker_options(),
+                                                               multidate_picker_options
+                                       ))
+                                       .addClass('.pickable'); // une seule fois;
+                               jQuery('#repetitions').attr("readonly","readonly");
+                       });
+               }
+       });
+</script>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/formulaires/editer_evenement.php b/www/plugins/agenda_3_5/formulaires/editer_evenement.php
new file mode 100644 (file)
index 0000000..a4776af
--- /dev/null
@@ -0,0 +1,141 @@
+<?php
+/**
+ * Plugin Agenda 4 pour Spip 3.0
+ * Licence GPL 3
+ *
+ * 2006-2011
+ * Auteurs : cf paquet.xml
+ */
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+include_spip('inc/actions');
+include_spip('inc/editer');
+include_spip('inc/autoriser');
+
+function formulaires_editer_evenement_charger_dist($id_evenement='new', $id_article=0, $retour='', $lier_trad = 0, $config_fonc='evenements_edit_config', $row=array(), $hidden=''){
+
+       $valeurs = formulaires_editer_objet_charger('evenement',$id_evenement,$id_article,0,$retour,$config_fonc,$row,$hidden);
+
+       if (!$valeurs['id_article'])
+               $valeurs['id_article'] = $id_article;
+       if (!$valeurs['titre'])
+               $valeurs['titre'] = sql_getfetsel('titre','spip_articles','id_article='.intval($valeurs['id_article']));
+       $valeurs['id_parent'] = $valeurs['id_article'];
+       unset($valeurs['id_article']);
+       // pour le selecteur d'article(s) optionnel
+       $valeurs['parents_id'] = array("article|".$valeurs['id_parent']);
+
+       // fixer la date par defaut en cas de creation d'evenement
+       if (!intval($id_evenement)){
+               $t=time();
+               $valeurs["date_debut"] = date('Y-m-d H:i:00',$t);
+               $valeurs["date_fin"] = date('Y-m-d H:i:00',$t+3600);
+               $valeurs['horaire'] = 'oui';
+       }
+
+       // les repetitions
+       $valeurs['repetitions'] = array();
+       if (intval($id_evenement)){
+               $repetitons = sql_allfetsel("date_debut","spip_evenements","id_evenement_source=".intval($id_evenement),'','date_debut');
+               foreach($repetitons as $d)
+                       $valeurs['repetitions'][] = date('d/m/Y',strtotime($d['date_debut']));
+       }
+       $valeurs['repetitions'] = implode(',',$valeurs['repetitions']);
+
+       // dispatcher date et heure
+       list($valeurs["date_debut"],$valeurs["heure_debut"]) = explode(' ',date('d/m/Y H:i',strtotime($valeurs["date_debut"])));
+       list($valeurs["date_fin"],$valeurs["heure_fin"]) = explode(' ',date('d/m/Y H:i',strtotime($valeurs["date_fin"])));
+
+       // traiter specifiquement l'horaire qui est une checkbox
+       if (_request('date_debut') AND !_request('horaire'))
+               $valeurs['horaire'] = 'oui';
+
+       // Pouvoir interdire l'affichage de l'inscription (puisque ce n'est pas traite' par le plugin)
+       $valeurs['affiche_inscription'] = $GLOBALS['agenda_affiche_inscription'];
+
+       $valeurs['places'] = intval($valeurs['places']);
+
+       return $valeurs;
+}
+
+/**
+ * Identifier le formulaire en faisant abstraction des parametres qui
+ * ne representent pas l'objet edite
+ */
+function formulaires_editer_evenement_identifier_dist($id_evenement='new', $id_article=0, $retour='', $lier_trad = 0, $config_fonc='evenements_edit_config', $row=array(), $hidden=''){
+       return serialize(array(intval($id_evenement),$lier_trad));
+}
+
+
+function evenements_edit_config(){
+       return array();
+}
+
+function formulaires_editer_evenement_verifier_dist($id_evenement='new', $id_article=0, $retour='', $lier_trad = 0, $config_fonc='evenements_edit_config', $row=array(), $hidden=''){
+       $erreurs = formulaires_editer_objet_verifier('evenement',$id_evenement,array('titre','date_debut','date_fin'));
+       include_spip('inc/date_gestion');
+
+       $horaire = _request('horaire')=='non'?false:true;
+       if(!$erreurs['date_debut'])
+               $date_debut = verifier_corriger_date_saisie('debut',$horaire,$erreurs);
+       if(!$erreurs['date_fin'])
+               $date_fin = verifier_corriger_date_saisie('fin',$horaire,$erreurs);
+
+       if ($date_debut AND $date_fin AND $date_fin<$date_debut)
+               $erreurs['date_fin'] = _T('agenda:erreur_date_avant_apres');
+
+       include_spip('formulaires/selecteur/selecteur_fonctions');
+       if (count($id = picker_selected(_request('parents_id'),'article'))
+               AND $id = reset($id)
+               AND $id = sql_getfetsel('id_article','spip_articles','id_article='.intval($id))){
+               // reinjecter dans id_parent
+               set_request('id_parent',$id);
+       }
+
+       if (!$id_parent = intval(_request('id_parent')))
+               $erreurs['id_parent'] = _T('agenda:erreur_article_manquant');
+       else {
+               if (!autoriser('creerevenementdans','article',$id_parent))
+                       $erreurs['id_parent'] = _T('agenda:erreur_article_interdit');
+       }
+
+       #if (!count($erreurs))
+       #       $erreurs['message_erreur'] = 'ok?';
+       return $erreurs;
+}
+
+function formulaires_editer_evenement_traiter_dist($id_evenement='new', $id_article=0, $retour='', $lier_trad = 0, $config_fonc='evenements_edit_config', $row=array(), $hidden=''){
+       set_request('horaire',_request('horaire')=='non'?'non':'oui');
+       set_request('inscription',_request('inscription')?1:0);
+       include_spip('inc/date_gestion');
+       $erreurs = array();
+       $date_debut = verifier_corriger_date_saisie('debut',_request('horaire')=='oui',$erreurs);
+       $date_fin = verifier_corriger_date_saisie('fin',_request('horaire')=='oui',$erreurs);
+       set_request('date_debut',date('Y-m-d H:i:s',$date_debut));
+       set_request('date_fin',date('Y-m-d H:i:s',$date_fin));
+
+       $res = formulaires_editer_objet_traiter('evenement',$id_evenement,$id_article,0,$retour,$config_fonc,$row,$hidden);
+       // si c'est une creation dans un article publie, passer l'evenement en publie
+       // l'article peut être renseigné/modifié par l'utilisateur dans le formulaire. On le retrouve.
+       if (!intval($id_evenement)
+               AND $id_article = sql_getfetsel('id_article', 'spip_evenements', 'id_evenement='.$res['id_evenement'])
+               AND objet_test_si_publie('article',$id_article)){
+               // sera refuse si auteur pas autorise
+               evenement_modifier($res['id_evenement'],array('statut'=>'publie'));
+       }
+       // a la creation, documenter la date de creation
+       if (!intval($id_evenement))
+               evenement_modifier($res['id_evenement'],array('date_creation'=>date('Y-m-d H:i:s')));
+
+       $id_evenement = $res['id_evenement'];
+       if ($res['redirect']) {
+               if (strpos($res['redirect'],'article')!==false){
+                       $id_article = sql_getfetsel('id_article','spip_evenements','id_evenement='.intval($id_evenement));
+                       $res['redirect'] = parametre_url($res['redirect'],'id_article',$id_article);
+               }
+       }
+       return $res;
+}
+
+?>
diff --git a/www/plugins/agenda_3_5/formulaires/migrer_agenda.html b/www/plugins/agenda_3_5/formulaires/migrer_agenda.html
new file mode 100644 (file)
index 0000000..29f09e7
--- /dev/null
@@ -0,0 +1,102 @@
+<div class="formulaire_spip formulaire_#FORM formulaire_#FORM-#ENV{id,nouveau}">
+       <h3 class="titrem"><:migreragenda:titre_migrer_agenda:></h3>
+       [<p class="reponse_formulaire reponse_formulaire_ok">(#ENV**{message_ok})</p>]
+       [<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV**{message_erreur})</p>]
+       [(#ENV{editable})
+       <form method='post' action='#ENV{action}'><div>
+               [(#REM) déclarer les hidden qui déclencheront le service du formulaire
+               paramêtre : url d'action ]
+               #ACTION_FORMULAIRE{#ENV{action}}
+               #SET{fl,migreragenda}
+
+               <p class="explication"><:migreragenda:explication_migration_agenda_article_1:></p>
+               <p class="explication"><:migreragenda:explication_migration_agenda_article_2:></p>
+               <ul>
+                       [<li class="editer editer_parent[ (#ENV**{erreurs}|table_valeur{id_parent}|oui)erreur]">
+                               <label for="id_parent"><:migreragenda:label_rubrique_source:></label>[
+                               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{id_parent})</span>
+                               ]
+                               (#VAL|chercher_rubrique{0,#ENV{id_parent},'article',0,0,0,form_simple})
+           </li>]
+                       #SET{name,toute_la_branche}#SET{obli,''}#SET{defaut,''}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}}
+                       <li class="editer long_label editer_[(#GET{name})][ (#GET{obli})][ (#GET{erreurs}|oui)erreur]">[
+                               <span class='erreur_message'>(#GET{erreurs})</span>
+                               ]
+                               #SET{val,oui}
+                               <div class="choix">
+                                       <input type="checkbox" name="#GET{name}" class="checkbox" id="#GET{name}_#GET{val}" value="#GET{val}"[(#GET{val}|=={#ENV{#GET{name},#GET{defaut}}}|oui)checked="checked"] />
+                                       <label for="#GET{name}_#GET{val}">[(#GET{fl}|concat{':label_',#GET{name},'_',#GET{val}}|_T)]</label>
+                               </div>
+                       </li>
+
+                       #SET{name,champ_date_debut}#SET{obli,''}#SET{defaut,'date'}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}}
+                       <li class="editer long_label editer_[(#GET{name})][ (#GET{obli})][ (#GET{erreurs}|oui)erreur]">
+                               <label for="#GET{name}">[(#GET{fl}|concat{':label_',#GET{name}}|_T)]</label>[
+                               <span class='erreur_message'>(#GET{erreurs})</span>
+                               ]<select name="#GET{name}" class="select" id="#GET{name}">
+                                       #SET{val,date}
+                                       <option value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)selected="selected"]>[(#GET{fl}|concat{':label_champ_',#GET{val}}|_T)]</option>
+                                       #SET{val,date_redac}
+                                       <option value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)selected="selected"]>[(#GET{fl}|concat{':label_champ_',#GET{val}}|_T)]</option>
+                               </select>
+                       </li>
+                       #SET{name,champ_date_fin}#SET{obli,''}#SET{defaut,'date_redac'}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}}
+                       <li class="editer long_label editer_[(#GET{name})][ (#GET{obli})][ (#GET{erreurs}|oui)erreur]">
+                               <label for="#GET{name}">[(#GET{fl}|concat{':label_',#GET{name}}|_T)]</label>[
+                               <span class='erreur_message'>(#GET{erreurs})</span>
+                               ]<select name="#GET{name}" class="select" id="#GET{name}">
+                                       #SET{val,date}
+                                       <option value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)selected="selected"]>[(#GET{fl}|concat{':label_champ_',#GET{val}}|_T)]</option>
+                                       #SET{val,date_redac}
+                                       <option value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)selected="selected"]>[(#GET{fl}|concat{':label_champ_',#GET{val}}|_T)]</option>
+                               </select>
+                       </li>
+
+                       #SET{name,horaire}#SET{obli,''}#SET{defaut,'oui'}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}}
+                       <li class="editer long_label editer_[(#GET{name})][ (#GET{obli})][ (#GET{erreurs}|oui)erreur]">
+                               <label>[(#GET{fl}|concat{':label_',#GET{name}}|_T)]</label>[
+                               <span class='erreur_message'>(#GET{erreurs})</span>
+                               ]
+                               #SET{val,oui}
+                               <div class="choix">
+                                       <input type="radio" name="#GET{name}" class="radio" id="#GET{name}_#GET{val}" value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)checked="checked"] />
+                                       <label for="#GET{name}_#GET{val}">[(#GET{fl}|concat{':label_',#GET{name},'_',#GET{val}}|_T)]</label>
+                               </div>
+                               #SET{val,non}
+                               <div class="choix">
+                                       <input type="radio" name="#GET{name}" class="radio" id="#GET{name}_#GET{val}" value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)checked="checked"] />
+                                       <label for="#GET{name}_#GET{val}">[(#GET{fl}|concat{':label_',#GET{name},'_',#GET{val}}|_T)]</label>
+                               </div>
+                       </li>
+
+                       #SET{name,groupes_mots}#SET{obli,''}#SET{defaut,''}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}}
+                       <li class="editer long_label editer_[(#GET{name})][ (#GET{obli})][ (#GET{erreurs}|oui)erreur]">
+                               <label>[(#GET{fl}|concat{':label_',#GET{name}}|_T)]</label>[
+                               <span class='erreur_message'>(#GET{erreurs})</span>
+                               ]
+       ]
+                               <BOUCLE_gr(GROUPES_MOTS){par num titre,titre}{si #ENV{editable}}>
+                               #SET{val,#ID_GROUPE}
+                               <div class="choix">
+                                       <input type="checkbox" name="#GET{name}#EVAL{chr(91)}#EVAL{chr(93)}" class="checkbox" id="#GET{name}_#GET{val}" value="#GET{val}"[(#GET{val}|in_any{#ENV{#GET{name},#GET{defaut}}}|oui)checked="checked"] />
+                                       <label for="#GET{name}_#GET{val}">#TITRE (<:info_numero_abbreviation:> #ID_GROUPE)</label>
+                               </div>
+                               </BOUCLE_gr>
+       [(#ENV{editable})
+                       </li>
+               </ul>
+               <p class="explication"><:migreragenda:explication_migration_agenda_article_fin:></p>
+               [(#REM) ajouter les saisies supplémentaires : extra et autre, à cet endroit ]
+    <!--extra-->
+    <p class='boutons'><span class='image_loading'>&nbsp;</span>
+               <input type='submit' class='submit' value='<:migreragenda:bouton_migrer|attribut_html:>' /></p>
+
+               [<div class="notice">
+                        (#ENV**{erreurs}|table_valeur{confirmer})
+               </div>
+               <p class='boutons'><span class='image_loading'>&nbsp;</span>
+<input type='submit' class='submit' name='confirm' value='<:migreragenda:bouton_lancer_migration|attribut_html:>' /></p>
+               ]
+       </div></form>
+       ]
+</div>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/formulaires/migrer_agenda.php b/www/plugins/agenda_3_5/formulaires/migrer_agenda.php
new file mode 100644 (file)
index 0000000..1f79a57
--- /dev/null
@@ -0,0 +1,182 @@
+<?php
+/**
+ * Plugin Agenda 4 pour Spip 3.0
+ * Licence GPL 3
+ *
+ * 2006-2011
+ * Auteurs : cf paquet.xml
+ */
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+
+function formulaires_migrer_agenda_charger_dist(){
+
+       $valeurs = array(
+
+               'id_parent'=>'',
+               'toute_la_branche' => '',
+               'champ_date_debut' => 'date',
+               'champ_date_fin' => 'date_redac',
+               'horaire' => 'oui',
+               'groupes_mots' => array(),
+       );
+
+       return $valeurs;
+}
+
+function formulaires_migrer_agenda_verifier_dist(){
+
+       $erreurs = array();
+       $oblis = array('id_parent','champ_date_debut','champ_date_fin','horaire');
+
+       foreach ($oblis as $obli){
+               if (!_request($obli))
+                       $erreurs[$obli] = _T('info_obligatoire');
+       }
+
+       if (!isset($erreurs['champ_date_debut'])
+               AND !in_array(_request('champ_date_debut'),array('date','date_redac')))
+               $erreurs['champ_date_debut'] = _T('migreragenda:erreur_choix_incorrect');
+
+       if (!isset($erreurs['champ_date_fin'])
+               AND !in_array(_request('champ_date_fin'),array('date','date_redac')))
+               $erreurs['champ_date_fin'] = _T('migreragenda:erreur_choix_incorrect');
+
+       if (!isset($erreurs['horaire'])
+               AND !in_array(_request('horaire'),array('oui','non')))
+               $erreurs['horaire'] = _T('migreragenda:erreur_choix_incorrect');
+
+       if (!isset($erreurs['groupes_mots'])
+         AND $groupes = _request('groupes_mots')){
+
+               if (!is_array($groupes))
+                       $erreurs['groupes_mots'] = _T('migreragenda:erreur_choix_incorrect');
+               else {
+                       $groupes = array_map('intval',$groupes);
+                       if (sql_countsel('spip_groupes_mots',sql_in('id_groupe',$groupes))!=count($groupes))
+                               $erreurs['groupes_mots'] = _T('migreragenda:erreur_choix_incorrect');
+               }
+       }
+
+       // pas d'erreurs ? verifier ce qui va etre fait et l'annoncer
+       if (!count($erreurs) AND !_request('confirm')){
+               $where = migrer_agenda_where_articles(_request('id_parent'),_request('toute_la_branche'));
+               $nba = sql_countsel("spip_articles",$where);
+
+               $message = _T('migreragenda:info_migration_articles')." ";
+               $message .= sinon(singulier_ou_pluriel($nba,'info_1_article','info_nb_articles'),_T('info_aucun_article'));
+
+               $erreurs['confirmer'] = $message;
+       }
+
+       return $erreurs;
+}
+
+
+function formulaires_migrer_agenda_traiter_dist(){
+       $id_rubrique = _request('id_parent');
+       $where_articles = migrer_agenda_where_articles($id_rubrique,_request('toute_la_branche'));
+       $groupes = _request('groupes_mots');
+       if (!$groupes)
+               $groupes = array();
+       $where_mots = migrer_agenda_where_mots($groupes);
+
+       $horaire = (_request('horaire')=='oui'?true:false);
+       $champ_date_debut = _request('champ_date_debut');
+       $champ_date_fin = _request('champ_date_fin');
+
+       // poser le flag agenda sur la rubrique !
+       sql_updateq("spip_rubriques",array('agenda'=>1),'id_rubrique='.intval($id_rubrique));
+       // et migrer les articles
+       $nb = migrer_articles($where_articles,$champ_date_debut,$champ_date_fin,$horaire,$where_mots);
+
+       $message = _T('migreragenda:info_migration_articles_reussi')." ";
+       $message .= sinon(singulier_ou_pluriel($nb,'info_1_article','info_nb_articles'),_T('info_aucun_article'));
+
+       return array('message_ok'=>$message);
+}
+
+
+
+function migrer_articles($where_articles,$champ_date_debut,$champ_date_fin,$horaire,$where_mots){
+
+       include_spip("action/editer_evenement");
+
+       $where_mots = implode(" AND ",$where_mots);
+
+       $nb = 0;
+       $res = sql_select("*","spip_articles",$where_articles);
+       while ($row = sql_fetch($res)){
+
+               $id_evenement = evenement_inserer($row['id_article']);
+               // mettre les champs
+               $set = array(
+                       'date_debut' => $row[$champ_date_debut],
+                       'date_fin' => $row[$champ_date_fin],
+                       'titre' => $row['titre'],
+                       'horaire' => ($horaire?'oui':'non')
+               );
+               evenement_modifier($id_evenement,$set);
+
+               // associer les mots : en sql pour ne pas exploser si plein de mots en base
+               $mots = sql_allfetsel('M.id_mot','spip_mots AS M JOIN spip_mots_liens AS L ON (M.id_mot=L.id_mot AND L.objet='.sql_quote('article').')',"id_objet=".intval($row['id_article'])." AND (".$where_mots.")");
+               if (count($mots)){
+                       $insert = array();
+                       foreach ($mots as $mot){
+                               $insert[] = array('id_mot'=>$mot['id_mot'],'objet'=>'evenement','id_objet'=>$id_evenement);
+                       }
+                       sql_insertq_multi("spip_mots_liens",$insert);
+               }
+
+
+               // publier l'evenement
+               evenement_modifier($id_evenement,array('statut'=>'publie'));
+
+               $nb++;
+       }
+
+       return $nb;
+}
+
+
+
+function migrer_agenda_where_articles($id_rubrique,$branche = false){
+
+       $where = array();
+       $where[] = "statut=".sql_quote('publie');
+       if ($branche){
+               include_spip("inc/rubriques");
+               $where[] = sql_in('id_rubrique',calcul_branche_in($id_rubrique));
+       }
+       else
+               $where[] = "id_rubrique=".intval($id_rubrique);
+
+       // exclure les articles qui ont deja un evenement
+       $where[] = "id_article NOT IN (".sql_get_select("id_article","spip_evenements").")";
+
+       return $where;
+}
+
+function migrer_agenda_where_mots($groupes){
+       $id_groupe = array();
+
+       $rows = sql_allfetsel('*','spip_groupes_mots',sql_in('id_groupe',$groupes));
+       foreach($rows as $row){
+               $id_groupe[] = $row['id_groupe'];
+               $tables_liees = explode(',',$row['tables_liees']);
+               $tables_liees = array_filter($tables_liees);
+               // ajouter les evenements a ce groupe de mot
+               if (!in_array('evenements',$tables_liees)){
+                       include_spip("action/editer_groupe_mots");
+                       $tables_liees[] = 'evenements';
+                       $tables_liees = implode(',',$tables_liees);
+                       groupemots_modifier($row['id_groupe'],array('tables_liees'=>$tables_liees));
+               }
+       }
+
+       $where = array(sql_in('id_groupe',$id_groupe));
+       return $where;
+}
+
+?>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/formulaires/participer_evenement.html b/www/plugins/agenda_3_5/formulaires/participer_evenement.html
new file mode 100644 (file)
index 0000000..dc649cb
--- /dev/null
@@ -0,0 +1,51 @@
+<div class="formulaire_spip formulaire_#FORM formulaire_#FORM-#ENV{id,#ENV{id_evenement}}">
+       [(#ENV{editable}|non)
+       [<p class="reponse_formulaire reponse_formulaire_ok">(#ENV**{message_ok})</p>]
+       [<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV**{message_erreur})</p>]]
+       [(#ENV{editable})
+       <form method='post' action='#ENV{action}#vevent' enctype='multipart/form-data'><div>
+               [(#REM) declarer les hidden qui declencheront le service du formulaire 
+               parametre : url d'action ]
+               #ACTION_FORMULAIRE{#ENV{action}}
+               <fieldset><legend><:agenda:label_vous_inscrire:></legend>
+               [<p class="reponse_formulaire reponse_formulaire_ok">(#ENV*{message_ok})</p>]
+               [<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV*{message_erreur})</p>]
+               <ul>
+            [(#SESSION{statut}|oui)
+            [<li><:nom:> <strong>(#SESSION{nom}|typo)</strong> <span class="details">&#91;<a href="#URL_LOGOUT" rel="nofollow"><:icone_deconnecter:></a>&#93;</span></li>]
+            ]
+            [(#SESSION{statut}|non)
+            <li class="saisie_reponse[ (#ENV**{erreurs}|table_valeur{nom}|oui)erreur]">
+                <label for="nom_#FORM"><:entree_nom_pseudo_2:></label>
+                [<span class='erreur_message'>(#ENV**{erreurs}|table_valeur{nom})</span>]
+                <input type='text' class='text' name='nom' id='nom_#FORM' value="#ENV{nom}" />
+            </li>
+            <li class="saisie_reponse[ (#ENV**{erreurs}|table_valeur{email}|oui)erreur]">
+                <label for="email_#FORM"><:entree_adresse_email_2:></label>
+                <span class="explication"><:agenda:evenement_participant_email_mention:></span>
+                [<span class='erreur_message'>(#ENV**{erreurs}|table_valeur{email})</span>]
+                <input type='text' class='text' name='email' id='email_#FORM' value="#ENV{email}" />
+            </li>
+            ]
+                       <li class="saisie_reponse[ (#ENV**{erreurs}|table_valeur{reponse}|oui)erreur]">
+                               [<span class='erreur_message'>(#ENV**{erreurs}|table_valeur{reponse})</span>]
+                               <div class='choix'>
+                                       <input type='radio' class='radio' name='reponse' id='reponse_oui' value="oui"[ (#ENV{reponse}|=={oui}|oui)checked="checked"] />
+                                       <label for="reponse_oui"><:agenda:label_reponse_jyparticipe:></label>
+                               </div>
+                               <div class='choix'>
+                                       <input type='radio' class='radio' name='reponse' id='reponse_peutetre' value="?"[ (#ENV{reponse}|=={?}|oui)checked="checked"] />
+                                       <label for="reponse_peutetre"><:agenda:label_reponse_jyparticipe_peutetre:></label>
+                               </div>
+                               <div class='choix'>
+                                       <input type='radio' class='radio' name='reponse' id='reponse_non' value="non"[ (#ENV{reponse}|=={non}|oui)checked="checked"] />
+                                       <label for="reponse_non"><:agenda:label_reponse_jyparticipe_pas:></label>
+                               </div>
+                       </li>
+               </ul>
+       </fieldset>
+       [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ]
+       <!--extra-->
+       <p class='boutons'><input type='submit' class='submit' value='<:bouton_enregistrer:>' /></p>
+       </div></form>]
+</div>
diff --git a/www/plugins/agenda_3_5/formulaires/participer_evenement.php b/www/plugins/agenda_3_5/formulaires/participer_evenement.php
new file mode 100644 (file)
index 0000000..8c53fab
--- /dev/null
@@ -0,0 +1,123 @@
+<?php
+/**
+ * Plugin Agenda 4 pour Spip 3.0
+ * Licence GPL 3
+ *
+ * 2006-2011
+ * Auteurs : cf paquet.xml
+ */
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+include_spip('inc/actions');
+include_spip('inc/editer');
+
+function formulaires_participer_evenement_charger_dist($id_evenement, $mode=''){
+       $valeurs = array(
+        'nom' => isset($GLOBALS['visiteur_session']['id_auteur']) ? $GLOBALS['visiteur_session']['nom'] : _request('nom'),
+        'email' => isset($GLOBALS['visiteur_session']['id_auteur']) ? $GLOBALS['visiteur_session']['email'] : _request('email'),
+        'reponse' => _request('reponse'),
+    );
+       // si pas d'evenement ou d'inscription, on echoue silencieusement
+       if (!$row = sql_fetsel('inscription,places','spip_evenements','id_evenement='.intval($id_evenement).' AND date_fin>NOW()')
+               OR !$row['inscription'])
+               return false;
+
+       // si anonyme, on echoue avec avertissement
+       if ($mode!='public' && (!isset($GLOBALS['visiteur_session']['id_auteur']) || !$GLOBALS['visiteur_session']['id_auteur']))
+               return array(
+                       'message_erreur'=>_T('agenda:connexion_necessaire_pour_inscription'),
+                       'editable'=>false
+               );
+
+       // valeurs d'initialisation
+       $valeurs['id'] = $id_evenement;
+    if(isset($GLOBALS['visiteur_session']['id_auteur']))
+           $valeurs['reponse'] = sql_getfetsel('reponse','spip_evenements_participants','id_evenement='.intval($id_evenement).' AND id_auteur='.intval($GLOBALS['visiteur_session']['id_auteur']));
+
+       // si les places sont comptees, regarder si il en reste
+       if ($places = $row['places']){
+               $ok = sql_countsel('spip_evenements_participants','id_evenement='.intval($id_evenement)." AND reponse='oui'");
+               $peutetre = sql_countsel('spip_evenements_participants','id_evenement='.intval($id_evenement)." AND reponse='?'");
+               // Les reponses PEUT-ETRE sont ponderees a 0,5 donc
+               // on multiplie tout par 2 pour eviter les troncatures ($total ne sert de toute facon que dans les tests)
+               $total = 2*$ok+$peutetre;
+               if ($total>=2*$places){
+                       // dans ce cas, le formulaire est editable seulement si l'auteur a deja repondu oui ou peut-etre, et peut changer d'avis !
+                       if (!($valeurs['reponse']=='oui' OR $valeurs['reponse']=='?')){
+                               $valeurs['editable'] = false;
+                               $valeurs['message_ok'] = _T('agenda:evenement_complet');
+                       }
+               }
+       }
+
+       return $valeurs;
+}
+
+function formulaires_participer_evenement_verifier_dist($id_evenement, $mode=''){
+       $erreurs = array();
+       $reponse = _request('reponse');
+       $nom = _request('nom');
+       // Le test de la ligne suivante sert a savoir si la reponse est vide, non?
+       // On vient juste de la recuperer ci-dessus, pas la peine de la reaffecter...
+       if (!($reponse) OR !in_array($reponse,array('oui','non','?')))
+               $erreurs['reponse'] = _T('agenda:indiquez_votre_choix');
+       elseif ($mode=='public' && !isset($GLOBALS['visiteur_session']['id_auteur']) && !$nom)
+        $erreurs['nom'] = _T('info_obligatoire');
+       elseif ($reponse!=='non' && isset($GLOBALS['visiteur_session']['id_auteur'])) {
+               $row = sql_fetsel('places','spip_evenements','id_evenement='.intval($id_evenement));
+               $valeurs['reponse'] = sql_getfetsel('reponse','spip_evenements_participants','id_evenement='.intval($id_evenement).' AND id_auteur='.intval($GLOBALS['visiteur_session']['id_auteur']));
+               if ($places = $row['places'] AND $valeurs['reponse']!==$reponse){
+                       $ok = sql_countsel('spip_evenements_participants','id_evenement='.intval($id_evenement)." AND reponse='oui'");
+                       $peutetre = sql_countsel('spip_evenements_participants','id_evenement='.intval($id_evenement)." AND reponse='?'");
+                       // Les reponses PEUT-ETRE sont ponderees a 0,5 donc
+                       // on multiplie tout par 2 pour eviter les troncatures ($total ne sert de toute facon que dans les tests)
+                       $total = 2*$ok+$peutetre;
+                       if (
+                               // Si on est au taquet, le seul cas autorise restant (la reponse NON et la reponse identique sont prises
+                               // en compte dans les tests ci-dessus) est: transformation d'un OUI en PEUT-ETRE (-0,5)
+                               ($total>=2*$places AND !($valeurs['reponse']=='oui' AND $reponse=='?'))
+                               OR
+                               // Si il reste un siege PEUT-ETRE, le seul cas interdit restant est: transformation d'un NON en OUI (+1)
+                               ($total==2*$places-1 AND ($valeurs['reponse']=='non' AND $reponse=='oui'))){
+                                       $erreurs['reponse'] = _T('agenda:plus_de_place');
+                       }
+               }
+       }
+       return $erreurs;
+}
+
+function formulaires_participer_evenement_traiter_dist($id_evenement){
+
+       $reponse = _request('reponse');
+    $nom = _request('nom');
+    $email = _request('email');
+    
+    if(isset($GLOBALS['visiteur_session']['id_auteur'])){
+        $editable = true;
+        if (sql_fetsel('reponse','spip_evenements_participants','id_evenement='.intval($id_evenement).' AND id_auteur='.intval($GLOBALS['visiteur_session']['id_auteur'])))
+            sql_updateq('spip_evenements_participants',array('reponse'=>$reponse,'date'=>'NOW()'),'id_evenement='.intval($id_evenement).' AND id_auteur='.intval($GLOBALS['visiteur_session']['id_auteur']));
+        else
+            sql_insertq('spip_evenements_participants',array('id_evenement'=>$id_evenement,'id_auteur'=>$GLOBALS['visiteur_session']['id_auteur'],'reponse'=>$reponse,'date'=>'NOW()'));
+    } else {
+        if (sql_fetsel('reponse','spip_evenements_participants','id_evenement='.intval($id_evenement).' AND email='.sql_quote($email))) {
+               $editable = true;
+               $reponse = 'doublon';
+        } else {
+                       $editable = false;
+               sql_insertq('spip_evenements_participants',array('id_evenement'=>$id_evenement,'nom'=>$nom,'email'=>$email,'reponse'=>$reponse,'date'=>'NOW()'));
+               }
+    }
+    if ($reponse == 'oui')
+        $message = _T('agenda:participation_prise_en_compte');
+    elseif ($reponse == '?')
+        $message = _T('agenda:participation_incertaine_prise_en_compte');
+    elseif ($reponse == 'doublon')
+       $message = _T('erreur_email_deja_existant');
+    else
+        $message = _T('agenda:absence_prise_en_compte');
+
+       return array('message_ok'=>$message,'editable'=>$editable);
+}
+
+?>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/inc/agenda_filtres.php b/www/plugins/agenda_3_5/inc/agenda_filtres.php
new file mode 100644 (file)
index 0000000..84a6186
--- /dev/null
@@ -0,0 +1,197 @@
+<?php
+/**
+ * Plugin Agenda 4 pour Spip 3.0
+ * Licence GPL 3
+ *
+ * 2006-2011
+ * Auteurs : cf paquet.xml
+ */
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+
+/**
+ * Filtres&criteres deprecies/obsoletes
+ */
+
+
+/**
+ * {agendafull ..} variante etendue du crietre agenda du core
+ * qui accepte une date de debut et une date de fin
+ *
+ * {agendafull date_debut, date_fin, jour, #ENV{annee}, #ENV{mois}, #ENV{jour}}
+ * {agendafull date_debut, date_fin, semaine, #ENV{annee}, #ENV{mois}, #ENV{jour}}
+ * {agendafull date_debut, date_fin, mois, #ENV{annee}, #ENV{mois}}
+ * {agendafull date_debut, date_fin, periode, #ENV{annee}, #ENV{mois}, #ENV{jour},
+ *                                            #ENV{annee_fin}, #ENV{mois_fin}, #ENV{jour_fin}}
+ *
+ * @param string $idb
+ * @param object $boucles
+ * @param object $crit
+ */
+function critere_agendafull_dist($idb, &$boucles, $crit)
+{
+       $params = $crit->param;
+
+       if (count($params) < 1)
+             erreur_squelette(_T('zbug_info_erreur_squelette'),
+                              "{agenda ?} BOUCLE$idb");
+
+       $parent = $boucles[$idb]->id_parent;
+
+       // les valeurs $date et $type doivent etre connus a la compilation
+       // autrement dit ne pas etre des champs
+
+       $date_deb = array_shift($params);
+       $date_deb = $date_deb[0]->texte;
+
+       $date_fin = array_shift($params);
+       $date_fin = $date_fin[0]->texte;
+
+       $type = array_shift($params);
+       $type = $type[0]->texte;
+
+       $annee = $params ? array_shift($params) : "";
+       $annee = "\n" . 'sprintf("%04d", ($x = ' .
+               calculer_liste($annee, array(), $boucles, $parent) .
+               ') ? $x : date("Y"))';
+
+       $mois =  $params ? array_shift($params) : "";
+       $mois = "\n" . 'sprintf("%02d", ($x = ' .
+               calculer_liste($mois, array(), $boucles, $parent) .
+               ') ? $x : date("m"))';
+
+       $jour =  $params ? array_shift($params) : "";
+       $jour = "\n" . 'sprintf("%02d", ($x = ' .
+               calculer_liste($jour, array(), $boucles, $parent) .
+               ') ? $x : date("d"))';
+
+       $annee2 = $params ? array_shift($params) : "";
+       $annee2 = "\n" . 'sprintf("%04d", ($x = ' .
+               calculer_liste($annee2, array(), $boucles, $parent) .
+               ') ? $x : date("Y"))';
+
+       $mois2 =  $params ? array_shift($params) : "";
+       $mois2 = "\n" . 'sprintf("%02d", ($x = ' .
+               calculer_liste($mois2, array(), $boucles, $parent) .
+               ') ? $x : date("m"))';
+
+       $jour2 =  $params ? array_shift($params) : "";
+       $jour2 = "\n" .  'sprintf("%02d", ($x = ' .
+               calculer_liste($jour2, array(), $boucles, $parent) .
+               ') ? $x : date("d"))';
+
+       $boucle = &$boucles[$idb];
+       $date = $boucle->id_table . ".$date";
+
+       $quote_end = ",'".$boucle->sql_serveur."','text'";
+
+       if ($type == 'jour')
+               $boucle->where[]= array("'AND'",
+                                       array("'<='", "'DATE_FORMAT($date_deb, \'%Y%m%d\')'",("sql_quote($annee . $mois . $jour$quote_end)")),
+                                       array("'>='", "'DATE_FORMAT($date_fin, \'%Y%m%d\')'",("sql_quote($annee . $mois . $jour$quote_end)")));
+       elseif ($type == 'mois')
+               $boucle->where[]= array("'AND'",
+                                       array("'<='", "'DATE_FORMAT($date_deb, \'%Y%m\')'",("sql_quote($annee . $mois$quote_end)")),
+                                       array("'>='", "'DATE_FORMAT($date_fin, \'%Y%m\')'",("sql_quote($annee . $mois$quote_end)")));
+       elseif ($type == 'semaine')
+               $boucle->where[]= array("'AND'",
+                                       array("'>='",
+                                            "'DATE_FORMAT($date_fin, \'%Y%m%d\')'",
+                                             ("date_debut_semaine($annee, $mois, $jour)")),
+                                       array("'<='",
+                                             "'DATE_FORMAT($date_deb, \'%Y%m%d\')'",
+                                             ("date_fin_semaine($annee, $mois, $jour)")));
+       elseif (count($crit->param) > 3)
+               $boucle->where[]= array("'AND'",
+                                       array("'>='",
+                                             "'DATE_FORMAT($date_fin, \'%Y%m%d\')'",
+                                             ("sql_quote($annee . $mois . $jour$quote_end)")),
+                                       array("'<='", "'DATE_FORMAT($date_deb, \'%Y%m%d\')'", ("sql_quote($annee2 . $mois2 . $jour2$quote_end)")));
+       // sinon on prend tout
+}
+
+
+/**
+ * Afficher de facon textuelle les dates de debut et fin en fonction des cas
+ * - Le lundi 20 fevrier a 18h
+ * - Le 20 fevrier de 18h a 20h
+ * - Du 20 au 23 fevrier
+ * - du 20 fevrier au 30 mars
+ * - du 20 fevrier 2007 au 30 mars 2008
+ * $horaire='oui' permet d'afficher l'horaire, toute autre valeur n'indique que le jour
+ * $forme peut contenir abbr (afficher le nom des jours en abbrege) et ou hcal (generer une date au format hcal)
+ * 
+ * @param string $date_debut
+ * @param string $date_fin
+ * @param string $horaire
+ * @param string $forme
+ * @return string
+ */
+function agenda_affdate_debut_fin($date_debut, $date_fin, $horaire = 'oui', $forme=''){
+       $abbr = '';
+       if (strpos($forme,'abbr')!==false) $abbr = 'abbr';
+       $affdate = "affdate_jourcourt";
+       if (strpos($forme,'annee')!==false) $affdate = 'affdate';
+       
+       $dtstart = $dtend = $dtabbr = "";
+       if (strpos($forme,'hcal')!==false) {
+               $dtstart = "<abbr class='dtstart' title='".date_iso($date_debut)."'>";
+               $dtend = "<abbr class='dtend' title='".date_iso($date_fin)."'>";
+               $dtabbr = "</abbr>";
+       }
+       
+       $date_debut = strtotime($date_debut);
+       $date_fin = strtotime($date_fin);
+       $d = date("Y-m-d", $date_debut);
+       $f = date("Y-m-d", $date_fin);
+       $h = $horaire=='oui';
+       $hd = date("H:i",$date_debut);
+       $hf = date("H:i",$date_fin);
+       $au = " " . strtolower(_T('agenda:evenement_date_au'));
+       $du = _T('agenda:evenement_date_du') . " ";
+       $s = "";
+       if ($d==$f)
+       { // meme jour
+               $s = ucfirst(nom_jour($d,$abbr))." ".$affdate($d);
+               if ($h)
+                       $s .= " $hd";
+               $s = "$dtstart$s$dtabbr";
+               if ($h AND $hd!=$hf) $s .= "-$dtend$hf$dtabbr";
+       }
+       else if ((date("Y-m",$date_debut))==date("Y-m",$date_fin))
+       { // meme annee et mois, jours differents
+               if ($h){
+                       $s = $du . $dtstart . affdate_jourcourt($d) . " $hd" . $dtabbr;
+                       $s .= $au . $dtend . $affdate($f);
+                       if ($hd!=$hf) $s .= " $hf";
+                       $s .= $dtabbr;
+               }
+               else {
+                       $s = $du . $dtstart . jour($d) . $dtabbr;
+                       $s .= $au . $dtend . $affdate($f) . $dtabbr;
+               }
+       }
+       else if ((date("Y",$date_debut))==date("Y",$date_fin))
+       { // meme annee, mois et jours differents
+               $s = $du . $dtstart . affdate_jourcourt($d);
+               if ($h) $s .= " $hd";
+               $s .= $dtabbr . $au . $dtend . $affdate($f);
+               if ($h) $s .= " $hf";
+               $s .= $dtabbr;
+       }
+       else
+       { // tout different
+               $s = $du . $dtstart . affdate($d);
+               if ($h)
+                       $s .= " ".date("(H:i)",$date_debut);
+               $s .= $dtabbr . $au . $dtend. affdate($f);
+               if ($h)
+                       $s .= " ".date("(H:i)",$date_fin);
+               $s .= $dtabbr;
+       }
+       return unicode2charset(charset2unicode($s,'AUTO'));     
+}
+
+
+?>
diff --git a/www/plugins/agenda_3_5/inc/date_gestion.php b/www/plugins/agenda_3_5/inc/date_gestion.php
new file mode 100644 (file)
index 0000000..6531615
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Plugin Agenda 4 pour Spip 3.0
+ * Licence GPL 3
+ *
+ * 2006-2011
+ * Auteurs : cf paquet.xml
+ */
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+
+
+/**
+ * Recuperer les champs date_xx et heure_xx, verifier leur coherence et les reformater
+ *
+ * @param string $suffixe
+ * @param bool $horaire
+ * @param array $erreurs
+ * @return int
+ */
+function verifier_corriger_date_saisie($suffixe,$horaire,&$erreurs){
+       include_spip('inc/filtres');
+       $date = _request("date_$suffixe").($horaire?' '.trim(_request("heure_$suffixe")).':00':'');
+       $date = recup_date($date);
+       if (!$date)
+               return '';
+       $ret = null;
+       if (!$ret=mktime(0,0,0,$date[1],$date[2],$date[0]))
+               $erreurs["date_$suffixe"] = _T('agenda:erreur_date');
+       elseif (!$ret=mktime($date[3],$date[4],$date[5],$date[1],$date[2],$date[0]))
+               $erreurs["date_$suffixe"] = _T('agenda:erreur_heure');
+       if ($ret){
+               if (trim(_request("date_$suffixe")!==($d=date('d/m/Y',$ret)))){
+                       $erreurs["date_$suffixe"] = _T('agenda:erreur_date_corrigee');
+                       set_request("date_$suffixe",$d);
+               }
+               if ($horaire AND trim(_request("heure_$suffixe")!==($h=date('H:i',$ret)))){
+                       $erreurs["heure_$suffixe"] = _T('agenda:erreur_heure_corrigee');
+                       set_request("heure_$suffixe",$h);
+               }
+       }
+       return $ret;
+}
+
+?>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/inc/un-evenement-ical.html b/www/plugins/agenda_3_5/inc/un-evenement-ical.html
new file mode 100644 (file)
index 0000000..ac77161
--- /dev/null
@@ -0,0 +1,17 @@
+BEGIN:VEVENT
+SUMMARY:[(#EVTITRE|textebrut|filtrer_ical)]
+UID:evenement#ID_EVENEMENT @ [(#URL_SITE_SPIP|filtrer_ical)][
+DTSTAMP:(#DATE_CREATION|date_ical)][(#HORAIRE|=={oui}|?{[
+DTSTART:(#DATE_DEBUT|date_ical)][
+DTEND:(#DATE_FIN|date_ical)],[
+DTSTART;VALUE=DATE:(#DATE_DEBUT|affdate{Ymd})][
+DTEND;VALUE=DATE:(#DATE_FIN|agenda_jourdecal{1,Ymd})]})][
+CREATED:(#DATE_CREATION|date_ical)][
+LAST-MODIFIED:(#MAJ|date_ical)][
+LOCATION:(#LIEU|PtoBR|textebrut|filtrer_ical)][
+DESCRIPTION:(#DESCRIPTIF|supprimer_tags|textebrut|filtrer_ical)]
+CATEGORIES:<BOUCLE_article(ARTICLES) {id_article=#ID_ARTICLE}>[(#TITRE|textebrut|filtrer_ical)]</BOUCLE_article>
+URL:[(#URL_ARTICLE|parametre_url{id_evenement,#ID_EVENEMENT}|url_absolue|filtrer_ical)]<BOUCLE_sequence(VERSIONS ?){objet=evenement}{id_objet=#ID_EVENEMENT}{!par id_version}{0,1}>[
+SEQUENCE:(#ID_VERSION|moins{1})
+]</BOUCLE_sequence>STATUS:CONFIRMED
+END:VEVENT
diff --git a/www/plugins/agenda_3_5/inclure/agenda-vue-calendrier.html b/www/plugins/agenda_3_5/inclure/agenda-vue-calendrier.html
new file mode 100644 (file)
index 0000000..a65661d
--- /dev/null
@@ -0,0 +1,82 @@
+<style type="text/css">
+.hover-title { color: black; border: 1px solid #888; padding: 3px; background-color: #DDD; position: absolute; bottom: 0; border-radius: 3px; width: auto; width: 200%; opacity: .9; }
+.fc-event-inner { background-color: #E0ECFF; }
+.fc-header-title { margin: auto 1em; }
+#fullcalendar_#ID_MOT-#ID_RUBRIQUE { position: relative; margin: 1em auto; padding: 1em; [(#LARGEUR|is_null|non)width: #LARGEURpx;] }
+</style>
+
+#SET{data,#ARRAY}
+<BOUCLE_articles(ARTICLES) {id_mot ?} {id_rubrique ?} {par date} {inverse} ><BOUCLE_événements(EVENEMENTS) {id_article} >[
+(#SET{événement,#ARRAY{title,'',hover,#TITRE,start,#DATE_DEBUT,end,#DATE_FIN,id,#ID_ARTICLE,url,#URL_ARTICLE}})][
+(#SET{data,#GET{data}|push{#GET{événement}}})
+]</BOUCLE_événements></BOUCLE_articles>
+
+[(#SET{json_data,#GET{data}|json_encode{2}})]
+
+<div id="fullcalendar_#ID_MOT-#ID_RUBRIQUE"></div>
+
+#SET{français,0}
+[(#LANG|is_null|oui)#SET{français,1}]
+[(#LANG|=={fr}|oui)#SET{français,1}]
+
+<script type="text/javascript">
+$(function() {
+       $.getScript('#CHEMIN{lib/fullcalendar/fullcalendar.min.js}', function() {
+               $('head').append('<link rel="stylesheet" type="text/css"  media="screen" href="#CHEMIN{lib/fullcalendar/fullcalendar.css}">');
+               $('head').append('<link rel="stylesheet" type="text/css" media="print" href="#CHEMIN{lib/fullcalendar/fullcalendar.print.css}">');
+               var data = #GET{json_data};
+               $('#fullcalendar_#ID_MOT-#ID_RUBRIQUE').fullCalendar({
+                       header: {
+                               left: '',
+                               center: 'prev,title,next',
+                               right: ''
+                       },
+                       [(#ENV{hauteur}|is_null|non)height: #HAUTEUR,]
+
+                       monthNames:['<:date_mois_1|attribut_html:>','<:date_mois_2|attribut_html:>','<:date_mois_3|attribut_html:>','<:date_mois_4|attribut_html:>','<:date_mois_5|attribut_html:>','<:date_mois_6|attribut_html:>','<:date_mois_7|attribut_html:>','<:date_mois_8|attribut_html:>','<:date_mois_9|attribut_html:>','<:date_mois_10|attribut_html:>','<:date_mois_11|attribut_html:>','<:date_mois_12|attribut_html:>'],
+                       monthNamesShort:['<:date_mois_1_abbr|attribut_html:>','<:date_mois_2_abbr|attribut_html:>','<:date_mois_3_abbr|attribut_html:>','<:date_mois_4_abbr|attribut_html:>','<:date_mois_5_abbr|attribut_html:>','<:date_mois_6_abbr|attribut_html:>','<:date_mois_7_abbr|attribut_html:>','<:date_mois_8_abbr|attribut_html:>','<:date_mois_9_abbr|attribut_html:>','<:date_mois_10_abbr|attribut_html:>','<:date_mois_11_abbr|attribut_html:>','<:date_mois_12_abbr|attribut_html:>'],
+                       dayNames:['<:date_jour_1|attribut_html:>','<:date_jour_2|attribut_html:>','<:date_jour_3|attribut_html:>','<:date_jour_4|attribut_html:>','<:date_jour_5|attribut_html:>','<:date_jour_6|attribut_html:>','<:date_jour_7|attribut_html:>'],
+                       dayNamesShort:['<:date_jour_1_abbr|attribut_html:>','<:date_jour_2_abbr|attribut_html:>','<:date_jour_3_abbr|attribut_html:>','<:date_jour_4_abbr|attribut_html:>','<:date_jour_5_abbr|attribut_html:>','<:date_jour_6_abbr|attribut_html:>','<:date_jour_7_abbr|attribut_html:>'],
+                       buttonText: {
+                               prev: "<span class='fc-text-arrow'>&lsaquo;</span>",
+                               next: "<span class='fc-text-arrow'>&rsaquo;</span>",
+                               prevYear: "<span class='fc-text-arrow'>&laquo;</span>",
+                               nextYear: "<span class='fc-text-arrow'>&raquo;</span>",
+                today: '<:date_aujourdhui|attribut_html:>',
+                month: '<:cal_par_mois|attribut_html:>',
+                day: '<:cal_par_jour|attribut_html:>',
+                week: '<:cal_par_semaine|attribut_html:>'
+                       },
+                       // time formats
+                       titleFormat: {
+                               month: 'MMMM yyyy',
+                               week: "d MMM[ yyyy]{ '&#8212;'d[ MMM] yyyy}",
+                               day: 'dddd, d MMM, yyyy'
+                       },
+                       columnFormat: {
+                               month: 'ddd',
+                               week: 'ddd d/M',
+                               day: 'dddd d/M'
+                       },
+                       firstDay: 1,
+                       events: data,
+                       defaultView: '[(#VUE|=={semaine}|?{week,month})]',
+                       // events: data,
+                       eventMouseover: function(event, jsEvent, view) {
+                               $('.fc-event-inner', this).append('<div id=\"'+event.id+'\" class=\"hover-title\" style=\"\">'+event.hover+'</div>');
+                       },
+                       eventMouseout: function(event, jsEvent, view) {
+                               $('#'+event.id).remove();
+                       },
+                       eventClick: function(event) {
+                               console.log(event);
+                               if (false) {
+                                       var url = '/?page=location_donnees&who=direction_scientifique&id_location=' + mrbs_ids[event.id];
+                                       var win=window.open(url, '_blank');
+                                       win.focus();
+                               }
+                       }
+               })
+       });
+});
+</script>
diff --git a/www/plugins/agenda_3_5/inclure/liste_participants_evenement.html b/www/plugins/agenda_3_5/inclure/liste_participants_evenement.html
new file mode 100644 (file)
index 0000000..c9ddc28
--- /dev/null
@@ -0,0 +1,31 @@
+<BOUCLE_inscrits_oui(evenements_participants){id_evenement=#ENV{id,#ENV{id_evenement}}}{reponse='oui'}>
+</BOUCLE_inscrits_oui>#SET{inscrits,#TOTAL_BOUCLE}</B_inscrits_oui>
+        
+<B_inscrits>
+    #ANCRE_PAGINATION
+    <table class='spip liste'>
+        [<caption>(#GRAND_TOTAL|singulier_ou_pluriel{agenda:info_une_reponse,agenda:info_nb_reponses,nb})[,
+                  (#GET{inscrits}|singulier_ou_pluriel{agenda:info_un_inscrit,agenda:info_nb_inscrits,nb})]</caption>]
+        <thead>
+        <tr class='first_row'>
+            <th scope='col'><:nom:></th>
+            <th scope='col'><:agenda:evenement_date_inscription:></th>
+            <th scope='col'><:agenda:info_reponse_inscriptions:></th>
+        </tr>
+        </thead>
+        <tbody>
+        <BOUCLE_inscrits(evenements_participants){id_evenement=#ENV{id,#ENV{id_evenement}}}{par date}{inverse}{pagination #ENV{nb,15}}>
+        <tr class="[(#COMPTEUR_BOUCLE|alterner{row_odd,row_even})]">
+            <td>[(#NOM|sinon{#INFO_NOM{auteur,#ID_AUTEUR}})]</td>
+            <td>[(#DATE|affdate_jourcourt)]</td>
+            <td>
+                [(#REPONSE|=={'oui'}|oui)<:agenda:label_reponse_jyparticipe:>]
+                [(#REPONSE|=={'non'}|oui)<:agenda:label_reponse_jyparticipe_pas:>]
+                [(#REPONSE|=={'?'}|oui)<:agenda:label_reponse_jyparticipe_peutetre:>]
+            </td>
+        </tr>
+        </BOUCLE_inscrits>
+        </tbody>
+    </table>
+    [<p class='pagination'>(#PAGINATION)</p>]
+</B_inscrits>
diff --git a/www/plugins/agenda_3_5/javascript/jquery-ui.multidatespicker.js b/www/plugins/agenda_3_5/javascript/jquery-ui.multidatespicker.js
new file mode 100755 (executable)
index 0000000..36511a8
--- /dev/null
@@ -0,0 +1,461 @@
+/*\r
+ * MultiDatesPicker v1.6.1\r
+ * http://multidatespickr.sourceforge.net/\r
+ * \r
+ * Copyright 2011, Luca Lauretta\r
+ * Dual licensed under the MIT or GPL version 2 licenses.\r
+ */\r
+(function( $ ){\r
+       $.extend($.ui, { multiDatesPicker: { version: "1.6.1" } });\r
+       \r
+       $.fn.multiDatesPicker = function(method) {\r
+               var mdp_arguments = arguments;\r
+               var ret = this;\r
+               var today_date = new Date();\r
+               var day_zero = new Date(0);\r
+               var mdp_events = {};\r
+               \r
+               function removeDate(date, type) {\r
+                       if(!type) type = 'picked';\r
+                       date = dateConvert.call(this, date);\r
+                       for(var i in this.multiDatesPicker.dates[type])\r
+                               if(!methods.compareDates(this.multiDatesPicker.dates[type][i], date))\r
+                                       return this.multiDatesPicker.dates[type].splice(i, 1).pop();\r
+               }\r
+               function removeIndex(index, type) {\r
+                       if(!type) type = 'picked';\r
+                       return this.multiDatesPicker.dates[type].splice(index, 1).pop();\r
+               }\r
+               function addDate(date, type, no_sort) {\r
+                       if(!type) type = 'picked';\r
+                       date = dateConvert.call(this, date);\r
+                       \r
+                       // @todo: use jQuery UI datepicker method instead\r
+                       date.setHours(0);\r
+                       date.setMinutes(0);\r
+                       date.setSeconds(0);\r
+                       date.setMilliseconds(0);\r
+                       \r
+                       if (methods.gotDate.call(this, date, type) === false) {\r
+                               this.multiDatesPicker.dates[type].push(date);\r
+                               if(!no_sort) this.multiDatesPicker.dates[type].sort(methods.compareDates);\r
+                       } \r
+               }\r
+               function sortDates(type) {\r
+                       if(!type) type = 'picked';\r
+                       this.multiDatesPicker.dates[type].sort(methods.compareDates);\r
+               }\r
+               function dateConvert(date, desired_type, date_format) {\r
+                       if(!desired_type) desired_type = 'object';/*\r
+                       if(!date_format && (typeof date == 'string')) {\r
+                               date_format = $(this).datepicker('option', 'dateFormat');\r
+                               if(!date_format) date_format = $.datepicker._defaults.dateFormat;\r
+                       }\r
+                       */\r
+                       return methods.dateConvert.call(this, date, desired_type, date_format);\r
+               }\r
+               \r
+               var methods = {\r
+                       init : function( options ) {\r
+                               var $this = $(this);\r
+                               this.multiDatesPicker.changed = false;\r
+                               \r
+                               var mdp_events = {\r
+                                       beforeShow: function(input, inst) {\r
+                                               this.multiDatesPicker.changed = false;\r
+                                               if(this.multiDatesPicker.originalBeforeShow) \r
+                                                       this.multiDatesPicker.originalBeforeShow.call(this, input, inst);\r
+                                       },\r
+                                       onSelect : function(dateText, inst) {\r
+                                               var $this = $(this);\r
+                                               this.multiDatesPicker.changed = true;\r
+                                               \r
+                                               if (dateText) {\r
+                                                       $this.multiDatesPicker('toggleDate', dateText);\r
+                                               }\r
+                                               \r
+                                               if (this.multiDatesPicker.mode == 'normal' && this.multiDatesPicker.dates.picked.length > 0 && this.multiDatesPicker.pickableRange) {\r
+                                                       var min_date = this.multiDatesPicker.dates.picked[0],\r
+                                                               max_date = new Date(min_date.getTime());\r
+                                                       \r
+                                                       methods.sumDays(max_date, this.multiDatesPicker.pickableRange-1);\r
+                                                               \r
+                                                       // counts the number of disabled dates in the range\r
+                                                       if(this.multiDatesPicker.adjustRangeToDisabled) {\r
+                                                               var c_disabled, \r
+                                                                       disabled = this.multiDatesPicker.dates.disabled.slice(0);\r
+                                                               do {\r
+                                                                       c_disabled = 0;\r
+                                                                       for(var i = 0; i < disabled.length; i++) {\r
+                                                                               if(disabled[i].getTime() <= max_date.getTime()) {\r
+                                                                                       if((min_date.getTime() <= disabled[i].getTime()) && (disabled[i].getTime() <= max_date.getTime()) ) {\r
+                                                                                               c_disabled++;\r
+                                                                                       }\r
+                                                                                       disabled.splice(i, 1);\r
+                                                                                       i--;\r
+                                                                               }\r
+                                                                       }\r
+                                                                       max_date.setDate(max_date.getDate() + c_disabled);\r
+                                                               } while(c_disabled != 0);\r
+                                                       }\r
+                                                       \r
+                                                       if(this.multiDatesPicker.maxDate && (max_date > this.multiDatesPicker.maxDate))\r
+                                                               max_date = this.multiDatesPicker.maxDate;\r
+                                                       \r
+                                                       $this\r
+                                                               .datepicker("option", "minDate", min_date)\r
+                                                               .datepicker("option", "maxDate", max_date);\r
+                                               } else {\r
+                                                       $this\r
+                                                               .datepicker("option", "minDate", this.multiDatesPicker.minDate)\r
+                                                               .datepicker("option", "maxDate", this.multiDatesPicker.maxDate);\r
+                                               }\r
+                                               \r
+                                               if(this.tagName == 'INPUT') { // for inputs\r
+                                                       $this.val(\r
+                                                               $this.multiDatesPicker('getDates', 'string')\r
+                                                       );\r
+                                               }\r
+                                               \r
+                                               if(this.multiDatesPicker.originalOnSelect && dateText)\r
+                                                       this.multiDatesPicker.originalOnSelect.call(this, dateText, inst);\r
+                                               \r
+                                               // thanks to bibendus83 -> http://sourceforge.net/tracker/?func=detail&atid=1495384&aid=3403159&group_id=358205\r
+                                               if ($this.datepicker('option', 'altField') != undefined && $this.datepicker('option', 'altField') != "") {\r
+                                                       $($this.datepicker('option', 'altField')).val(\r
+                                                               $this.multiDatesPicker('getDates', 'string')\r
+                                                       );\r
+                                               }\r
+                                       },\r
+                                       beforeShowDay : function(date) {\r
+                                               var $this = $(this),\r
+                                                       gotThisDate = $this.multiDatesPicker('gotDate', date) !== false,\r
+                                                       isDisabledCalendar = $this.datepicker('option', 'disabled'),\r
+                                                       isDisabledDate = $this.multiDatesPicker('gotDate', date, 'disabled') !== false,\r
+                                                       areAllSelected = this.multiDatesPicker.maxPicks == this.multiDatesPicker.dates.picked.length;\r
+                                               \r
+                                               var custom = [true, ''];\r
+                                               if(this.multiDatesPicker.originalBeforeShowDay)\r
+                                                       custom = this.multiDatesPicker.originalBeforeShowDay.call(this, date);\r
+                                               \r
+                                               var highlight_class = gotThisDate ? 'ui-state-highlight' : custom[1];\r
+                                               var highlight_class = (gotThisDate ? 'ui-state-highlight' : '') + ((custom[1] && gotThisDate) ? ' ' : '') + custom[1];\r
+                                               var selectable_date = !(isDisabledCalendar || isDisabledDate || (areAllSelected && !highlight_class));\r
+                                               custom[0] = selectable_date && custom[0];\r
+                                               custom[1] = highlight_class;\r
+                                               return custom;\r
+                                       },\r
+                                       onClose: function(dateText, inst) {\r
+                                               if(this.tagName == 'INPUT' && this.multiDatesPicker.changed) {\r
+                                                       $(inst.dpDiv[0]).stop(false,true);\r
+                                                       setTimeout('$("#'+inst.id+'").datepicker("show")',50);\r
+                                               }\r
+                                               if(this.multiDatesPicker.originalOnClose) this.multiDatesPicker.originalOnClose.call(this, dateText, inst);\r
+                                       }\r
+                               };\r
+                               \r
+                               if(options) {\r
+                                       this.multiDatesPicker.originalBeforeShow = options.beforeShow;\r
+                                       this.multiDatesPicker.originalOnSelect = options.onSelect;\r
+                                       this.multiDatesPicker.originalBeforeShowDay = options.beforeShowDay;\r
+                                       this.multiDatesPicker.originalOnClose = options.onClose;\r
+                                       \r
+                                       $this.datepicker(options);\r
+                                       \r
+                                       this.multiDatesPicker.minDate = $.datepicker._determineDate(this, options.minDate, null);\r
+                                       this.multiDatesPicker.maxDate = $.datepicker._determineDate(this, options.maxDate, null);\r
+                                       \r
+                                       if(options.addDates) methods.addDates.call(this, options.addDates);\r
+                                       if(options.addDisabledDates)\r
+                                               methods.addDates.call(this, options.addDisabledDates, 'disabled');\r
+                                       \r
+                                       methods.setMode.call(this, options);\r
+                               } else {\r
+                                       $this.datepicker();\r
+                               }\r
+                               \r
+                               $this.datepicker('option', mdp_events);\r
+                               \r
+                               if(this.tagName == 'INPUT') $this.val($this.multiDatesPicker('getDates', 'string'));\r
+                               \r
+                               // Fixes the altField filled with defaultDate by default\r
+                               var altFieldOption = $this.datepicker('option', 'altField');\r
+                               if (altFieldOption) $(altFieldOption).val($this.multiDatesPicker('getDates', 'string'));\r
+                       },\r
+                       compareDates : function(date1, date2) {\r
+                               date1 = dateConvert.call(this, date1);\r
+                               date2 = dateConvert.call(this, date2);\r
+                               // return > 0 means date1 is later than date2 \r
+                               // return == 0 means date1 is the same day as date2 \r
+                               // return < 0 means date1 is earlier than date2 \r
+                               var diff = date1.getFullYear() - date2.getFullYear();\r
+                               if(!diff) {\r
+                                       diff = date1.getMonth() - date2.getMonth();\r
+                                       if(!diff) \r
+                                               diff = date1.getDate() - date2.getDate();\r
+                               }\r
+                               return diff;\r
+                       },\r
+                       sumDays : function( date, n_days ) {\r
+                               var origDateType = typeof date;\r
+                               obj_date = dateConvert.call(this, date);\r
+                               obj_date.setDate(obj_date.getDate() + n_days);\r
+                               return dateConvert.call(this, obj_date, origDateType);\r
+                       },\r
+                       dateConvert : function( date, desired_format, dateFormat ) {\r
+                               var from_format = typeof date;\r
+                               \r
+                               if(from_format == desired_format) {\r
+                                       if(from_format == 'object') {\r
+                                               try {\r
+                                                       date.getTime();\r
+                                               } catch (e) {\r
+                                                       $.error('Received date is in a non supported format!');\r
+                                                       return false;\r
+                                               }\r
+                                       }\r
+                                       return date;\r
+                               }\r
+                               \r
+                               var $this = $(this);\r
+                               if(typeof date == 'undefined') date = new Date(0);\r
+                               \r
+                               if(desired_format != 'string' && desired_format != 'object' && desired_format != 'number')\r
+                                       $.error('Date format "'+ desired_format +'" not supported!');\r
+                               \r
+                               if(!dateFormat) {\r
+                                       dateFormat = $.datepicker._defaults.dateFormat;\r
+                                       \r
+                                       // thanks to bibendus83 -> http://sourceforge.net/tracker/index.php?func=detail&aid=3213174&group_id=358205&atid=1495382\r
+                                       var dp_dateFormat = $this.datepicker('option', 'dateFormat');\r
+                                       if (dp_dateFormat) {\r
+                                               dateFormat = dp_dateFormat;\r
+                                       }\r
+                               }\r
+                               \r
+                               // converts to object as a neutral format\r
+                               switch(from_format) {\r
+                                       case 'object': break;\r
+                                       case 'string': date = $.datepicker.parseDate(dateFormat, date); break;\r
+                                       case 'number': date = new Date(date); break;\r
+                                       default: $.error('Conversion from "'+ desired_format +'" format not allowed on jQuery.multiDatesPicker');\r
+                               }\r
+                               // then converts to the desired format\r
+                               switch(desired_format) {\r
+                                       case 'object': return date;\r
+                                       case 'string': return $.datepicker.formatDate(dateFormat, date);\r
+                                       case 'number': return date.getTime();\r
+                                       default: $.error('Conversion to "'+ desired_format +'" format not allowed on jQuery.multiDatesPicker');\r
+                               }\r
+                               return false;\r
+                       },\r
+                       gotDate : function( date, type ) {\r
+                               if(!type) type = 'picked';\r
+                               for(var i = 0; i < this.multiDatesPicker.dates[type].length; i++) {\r
+                                       if(methods.compareDates.call(this, this.multiDatesPicker.dates[type][i], date) === 0) {\r
+                                               return i;\r
+                                       }\r
+                               }\r
+                               return false;\r
+                       },\r
+                       getDates : function( format, type ) {\r
+                               if(!format) format = 'string';\r
+                               if(!type) type = 'picked';\r
+                               switch (format) {\r
+                                       case 'object':\r
+                                               return this.multiDatesPicker.dates[type];\r
+                                       case 'string':\r
+                                       case 'number':\r
+                                               var o_dates = new Array();\r
+                                               for(var i in this.multiDatesPicker.dates[type])\r
+                                                       o_dates.push(\r
+                                                               dateConvert.call(\r
+                                                                       this, \r
+                                                                       this.multiDatesPicker.dates[type][i], \r
+                                                                       format\r
+                                                               )\r
+                                                       );\r
+                                               return o_dates;\r
+                                       \r
+                                       default: $.error('Format "'+format+'" not supported!');\r
+                               }\r
+                       },\r
+                       addDates : function( dates, type ) {\r
+                               if(dates.length > 0) {\r
+                                       if(!type) type = 'picked';\r
+                                       switch(typeof dates) {\r
+                                               case 'object':\r
+                                               case 'array':\r
+                                                       if(dates.length) {\r
+                                                               for(var i in dates)\r
+                                                                       if (typeof dates[i] != "function")\r
+                                                                               addDate.call(this, dates[i], type, true);\r
+                                                               sortDates.call(this, type);\r
+                                                               break;\r
+                                                       } // else does the same as 'string'\r
+                                               case 'string':\r
+                                               case 'number':\r
+                                                       addDate.call(this, dates, type);\r
+                                                       break;\r
+                                               default: \r
+                                                       $.error('Date format "'+ typeof dates +'" not allowed on jQuery.multiDatesPicker');\r
+                                       }\r
+                                       $(this).datepicker('refresh');\r
+                               } else {\r
+                                       $.error('Empty array of dates received.');\r
+                               }\r
+                       },\r
+                       removeDates : function( dates, type ) {\r
+                               if(!type) type = 'picked';\r
+                               var removed = [];\r
+                               if (Object.prototype.toString.call(dates) === '[object Array]') {\r
+                                       for(var i in dates.sort(function(a,b){return b-a})) {\r
+                                               removed.push(removeDate.call(this, dates[i], type));\r
+                                       }\r
+                               } else {\r
+                                       removed.push(removeDate.call(this, dates, type));\r
+                               }\r
+                               $(this).datepicker('refresh');\r
+                               return removed;\r
+                       },\r
+                       removeIndexes : function( indexes, type ) {\r
+                               if(!type) type = 'picked';\r
+                               var removed = [];\r
+                               if (Object.prototype.toString.call(indexes) === '[object Array]') {\r
+                                       for(var i in indexes.sort(function(a,b){return b-a})) {\r
+                                               removed.push(removeIndex.call(this, indexes[i], type));\r
+                                       }\r
+                               } else {\r
+                                       removed.push(removeIndex.call(this, indexes, type));\r
+                               }\r
+                               $(this).datepicker('refresh');\r
+                               return removed;\r
+                       },\r
+                       resetDates : function ( type ) {\r
+                               if(!type) type = 'picked';\r
+                               this.multiDatesPicker.dates[type] = [];\r
+                               $(this).datepicker('refresh');\r
+                       },\r
+                       toggleDate : function( date, type ) {\r
+                               if(!type) type = 'picked';\r
+                               \r
+                               switch(this.multiDatesPicker.mode) {\r
+                                       case 'daysRange':\r
+                                               this.multiDatesPicker.dates[type] = []; // deletes all picked/disabled dates\r
+                                               var end = this.multiDatesPicker.autoselectRange[1];\r
+                                               var begin = this.multiDatesPicker.autoselectRange[0];\r
+                                               if(end < begin) { // switch\r
+                                                       end = this.multiDatesPicker.autoselectRange[0];\r
+                                                       begin = this.multiDatesPicker.autoselectRange[1];\r
+                                               }\r
+                                               for(var i = begin; i < end; i++) \r
+                                                       methods.addDates.call(this, methods.sumDays(date, i), type);\r
+                                               break;\r
+                                       default:\r
+                                               if(methods.gotDate.call(this, date) === false) // adds dates\r
+                                                       methods.addDates.call(this, date, type);\r
+                                               else // removes dates\r
+                                                       methods.removeDates.call(this, date, type);\r
+                                               break;\r
+                               }\r
+                       }, \r
+                       setMode : function( options ) {\r
+                               var $this = $(this);\r
+                               if(options.mode) this.multiDatesPicker.mode = options.mode;\r
+                               \r
+                               switch(this.multiDatesPicker.mode) {\r
+                                       case 'normal':\r
+                                               for(option in options)\r
+                                                       switch(option) {\r
+                                                               case 'maxPicks':\r
+                                                               case 'minPicks':\r
+                                                               case 'pickableRange':\r
+                                                               case 'adjustRangeToDisabled':\r
+                                                                       this.multiDatesPicker[option] = options[option];\r
+                                                                       break;\r
+                                                               //default: $.error('Option ' + option + ' ignored for mode "'.options.mode.'".');\r
+                                                       }\r
+                                       break;\r
+                                       case 'daysRange':\r
+                                       case 'weeksRange':\r
+                                               var mandatory = 1;\r
+                                               for(option in options)\r
+                                                       switch(option) {\r
+                                                               case 'autoselectRange':\r
+                                                                       mandatory--;\r
+                                                               case 'pickableRange':\r
+                                                               case 'adjustRangeToDisabled':\r
+                                                                       this.multiDatesPicker[option] = options[option];\r
+                                                                       break;\r
+                                                               //default: $.error('Option ' + option + ' does not exist for setMode on jQuery.multiDatesPicker');\r
+                                                       }\r
+                                               if(mandatory > 0) $.error('Some mandatory options not specified!');\r
+                                       break;\r
+                               }\r
+                               \r
+                               /*\r
+                               if(options.pickableRange) {\r
+                                       $this.datepicker("option", "maxDate", options.pickableRange);\r
+                                       $this.datepicker("option", "minDate", this.multiDatesPicker.minDate);\r
+                               }\r
+                               */\r
+                               \r
+                               if(mdp_events.onSelect)\r
+                                       mdp_events.onSelect();\r
+                               $this.datepicker('refresh');\r
+                       }\r
+               };\r
+               \r
+               this.each(function() {\r
+                       if (!this.multiDatesPicker) {\r
+                               this.multiDatesPicker = {\r
+                                       dates: {\r
+                                               picked: [],\r
+                                               disabled: []\r
+                                       },\r
+                                       mode: 'normal',\r
+                                       adjustRangeToDisabled: true\r
+                               };\r
+                       }\r
+                       \r
+                       if(methods[method]) {\r
+                               var exec_result = methods[method].apply(this, Array.prototype.slice.call(mdp_arguments, 1));\r
+                               switch(method) {\r
+                                       case 'getDates':\r
+                                       case 'removeDates':\r
+                                       case 'gotDate':\r
+                                       case 'sumDays':\r
+                                       case 'compareDates':\r
+                                       case 'dateConvert':\r
+                                               ret = exec_result;\r
+                               }\r
+                               return exec_result;\r
+                       } else if( typeof method === 'object' || ! method ) {\r
+                               return methods.init.apply(this, mdp_arguments);\r
+                       } else {\r
+                               $.error('Method ' +  method + ' does not exist on jQuery.multiDatesPicker');\r
+                       }\r
+                       return false;\r
+               });\r
+               \r
+               if(method != 'gotDate' && method != 'getDates') {\r
+                       aaaa = 1;\r
+               }\r
+               \r
+               return ret;\r
+       };\r
+\r
+       var PROP_NAME = 'multiDatesPicker';\r
+       var dpuuid = new Date().getTime();\r
+       var instActive;\r
+\r
+       $.multiDatesPicker = {version: false};\r
+       //$.multiDatesPicker = new MultiDatesPicker(); // singleton instance\r
+       $.multiDatesPicker.initialized = false;\r
+       $.multiDatesPicker.uuid = new Date().getTime();\r
+       $.multiDatesPicker.version = $.ui.multiDatesPicker.version;\r
+\r
+       // Workaround for #4055\r
+       // Add another global to avoid noConflict issues with inline event handlers\r
+       window['DP_jQuery_' + dpuuid] = $;\r
+})( jQuery );
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/lang/agenda.xml b/www/plugins/agenda_3_5/lang/agenda.xml
new file mode 100644 (file)
index 0000000..72dfd45
--- /dev/null
@@ -0,0 +1,21 @@
+<traduction module="agenda" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/agenda/trunk/lang/" reference="fr">
+       <langue code="de" url="http://trad.spip.net/tradlang_module/agenda?lang_cible=de" total="123" traduits="90" relire="0" modifs="5" nouveaux="28" pourcent="73.17">
+               <traducteur nom="Rainer Müller" lien="http://trad.spip.net/auteur/rainer-muller" />
+       </langue>
+       <langue code="en" url="http://trad.spip.net/tradlang_module/agenda?lang_cible=en" total="123" traduits="123" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
+               <traducteur nom="kent1" lien="http://trad.spip.net/auteur/kent1" />
+       </langue>
+       <langue code="es" url="http://trad.spip.net/tradlang_module/agenda?lang_cible=es" total="123" traduits="123" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Raquel S. Bujaldón" lien="http://trad.spip.net/auteur/raquel-s-bujaldon" />
+               <traducteur nom="severo" lien="http://trad.spip.net/auteur/severo" />
+       </langue>
+       <langue code="fr" url="http://trad.spip.net/tradlang_module/agenda?lang_cible=fr" total="123" traduits="123" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       </langue>
+       <langue code="nl" url="http://trad.spip.net/tradlang_module/agenda?lang_cible=nl" total="123" traduits="123" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
+       </langue>
+       <langue code="sk" url="http://trad.spip.net/tradlang_module/agenda?lang_cible=sk" total="123" traduits="123" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="jaro" lien="http://trad.spip.net/auteur/jaro" />
+       </langue>
+</traduction>
diff --git a/www/plugins/agenda_3_5/lang/agenda_de.php b/www/plugins/agenda_3_5/lang/agenda_de.php
new file mode 100644 (file)
index 0000000..d5ba3d4
--- /dev/null
@@ -0,0 +1,136 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/agenda?lang_cible=de
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'absence_prise_en_compte' => 'Ihre Abwesenheit wurde erfasst',
+       'activite_editoriale' => 'Redaktionelle Aktivität',
+       'afficher_calendrier' => 'Kalender anzeigen',
+       'agenda' => 'Kalender',
+       'ajouter_repetition' => 'Wiederholungen hinzufügen',
+       'ajouter_un_evenement' => 'diesem Artikel ein Event hinzufügen',
+       'annee_precedente' => 'Jahr davor', # MODIF
+       'annee_suivante' => 'Jahr danach',
+       'aucun_evenement' => 'Kein Event',
+       'aucune_rubrique_mode_agenda' => 'In der Grundeinstellung können Events in alle Rubriken eingetragen werden. Wenn sie den Kalender gezielt für eine oder mehrere Rubriken aktivieren, dann steht ihnen der Kalender nur für diese Rubriken zur Verfügung.',
+
+       // B
+       'bouton_annuler' => 'Abbrechen',
+       'bouton_supprimer' => 'Löschen',
+
+       // C
+       'confirm_suppression_inscription' => 'Wollen sie diese Anmeldung wirklich löschen?',
+       'creer_evenement' => 'Event neu anlegen',
+
+       // E
+       'erreur_article_interdit' => 'Sie dürfen dieses Event nicht diesem Artikel zuordnen',
+       'erreur_article_manquant' => 'Sie müssen einen Artikel angeben',
+       'erreur_date' => 'Falsches Datum',
+       'erreur_date_avant_apres' => 'Das Enddatum muß nach dem Anfangsdatum liegen!',
+       'erreur_date_corrigee' => 'Das Datum wurde korrigiert',
+       'erreur_heure' => 'Falsche Uhrzeit',
+       'erreur_heure_corrigee' => 'Die Uhrzeit wurde korrigiert',
+       'evenement_adresse' => 'Adresse',
+       'evenement_article' => 'Verbunden mit Artikel',
+       'evenement_autres_occurences' => 'Weitere Zuordnungen:',
+       'evenement_date' => 'Datum',
+       'evenement_date_a' => 'bis ',
+       'evenement_date_a_immediat' => 'zu ',
+       'evenement_date_au' => 'bis ',
+       'evenement_date_de' => 'Von ',
+       'evenement_date_debut' => 'Anfangsdatum',
+       'evenement_date_du' => 'Vom ',
+       'evenement_date_fin' => 'Enddatum',
+       'evenement_descriptif' => 'Beschreibung',
+       'evenement_horaire' => 'ganztägig',
+       'evenement_lieu' => 'Ort',
+       'evenement_repetitions' => 'Wiederholungen',
+       'evenement_titre' => 'Titel',
+       'evenements' => 'Event',
+       'evenements_a_venir' => 'In der Zukunft',
+       'evenements_depuis_debut' => 'Alle',
+
+       // F
+       'fermer' => 'Schließen',
+
+       // I
+       'icone_creer_evenement' => 'Neue Veranstaltung anlegen',
+       'icone_modifier_evenement' => 'Event Bearbeiten',
+       'info_1_place' => '1 Platz',
+       'info_aucun_evenement' => 'Kein Event',
+       'info_evenement' => 'Event',
+       'info_evenement_poubelle' => 'Event gelöscht',
+       'info_evenement_propose' => 'Event vorgeschlagen',
+       'info_evenement_publie' => 'Event veröffentlicht',
+       'info_evenements' => 'Event',
+       'info_lieu' => 'Ort:',
+       'info_nb_places' => '@nb@ Plätze',
+       'info_nombre_evenements' => '@nb@ Events',
+       'info_nouvel_evenement' => 'Neues Event',
+       'info_reponse_inscription_non' => 'nein',
+       'info_reponse_inscription_nsp' => '?',
+       'info_reponse_inscription_oui' => 'ja',
+       'info_reponses_inscriptions' => 'Antworten',
+       'info_un_evenement' => 'ein Event',
+       'inscrits' => 'Anmeldungen',
+
+       // L
+       'label_inscription' => 'Online-Anmeldungen',
+       'label_places' => 'Maximale Anzahl Plätze',
+       'label_reponse_jyparticipe' => 'Ich komme',
+       'label_reponse_jyparticipe_pas' => 'Ich komme nicht', # MODIF
+       'label_reponse_jyparticipe_peutetre' => 'Ich komem vielleicht',
+       'label_vous_inscrire' => 'Ihre Teilnahme',
+       'lien_desinscrire' => 'Entfernen',
+       'lien_retirer_evenement' => 'Löschen',
+       'liste_inscrits' => 'Liste der Anmeldungen',
+
+       // M
+       'mois_precedent' => 'voriger Monat',
+       'mois_suivant' => 'nächster Monat',
+
+       // N
+       'nb_repetitions' => '@nb@ Wiederholungen',
+
+       // P
+       'participation_incertaine_prise_en_compte' => 'Ihre vorläufige Anmeldung wurde gespeichert',
+       'participation_prise_en_compte' => 'Ihre Anmeldung wurde gespeichert',
+       'probleme_technique' => 'Ein technisches Problem ist aufgetreten. Bitte versuchen sie es später noch einmal.',
+
+       // R
+       'repetition' => 'Wiederholung',
+       'repetition_de' => 'Wiederholung von',
+       'rubrique_activer_agenda' => 'Kalender für diese Rubrik aktivieren',
+       'rubrique_dans_une_rubrique_mode_agenda' => 'Diese Rubrik kann den Kalender nutzen, denn sie befindet sich innerhalb einer Rubrik, für die den Kalende nutzen darf.',
+       'rubrique_desactiver_agenda' => 'Kalender in dieser Rubrik deaktivieren',
+       'rubrique_liste_evenements_de' => 'Events der Rubrik',
+       'rubrique_mode_agenda' => 'Der Kalender wurde für diese Rubrik und ihre Artikel aktiviert. ',
+       'rubrique_sans_gestion_evenement' => 'Der Kalender ist für diese Rubrik noch nicht aktiviert. ',
+       'rubriques' => 'Kalender-Rubriken',
+
+       // S
+       'sans_titre' => '(ohne Titel)',
+
+       // T
+       'telecharger' => 'Herunterladen', # MODIF
+       'texte_agenda' => 'KALENDER', # MODIF
+       'texte_evenement_statut' => 'Dieses Event ist',
+       'texte_logo_objet' => 'EVENT-LOGO', # MODIF
+       'titre_cadre_ajouter_evenement' => 'Event hinzufügen',
+       'titre_cadre_modifier_evenement' => 'Event umändern',
+       'titre_sur_l_agenda' => 'Im Kalender',
+       'toutes_rubriques' => 'Alle',
+
+       // U
+       'une_repetition' => '1 Wiederholung',
+
+       // V
+       'voir_evenements_rubrique' => 'Die Events der Rubrik einsehen'
+);
+
+?>
diff --git a/www/plugins/agenda_3_5/lang/agenda_en.php b/www/plugins/agenda_3_5/lang/agenda_en.php
new file mode 100644 (file)
index 0000000..747b657
--- /dev/null
@@ -0,0 +1,166 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/agenda?lang_cible=en
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'absence_prise_en_compte' => 'Your absence is recorded',
+       'activite_editoriale' => 'Editorial activity',
+       'afficher_calendrier' => 'Show the calendar',
+       'agenda' => 'Agenda',
+       'ajouter_repetition' => 'Add repetitions',
+       'ajouter_un_evenement' => 'Add one event to this article',
+       'annee_precedente' => 'previous year',
+       'annee_suivante' => 'next year',
+       'aucun_evenement' => 'no event',
+       'aucun_inscrit' => 'No registered',
+       'aucune_rubrique_mode_agenda' => 'By default, all sections can use the events. If you activate the agenda mode on one or more sections, event management will be limited in its subtree.',
+
+       // B
+       'bouton_annuler' => 'Cancel',
+       'bouton_supprimer' => 'Delete',
+
+       // C
+       'cal_par_jour' => 'day',
+       'cal_par_mois' => 'month',
+       'cal_par_semaine' => 'week',
+       'confirm_suppression_inscription' => 'Are you sure you want to delete this registration?',
+       'confirm_suppression_inscription_toutes' => 'Do you really want to delete all registrations?',
+       'connexion_necessaire_pour_inscription' => 'Please log in to register to this event.',
+       'creer_evenement' => 'Create an event',
+
+       // D
+       'date_fmt_agenda_label' => '<b class="month">@mois@</b> <b class="day">@jour@</b> <b class="year">@annee@</b>',
+
+       // E
+       'erreur_article_interdit' => 'You have no right to associate this event to this article',
+       'erreur_article_manquant' => 'You should link to an article',
+       'erreur_date' => 'This date is incorrect',
+       'erreur_date_avant_apres' => 'Please enter an end date after the date of beginning.',
+       'erreur_date_corrigee' => 'The date has been corrected',
+       'erreur_heure' => 'This hour is incorrect',
+       'erreur_heure_corrigee' => 'The hour has been corrected',
+       'evenement_adresse' => 'Address',
+       'evenement_article' => 'Link to the article',
+       'evenement_autres_occurences' => 'Other occurences :',
+       'evenement_date' => 'Date',
+       'evenement_date_a' => 'to ',
+       'evenement_date_a_immediat' => 'at ',
+       'evenement_date_au' => 'To ',
+       'evenement_date_de' => 'From ',
+       'evenement_date_debut' => 'Starting date',
+       'evenement_date_du' => 'From ',
+       'evenement_date_fin' => 'Ending date',
+       'evenement_date_inscription' => 'Registration date',
+       'evenement_descriptif' => 'Description',
+       'evenement_horaire' => 'All day',
+       'evenement_lieu' => 'Location',
+       'evenement_participant_email_mention' => 'To stay in contact, you should submit your email address. It will not be communicated on the site.',
+       'evenement_repetitions' => 'Repetition',
+       'evenement_titre' => 'Title',
+       'evenements' => 'Events',
+       'evenements_a_venir' => 'Next',
+       'evenements_depuis_debut' => 'All',
+       'explication_synchro_flux_ical' => 'Plugin Agenda supplies a flow of events in iCal format. Certain clients do not update an event unless a version number (also indicating any modification) is present in the flow. In order to have this number included in the generated iCal flow,  you can activate revisions for events (Configuration menu > Revisions).',
+       'explication_synchro_flux_ical_titre' => 'Synchronisation of iCal flow',
+
+       // F
+       'fermer' => 'close',
+
+       // I
+       'icone_creer_evenement' => 'Generate a new event',
+       'icone_modifier_evenement' => 'Edit the event',
+       'indiquez_votre_choix' => 'Indicate your choice',
+       'info_1_mois' => '1 month',
+       'info_1_place' => '1 seat',
+       'info_aucun_evenement' => 'No event',
+       'info_evenement' => 'Event',
+       'info_evenement_poubelle' => 'Event deleted',
+       'info_evenement_propose' => 'Event proposed',
+       'info_evenement_publie' => 'Event published',
+       'info_evenements' => 'Events',
+       'info_inscription' => 'Online registration:',
+       'info_lieu' => 'Place:',
+       'info_nb_inscrits' => '@nb@ registered',
+       'info_nb_mois' => '@nb@ months',
+       'info_nb_places' => '@nb@ seats',
+       'info_nb_reponses' => '@nb@ replies',
+       'info_nombre_evenements' => '@nb@ events',
+       'info_nouvel_evenement' => 'New event',
+       'info_reponse_inscription_non' => 'no',
+       'info_reponse_inscription_nsp' => '?',
+       'info_reponse_inscription_oui' => 'yes',
+       'info_reponse_inscriptions' => 'Answer',
+       'info_reponses_inscriptions' => 'Answers:',
+       'info_un_evenement' => 'One event',
+       'info_un_inscrit' => 'One registered',
+       'info_une_reponse' => 'One reply',
+       'inscrits' => 'Registrations',
+
+       // L
+       'label_annee' => 'Year',
+       'label_inscription' => 'Online registration',
+       'label_periode_saison' => 'Season',
+       'label_places' => 'Limit the seats number',
+       'label_reponse_jyparticipe' => 'I’ll be there',
+       'label_reponse_jyparticipe_pas' => 'I won’t be there',
+       'label_reponse_jyparticipe_peutetre' => 'Maybe I’ll be there',
+       'label_vous_inscrire' => 'Your participation',
+       'lien_desinscrire' => 'Remove',
+       'lien_desinscrire_tous' => 'Delete all registrations',
+       'lien_retirer_evenement' => 'Deleted ',
+       'liste_inscrits' => 'Registrations',
+
+       // M
+       'mois_precedent' => 'previous month',
+       'mois_suivant' => 'next month',
+
+       // N
+       'nb_repetitions' => '@nb@ repetitions',
+
+       // P
+       'participation_incertaine_prise_en_compte' => 'Your possible participation is registered',
+       'participation_prise_en_compte' => 'Your participation is recorded',
+       'probleme_technique' => 'A technical problem occurred. Try again later.',
+
+       // R
+       'repetition' => 'Repetition',
+       'repetition_de' => 'Repetition of',
+       'retour_evenement' => 'Back to the event',
+       'rubrique_activer_agenda' => 'Activate the agenda for this section',
+       'rubrique_dans_une_rubrique_mode_agenda' => 'This section allows you to use the events as it is in a section where agenda mode has been enabled',
+       'rubrique_desactiver_agenda' => 'Disable agenda mode for this section',
+       'rubrique_liste_evenements_de' => 'Events of the section',
+       'rubrique_mode_agenda' => 'The agenda mode is enabled for this section and its subtree',
+       'rubrique_sans_gestion_evenement' => 'The agenda mode is not enabled for this section',
+       'rubriques' => 'Agenda sections',
+
+       // S
+       'sans_titre' => '(without title)',
+
+       // T
+       'telecharger' => 'Download (csv)',
+       'telecharger_oui' => 'Only positive answers',
+       'telecharger_toutes' => 'All answers',
+       'telecharger_toutes_tous_evenements' => 'All answers to registrations',
+       'texte_agenda' => 'Agenda',
+       'texte_evenement_statut' => 'This event is:',
+       'texte_logo_objet' => 'Event’s logo',
+       'titre_cadre_ajouter_evenement' => 'Add one event',
+       'titre_cadre_modifier_evenement' => 'Modify one event',
+       'titre_sur_l_agenda' => 'On agenda',
+       'titre_sur_l_agenda_aussi' => 'And also...',
+       'toutes_rubriques' => 'All',
+
+       // U
+       'une_repetition' => '1 repetition',
+
+       // V
+       'voir_evenements_rubrique' => 'See this section’s events'
+);
+
+?>
diff --git a/www/plugins/agenda_3_5/lang/agenda_es.php b/www/plugins/agenda_3_5/lang/agenda_es.php
new file mode 100644 (file)
index 0000000..36d6360
--- /dev/null
@@ -0,0 +1,166 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/agenda?lang_cible=es
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'absence_prise_en_compte' => 'Su ausencia se ha registrado',
+       'activite_editoriale' => 'Actividad Editorial',
+       'afficher_calendrier' => 'Mostrar el calendario',
+       'agenda' => 'Agenda',
+       'ajouter_repetition' => 'Añadir repeticiones',
+       'ajouter_un_evenement' => 'Añadir un evento a este artículo',
+       'annee_precedente' => 'Año anterior',
+       'annee_suivante' => 'Año siguiente',
+       'aucun_evenement' => 'Ningún evento',
+       'aucun_inscrit' => 'Ningún inscrito',
+       'aucune_rubrique_mode_agenda' => 'Por defecto, todas las secciones permiten utilizar los eventos. Si activa el modo agenda sobre una o más secciones, la gestión de enventos se limitará a su rama.',
+
+       // B
+       'bouton_annuler' => 'Cancelar',
+       'bouton_supprimer' => 'Eliminar',
+
+       // C
+       'cal_par_jour' => 'día',
+       'cal_par_mois' => 'mes',
+       'cal_par_semaine' => 'semana',
+       'confirm_suppression_inscription' => '¿Desea realmente eliminar esta inscripción?',
+       'confirm_suppression_inscription_toutes' => '¿Desea realmente eliminar todas las inscripciones?',
+       'connexion_necessaire_pour_inscription' => 'Gracias por conectarse para poder inscribirse a este evento.',
+       'creer_evenement' => 'Crear un evento',
+
+       // D
+       'date_fmt_agenda_label' => '<b class="day">@jour@</b> <b class="month">@mois@</b> <b class="year">@annee@</b>',
+
+       // E
+       'erreur_article_interdit' => 'No tiene permiso para asociar este evento a este artículo',
+       'erreur_article_manquant' => 'Debe indicar un artículo',
+       'erreur_date' => 'Esta fecha es incorrecta',
+       'erreur_date_avant_apres' => '¡Indique una fecha de finalización posterior a la fecha de inicio!',
+       'erreur_date_corrigee' => 'La fecha ha sido corregida',
+       'erreur_heure' => 'Esta hora es incorrecta',
+       'erreur_heure_corrigee' => 'La hora ha sido corregida',
+       'evenement_adresse' => 'Dirección',
+       'evenement_article' => 'Asociado al artículo',
+       'evenement_autres_occurences' => 'Otras ocasiones:',
+       'evenement_date' => 'Fecha',
+       'evenement_date_a' => 'a las ',
+       'evenement_date_a_immediat' => 'a las ',
+       'evenement_date_au' => 'Al ',
+       'evenement_date_de' => 'De ',
+       'evenement_date_debut' => 'Fecha de inicio',
+       'evenement_date_du' => 'Del ',
+       'evenement_date_fin' => 'Fecha de finalización',
+       'evenement_date_inscription' => 'Fecha de inscripción',
+       'evenement_descriptif' => 'Descripción',
+       'evenement_horaire' => 'Todo el día',
+       'evenement_lieu' => 'Lugar',
+       'evenement_participant_email_mention' => 'Para quedar en contact, indique su dirección mail. No sera publicada en el sitio.',
+       'evenement_repetitions' => 'Repeticiones',
+       'evenement_titre' => 'Título',
+       'evenements' => 'Eventos',
+       'evenements_a_venir' => 'Próximos',
+       'evenements_depuis_debut' => 'Desde el inicio',
+       'explication_synchro_flux_ical' => 'El plugin Agenda proporciona una fuente de eventos en formato iCal. Algunos clientes sólo actualizan un evento si un número de versión (indicando así que ha habido una actualización) está presente en esta fuente. Para que este número de versión se integre en la fuente iCal generado, debe activar el seguimiento de las revisiones para los eventos (menú Configuración > Revisiones).',
+       'explication_synchro_flux_ical_titre' => 'Sincronización de la fuente iCal',
+
+       // F
+       'fermer' => 'cerrar',
+
+       // I
+       'icone_creer_evenement' => 'Crear un nuevo evento',
+       'icone_modifier_evenement' => 'Modificar el evento',
+       'indiquez_votre_choix' => 'Indique su elección',
+       'info_1_mois' => '1 mes',
+       'info_1_place' => '1 plaza',
+       'info_aucun_evenement' => 'Ningún evento',
+       'info_evenement' => 'Evento',
+       'info_evenement_poubelle' => 'Evento eliminado',
+       'info_evenement_propose' => 'Evento propuesto',
+       'info_evenement_publie' => 'Evento publicado',
+       'info_evenements' => 'Eventos',
+       'info_inscription' => 'Inscripción en línea:',
+       'info_lieu' => 'Lugar:',
+       'info_nb_inscrits' => '@nb@ inscritos',
+       'info_nb_mois' => '@nb@ meses',
+       'info_nb_places' => '@nb@ plazas',
+       'info_nb_reponses' => '@nb@ respuestas',
+       'info_nombre_evenements' => '@nb@ eventos',
+       'info_nouvel_evenement' => 'Nuevo evento',
+       'info_reponse_inscription_non' => 'no',
+       'info_reponse_inscription_nsp' => '¿?',
+       'info_reponse_inscription_oui' => 'sí',
+       'info_reponse_inscriptions' => 'Respuesta',
+       'info_reponses_inscriptions' => 'Respuestas:',
+       'info_un_evenement' => '1 evento',
+       'info_un_inscrit' => 'Un inscrito',
+       'info_une_reponse' => 'Una respuesta',
+       'inscrits' => 'Inscripciones',
+
+       // L
+       'label_annee' => 'Año',
+       'label_inscription' => 'Inscripción en línea ',
+       'label_periode_saison' => 'Estación',
+       'label_places' => 'Limitar el número de lugares',
+       'label_reponse_jyparticipe' => 'Asistiré',
+       'label_reponse_jyparticipe_pas' => 'No asistiré',
+       'label_reponse_jyparticipe_peutetre' => 'Tal vez asista',
+       'label_vous_inscrire' => 'Su participación',
+       'lien_desinscrire' => 'Eliminar',
+       'lien_desinscrire_tous' => 'Eliminar todas las inscripciones',
+       'lien_retirer_evenement' => 'Eliminar',
+       'liste_inscrits' => 'Lista de inscripciones',
+
+       // M
+       'mois_precedent' => 'mes anterior',
+       'mois_suivant' => 'mes siguiente',
+
+       // N
+       'nb_repetitions' => '@nb@ repeticiones',
+
+       // P
+       'participation_incertaine_prise_en_compte' => 'Su eventual participación ha sido registrada',
+       'participation_prise_en_compte' => 'Su participación ha sido registrada',
+       'probleme_technique' => 'Ha habido un problema técnico. Por favor, inténtelo más tarde.',
+
+       // R
+       'repetition' => 'Repetición',
+       'repetition_de' => 'Repetición de',
+       'retour_evenement' => 'Volver al evento',
+       'rubrique_activer_agenda' => 'Activar el modo agenda',
+       'rubrique_dans_une_rubrique_mode_agenda' => 'Esta sección permite utilizar eventos porque está en una sección en la que el modo agenda ha sido activado',
+       'rubrique_desactiver_agenda' => 'Desactivar el modo agenda',
+       'rubrique_liste_evenements_de' => 'Eventos de la sección',
+       'rubrique_mode_agenda' => 'El modo agenda está activado para esta sección y su rama',
+       'rubrique_sans_gestion_evenement' => 'El modo agenda no está activado para esta sección',
+       'rubriques' => 'Secciones Agenda',
+
+       // S
+       'sans_titre' => '(sin título)',
+
+       // T
+       'telecharger' => 'Descargar',
+       'telecharger_oui' => 'Solamente respuestas positivas',
+       'telecharger_toutes' => 'Todas las respuestas',
+       'telecharger_toutes_tous_evenements' => 'Todas las respuestas a las inscripciones',
+       'texte_agenda' => 'Agenda',
+       'texte_evenement_statut' => 'Este evento es:',
+       'texte_logo_objet' => 'Logo del evento',
+       'titre_cadre_ajouter_evenement' => 'Añadir un evento',
+       'titre_cadre_modifier_evenement' => 'Modificar un evento',
+       'titre_sur_l_agenda' => 'En la agenda',
+       'titre_sur_l_agenda_aussi' => 'Y también...',
+       'toutes_rubriques' => 'Todas',
+
+       // U
+       'une_repetition' => '1 repetición',
+
+       // V
+       'voir_evenements_rubrique' => 'Ver los eventos de la sección'
+);
+
+?>
diff --git a/www/plugins/agenda_3_5/lang/agenda_fr.php b/www/plugins/agenda_3_5/lang/agenda_fr.php
new file mode 100644 (file)
index 0000000..8292a7d
--- /dev/null
@@ -0,0 +1,164 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// Fichier source, a modifier dans svn://zone.spip.org/spip-zone/_plugins_/agenda/trunk/lang/
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'absence_prise_en_compte' => 'Votre absence est enregistrée',
+       'activite_editoriale' => 'Activité Éditoriale',
+       'afficher_calendrier' => 'Afficher le calendrier',
+       'agenda' => 'Agenda',
+       'ajouter_repetition' => 'Ajouter des répétitions',
+       'ajouter_un_evenement' => 'ajouter un événement à cet article',
+       'annee_precedente' => 'année précédente',
+       'annee_suivante' => 'année suivante',
+       'aucun_evenement' => 'aucun événement',
+       'aucun_inscrit' => 'Aucun inscrit',
+       'aucune_rubrique_mode_agenda' => 'Par défaut, toutes les rubriques permettent d’utiliser les événements. Si vous activez le mode agenda sur une ou plusieurs rubriques, la gestion des événements sera limitée a sa branche.',
+
+       // B
+       'bouton_annuler' => 'Annuler',
+       'bouton_supprimer' => 'Supprimer',
+
+       // C
+       'cal_par_jour' => 'jour',
+       'cal_par_mois' => 'mois',
+       'cal_par_semaine' => 'semaine',
+       'confirm_suppression_inscription' => 'Voulez-vous vraiment supprimer cette inscription ?',
+       'confirm_suppression_inscription_toutes' => 'Voulez-vous vraiment supprimer toutes les inscriptions ?',
+       'connexion_necessaire_pour_inscription' => 'Merci de vous connecter pour pouvoir vous inscrire à cet événement.',
+       'creer_evenement' => 'Créer un événement',
+
+       // D
+       'date_fmt_agenda_label' => '<b class="day">@jour@</b> <b class="month">@mois@</b> <b class="year">@annee@</b>',
+
+       // E
+       'erreur_article_interdit' => 'Vous n’avez pas le droit d’associer cet événement à cet article',
+       'erreur_article_manquant' => 'Vous devez indiquer un article',
+       'erreur_date' => 'Cette date est incorrecte',
+       'erreur_date_avant_apres' => 'Indiquez une date de fin après la date de début !',
+       'erreur_date_corrigee' => 'La date a été corrigée',
+       'erreur_heure' => 'Cette heure est incorrecte',
+       'erreur_heure_corrigee' => 'L’heure a été corrigée',
+       'evenement_adresse' => 'Adresse',
+       'evenement_article' => 'Associé à l’article',
+       'evenement_autres_occurences' => 'Autres occurences :',
+       'evenement_date' => 'Date',
+       'evenement_date_a' => 'à ',
+       'evenement_date_a_immediat' => 'à ',
+       'evenement_date_au' => 'Au ',
+       'evenement_date_de' => 'De ',
+       'evenement_date_debut' => 'Date de début',
+       'evenement_date_du' => 'Du ',
+       'evenement_date_fin' => 'Date de fin',
+       'evenement_date_inscription' => 'Date d’inscription',
+       'evenement_descriptif' => 'Descriptif',
+       'evenement_horaire' => 'Toute la journée',
+       'evenement_lieu' => 'Lieu',
+       'evenement_participant_email_mention' => 'Pour rester en contact, vous pouvez indiquer votre adresse email. Elle ne sera pas communiquée sur le site.',
+       'evenement_repetitions' => 'Répétitions',
+       'evenement_titre' => 'Titre',
+       'evenements' => 'Événements',
+       'evenements_a_venir' => 'À venir',
+       'evenements_depuis_debut' => 'Depuis le début',
+       'explication_synchro_flux_ical' => 'Le plugin Agenda fournit un flux des évènements au format iCal. Certains clients ne mettent à jour un évènement que si un numéro de version (indiquant ansi qu’il y a eu modification) est présent dans ce flux. Pour que ce numéro de version soit intégré dans le flux iCal généré, vous devez activer le suivi des révisions pour les évènements (menu Configuration > Révisions).',
+       'explication_synchro_flux_ical_titre' => 'Synchronisation du flux iCal',
+
+       // F
+       'fermer' => 'fermer',
+
+       // I
+       'icone_creer_evenement' => 'Créer un nouvel événement',
+       'icone_modifier_evenement' => 'Modifier l’événement',
+       'indiquez_votre_choix' => 'Indiquez votre choix',
+       'info_1_mois' => '1 mois',
+       'info_1_place' => '1 place',
+       'info_aucun_evenement' => 'Aucun événement',
+       'info_evenement' => 'Événement',
+       'info_evenement_poubelle' => 'Événement supprimé',
+       'info_evenement_propose' => 'Événement proposé',
+       'info_evenement_publie' => 'Événement publié',
+       'info_evenements' => 'Événements',
+       'info_inscription' => 'Inscription en ligne :',
+       'info_lieu' => 'Lieu :',
+       'info_nb_inscrits' => '@nb@ inscrits',
+       'info_nb_mois' => '@nb@ mois',
+       'info_nb_places' => '@nb@ places',
+       'info_nb_reponses' => '@nb@ réponses',
+       'info_nombre_evenements' => '@nb@ événements',
+       'info_nouvel_evenement' => 'Nouvel événement',
+       'info_reponse_inscription_non' => 'non',
+       'info_reponse_inscription_nsp' => ' ?',
+       'info_reponse_inscription_oui' => 'oui',
+       'info_reponse_inscriptions' => 'Réponse',
+       'info_reponses_inscriptions' => 'Réponses :',
+       'info_un_evenement' => '1 événement',
+       'info_un_inscrit' => 'Un inscrit',
+       'info_une_reponse' => 'Une réponse',
+       'inscrits' => 'Inscriptions',
+
+       // L
+       'label_annee' => 'Année',
+       'label_inscription' => 'Inscription en ligne',
+       'label_periode_saison' => 'Saison',
+       'label_places' => 'Limiter le nombre de places',
+       'label_reponse_jyparticipe' => 'J’y serai',
+       'label_reponse_jyparticipe_pas' => 'Je n’y serai pas',
+       'label_reponse_jyparticipe_peutetre' => 'J’y serai peut-être',
+       'label_vous_inscrire' => 'Votre participation',
+       'lien_desinscrire' => 'Supprimer',
+       'lien_desinscrire_tous' => 'Supprimer toutes les inscriptions',
+       'lien_retirer_evenement' => 'Supprimer',
+       'liste_inscrits' => 'Liste des inscriptions',
+
+       // M
+       'mois_precedent' => 'mois précédent',
+       'mois_suivant' => 'mois suivant',
+
+       // N
+       'nb_repetitions' => '@nb@ répétitions',
+
+       // P
+       'participation_incertaine_prise_en_compte' => 'Votre participation éventuelle est enregistrée',
+       'participation_prise_en_compte' => 'Votre participation est enregistrée',
+       'probleme_technique' => 'Un problème technique est survenu. Reessayez plus tard.',
+
+       // R
+       'repetition' => 'Répétition',
+       'repetition_de' => 'Répétition de',
+       'retour_evenement' => 'Retour à l’événement',
+       'rubrique_activer_agenda' => 'Activer le mode agenda',
+       'rubrique_dans_une_rubrique_mode_agenda' => 'Cette rubrique permet d’utiliser les événements car elle est dans une rubrique dont le mode agenda a été activé',
+       'rubrique_desactiver_agenda' => 'Désactiver le mode agenda',
+       'rubrique_liste_evenements_de' => 'Événements de la rubrique',
+       'rubrique_mode_agenda' => 'Le mode agenda est activé pour cette rubrique et sa branche',
+       'rubrique_sans_gestion_evenement' => 'Le mode agenda n’est pas activé pour cette rubrique',
+       'rubriques' => 'Rubriques Agenda',
+
+       // S
+       'sans_titre' => '(sans titre)',
+
+       // T
+       'telecharger' => 'Télécharger (csv)',
+       'telecharger_oui' => 'Seulement les réponses positives',
+       'telecharger_toutes' => 'Toutes les réponses',
+       'telecharger_toutes_tous_evenements' => 'Toutes les réponses aux inscriptions',
+       'texte_agenda' => 'Agenda',
+       'texte_evenement_statut' => 'Cet événement est :',
+       'texte_logo_objet' => 'Logo de l’événement',
+       'titre_cadre_ajouter_evenement' => 'Ajouter un événement',
+       'titre_cadre_modifier_evenement' => 'Modifier un événement',
+       'titre_sur_l_agenda' => 'Sur l’agenda',
+       'titre_sur_l_agenda_aussi' => 'Et aussi...',
+       'toutes_rubriques' => 'Toutes',
+
+       // U
+       'une_repetition' => '1 répétition',
+
+       // V
+       'voir_evenements_rubrique' => 'Voir les événements de la rubrique'
+);
+
+?>
diff --git a/www/plugins/agenda_3_5/lang/agenda_nl.php b/www/plugins/agenda_3_5/lang/agenda_nl.php
new file mode 100644 (file)
index 0000000..4c8b266
--- /dev/null
@@ -0,0 +1,166 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/agenda?lang_cible=nl
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'absence_prise_en_compte' => 'Je afwezigheid is genoteerd',
+       'activite_editoriale' => 'Aanpassingen inhoud',
+       'afficher_calendrier' => 'De kalender tonen',
+       'agenda' => 'Agenda',
+       'ajouter_repetition' => 'Herhalingen toevoegen',
+       'ajouter_un_evenement' => 'een gebeurtenis aan dit artikel toevoegen',
+       'annee_precedente' => 'vorig jaar',
+       'annee_suivante' => 'volgend jaar',
+       'aucun_evenement' => 'geen gebeurtenissen',
+       'aucun_inscrit' => 'Geen inschrijvingen',
+       'aucune_rubrique_mode_agenda' => 'Standaard kunnen aan elke rubriek gebeurtenissen worden toegevoegd.',
+
+       // B
+       'bouton_annuler' => 'Annuleren',
+       'bouton_supprimer' => 'Verwijderen',
+
+       // C
+       'cal_par_jour' => 'dag',
+       'cal_par_mois' => 'maand',
+       'cal_par_semaine' => 'week',
+       'confirm_suppression_inscription' => 'Wil je deze inschrijving werkelijk verwijderen?',
+       'confirm_suppression_inscription_toutes' => 'Wil je werkelijk alle inschrijvingen verwijderen?',
+       'connexion_necessaire_pour_inscription' => 'Je moet <a href=\'#LOGIN_PUBLIC\'>hier inloggen</a> om je te kunnen inschrijven.',
+       'creer_evenement' => 'Een gebeurtenis maken',
+
+       // D
+       'date_fmt_agenda_label' => '<b class="day">@jour@</b> <b class="month">@mois@</b> <b class="year">@annee@</b>',
+
+       // E
+       'erreur_article_interdit' => 'Je mag deze gebeurtenis niet aan dit artikel koppelen',
+       'erreur_article_manquant' => 'Je moet een artikel aangeven',
+       'erreur_date' => 'Deze datum is onjuist',
+       'erreur_date_avant_apres' => 'Kies een einddatum die na de begindatum ligt!',
+       'erreur_date_corrigee' => 'De datum is aangepast',
+       'erreur_heure' => 'Dit tijdstip is onjuist',
+       'erreur_heure_corrigee' => 'Het tijdstip is aangepast',
+       'evenement_adresse' => 'Adres',
+       'evenement_article' => 'Gekoppeld aan artikel',
+       'evenement_autres_occurences' => 'Andere tijdstippen:',
+       'evenement_date' => 'Datum',
+       'evenement_date_a' => '<br>tot ',
+       'evenement_date_a_immediat' => 'om ',
+       'evenement_date_au' => 'Tot ',
+       'evenement_date_de' => 'Van ',
+       'evenement_date_debut' => 'Begindatum',
+       'evenement_date_du' => 'Van ',
+       'evenement_date_fin' => 'Einddatum',
+       'evenement_date_inscription' => 'Inschrijfdatum',
+       'evenement_descriptif' => 'Omschrijving',
+       'evenement_horaire' => 'De hele dag',
+       'evenement_lieu' => 'Plaats',
+       'evenement_participant_email_mention' => 'Om in contact te blijven moet je je emailadres vermelden. Deze zal niet op de site worden vermeld.',
+       'evenement_repetitions' => 'Herhalingen',
+       'evenement_titre' => 'Titel',
+       'evenements' => 'Evenementen',
+       'evenements_a_venir' => 'Toekomstig',
+       'evenements_depuis_debut' => 'Alles',
+       'explication_synchro_flux_ical' => 'Plugin Agenda levert een stroom evenementen in iCal formaat. Sommige clients passen een evenement alleen aan bij een gewijzigd versienummer. Om dit nummer in de iCal flux te integreren, moet je de revisie optie voor evenementen inschakelen (menu Configuratie > Revisies).',
+       'explication_synchro_flux_ical_titre' => 'Synchronisatie van de iCal-flux',
+
+       // F
+       'fermer' => 'sluiten',
+
+       // I
+       'icone_creer_evenement' => 'Maak een nieuwe gebeurtenis',
+       'icone_modifier_evenement' => 'Evenement aanpassen',
+       'indiquez_votre_choix' => 'Maak een keuze',
+       'info_1_mois' => '1 maand',
+       'info_1_place' => '1 plaats',
+       'info_aucun_evenement' => 'Geen enkel evenement',
+       'info_evenement' => 'Evenement',
+       'info_evenement_poubelle' => 'Verwijderd evenement',
+       'info_evenement_propose' => 'Voorgesteld evenement',
+       'info_evenement_publie' => 'Gepubliceerd evenement',
+       'info_evenements' => 'Evenementen',
+       'info_inscription' => 'Online inschrijving:',
+       'info_lieu' => 'Plaats:',
+       'info_nb_inscrits' => '@nb@ inschrijvingen',
+       'info_nb_mois' => '@nb@ maanden',
+       'info_nb_places' => '@nb@ plaatsen',
+       'info_nb_reponses' => '@nb@ antwoorden',
+       'info_nombre_evenements' => '@nb@ gebeurtenissen',
+       'info_nouvel_evenement' => 'Nieuw evenement',
+       'info_reponse_inscription_non' => 'nee',
+       'info_reponse_inscription_nsp' => '?',
+       'info_reponse_inscription_oui' => 'ja',
+       'info_reponse_inscriptions' => 'Reactie',
+       'info_reponses_inscriptions' => 'Reacties:',
+       'info_un_evenement' => '1 gebeurtenis',
+       'info_un_inscrit' => '1 inschrijving',
+       'info_une_reponse' => 'Eén antwoord',
+       'inscrits' => 'Inschrijvingen',
+
+       // L
+       'label_annee' => 'Jaar',
+       'label_inscription' => 'Online inschrijven',
+       'label_periode_saison' => 'Seizoen',
+       'label_places' => 'Aantal plaatsen beperken',
+       'label_reponse_jyparticipe' => 'Ik kom zeker',
+       'label_reponse_jyparticipe_pas' => 'Ik kom niet',
+       'label_reponse_jyparticipe_peutetre' => 'Ik kom misschien',
+       'label_vous_inscrire' => 'Je inschrijving',
+       'lien_desinscrire' => 'Uitschrijven',
+       'lien_desinscrire_tous' => 'Alle inschrijvingen verwijderen',
+       'lien_retirer_evenement' => 'Verwijderen',
+       'liste_inscrits' => 'Lijst van inschrijvingen',
+
+       // M
+       'mois_precedent' => 'vorige maand',
+       'mois_suivant' => 'volgende maand',
+
+       // N
+       'nb_repetitions' => '@nb@ herhalingen',
+
+       // P
+       'participation_incertaine_prise_en_compte' => 'Je eventuele deelname is geregistreerd',
+       'participation_prise_en_compte' => 'Je deelname is geregistreerd',
+       'probleme_technique' => 'Er is een technisch probleem. Probeer het later nog eens.',
+
+       // R
+       'repetition' => 'Herhaling',
+       'repetition_de' => 'Herhaling van',
+       'retour_evenement' => 'Terug naar het evenement',
+       'rubrique_activer_agenda' => 'Activeer de agenda voor deze rubriek',
+       'rubrique_dans_une_rubrique_mode_agenda' => 'In deze rubriek kunnen gebeurtenissen worden gebruikt, want de agenda-functionaliteit is voor de hoofdrubriek geactiveerd',
+       'rubrique_desactiver_agenda' => 'Desactiveer de agenda voor deze rubriek',
+       'rubrique_liste_evenements_de' => 'Evenementen van de rubriek',
+       'rubrique_mode_agenda' => 'De agenda is voor deze rubriek en subrubrieken geactiveerd',
+       'rubrique_sans_gestion_evenement' => 'In deze rubriek kunnen geen gebeurtenissen worden gebruikt',
+       'rubriques' => 'Agenda Rubrieken',
+
+       // S
+       'sans_titre' => '(geen titel)',
+
+       // T
+       'telecharger' => 'Downloaden (csv)',
+       'telecharger_oui' => 'Alleen positieve reacties',
+       'telecharger_toutes' => 'Alle reacties',
+       'telecharger_toutes_tous_evenements' => 'Alle reacties op alle evenementen',
+       'texte_agenda' => 'AGENDA',
+       'texte_evenement_statut' => 'Dit evenement is:',
+       'texte_logo_objet' => 'Logo van het evenement',
+       'titre_cadre_ajouter_evenement' => 'Een gebeurtenis toevoegen',
+       'titre_cadre_modifier_evenement' => 'Een gebeurtenis aanpassen',
+       'titre_sur_l_agenda' => 'Binnenkort...',
+       'titre_sur_l_agenda_aussi' => 'En ook...',
+       'toutes_rubriques' => 'Alles',
+
+       // U
+       'une_repetition' => '1 herhaling',
+
+       // V
+       'voir_evenements_rubrique' => 'Bekijk de gebeurtenissen van de rubriek'
+);
+
+?>
diff --git a/www/plugins/agenda_3_5/lang/agenda_sk.php b/www/plugins/agenda_3_5/lang/agenda_sk.php
new file mode 100644 (file)
index 0000000..3c815b3
--- /dev/null
@@ -0,0 +1,166 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/agenda?lang_cible=sk
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'absence_prise_en_compte' => 'Vaša neúčasť bola zaznamenaná',
+       'activite_editoriale' => 'Redakčná činnosť',
+       'afficher_calendrier' => 'Zobraziť kalendár',
+       'agenda' => 'Kalendár udalostí',
+       'ajouter_repetition' => 'Pridať opakovania',
+       'ajouter_un_evenement' => 'pridať udalosť k tomuto článku',
+       'annee_precedente' => 'predchádzajúci rok',
+       'annee_suivante' => 'ďalší rok',
+       'aucun_evenement' => 'žiadna udalosť',
+       'aucun_inscrit' => 'Žiadne prihlásenie',
+       'aucune_rubrique_mode_agenda' => 'Podľa predvolených nastavení vám všetky rubriky umožňujú umožňujú využívať udalosti. Ak kalendár udalostí aktivujete pre jednu rubriku alebo viacero rubrík, riadenie udalostí bude obmedzené na vetvy týchto rubrík.',
+
+       // B
+       'bouton_annuler' => 'Zrušiť',
+       'bouton_supprimer' => 'Odstrániť',
+
+       // C
+       'cal_par_jour' => 'deň',
+       'cal_par_mois' => 'mesiac',
+       'cal_par_semaine' => 'týždeň',
+       'confirm_suppression_inscription' => 'Chcete odstrániť tento údaj?',
+       'confirm_suppression_inscription_toutes' => 'Naozaj chcete vymazať všetky prihlásenia?',
+       'connexion_necessaire_pour_inscription' => 'Ďakujeme vám, že sa prihlásite predtým, ako sa zaregistrujete na túto udalosť.',
+       'creer_evenement' => 'Vytvoriť udalosť',
+
+       // D
+       'date_fmt_agenda_label' => '<b class="day">@jour@</b> <b class="month">@mois@</b> <b class="year">@annee@</b>',
+
+       // E
+       'erreur_article_interdit' => 'Nemáte dostatočné práva na to, aby ste mohli priradiť túto udalosť k tomuto článku',
+       'erreur_article_manquant' => 'Musíte uviesť článok',
+       'erreur_date' => 'Tento dátum nie je správny',
+       'erreur_date_avant_apres' => 'Zadajte dátum ukončenia, ktorý nasleduje po dátume začiatku!',
+       'erreur_date_corrigee' => 'Dátum bol opravený',
+       'erreur_heure' => 'Tento čas nie je správny',
+       'erreur_heure_corrigee' => 'Čas bol opravený',
+       'evenement_adresse' => 'Adresa',
+       'evenement_article' => 'Priradená k článku',
+       'evenement_autres_occurences' => 'Ďalšie výskyty:',
+       'evenement_date' => 'Dátum',
+       'evenement_date_a' => 'k ',
+       'evenement_date_a_immediat' => 'o ',
+       'evenement_date_au' => 'Do ',
+       'evenement_date_de' => 'Z(o) ',
+       'evenement_date_debut' => 'Dátum začiatku',
+       'evenement_date_du' => 'Od ',
+       'evenement_date_fin' => 'Dátum ukončenia',
+       'evenement_date_inscription' => 'Dátum prihlásenia',
+       'evenement_descriptif' => 'Opis',
+       'evenement_horaire' => 'Celý deň',
+       'evenement_lieu' => 'Miesto',
+       'evenement_participant_email_mention' => 'Ak chcete mať prehľad o tom, čo sa deje, zadajte svoju e-mailovú adresu. Nebude zverejnená na stránke.',
+       'evenement_repetitions' => 'Opakovania',
+       'evenement_titre' => 'Titulok',
+       'evenements' => 'Udalosti',
+       'evenements_a_venir' => 'Budúce',
+       'evenements_depuis_debut' => 'od začiatku',
+       'explication_synchro_flux_ical' => 'Zásuvný modul Kalendár udalostí poskytuje výpis zoznamu udalostí vo formáte iCal. Niektoré programy aktualizujú udalosti, iba ak  je pri nich uvedené číslo verzie (a to, že nastala zmena). Na to, aby bolo toto číslo verzie zavedené do vygenerovaného zdroja iCal, treba aktivovať sledovanie zmien pre udalosti (menu Konfigurácia > Zmeny).',
+       'explication_synchro_flux_ical_titre' => 'Synchronizácia zdroja iCal',
+
+       // F
+       'fermer' => 'zatvoriť',
+
+       // I
+       'icone_creer_evenement' => 'Vytvoriť novú udalosť',
+       'icone_modifier_evenement' => 'Upraviť udalosť',
+       'indiquez_votre_choix' => 'Uveďte svoju voľbu',
+       'info_1_mois' => '1 mesiac',
+       'info_1_place' => '1 miesto',
+       'info_aucun_evenement' => 'Žiadna udalosť',
+       'info_evenement' => 'Udalosť',
+       'info_evenement_poubelle' => 'Odstránená udalosť',
+       'info_evenement_propose' => 'Navrhovaná udalosť',
+       'info_evenement_publie' => 'Uverejnená udalosť',
+       'info_evenements' => 'Udalosti',
+       'info_inscription' => 'Registrácia online:',
+       'info_lieu' => 'Miesto:',
+       'info_nb_inscrits' => '@nb@ prihlásení',
+       'info_nb_mois' => '@nb@ mesiac',
+       'info_nb_places' => '@nb@ miest',
+       'info_nb_reponses' => '@nb@ odpovedí',
+       'info_nombre_evenements' => '@nb@ udalostí',
+       'info_nouvel_evenement' => 'Nová udalosť',
+       'info_reponse_inscription_non' => 'nie',
+       'info_reponse_inscription_nsp' => '?',
+       'info_reponse_inscription_oui' => 'áno',
+       'info_reponse_inscriptions' => 'Reakcia',
+       'info_reponses_inscriptions' => 'Reakcie:',
+       'info_un_evenement' => '1 udalosť',
+       'info_un_inscrit' => 'Jedno prihlásenie',
+       'info_une_reponse' => 'Jedna odpoveď',
+       'inscrits' => 'Registrácie',
+
+       // L
+       'label_annee' => 'Rok',
+       'label_inscription' => 'Registrácia online',
+       'label_periode_saison' => 'Ročné obdobie',
+       'label_places' => 'Obmedziť počet miest',
+       'label_reponse_jyparticipe' => 'Budem tam',
+       'label_reponse_jyparticipe_pas' => 'Nebudem tam',
+       'label_reponse_jyparticipe_peutetre' => 'Možno prídem',
+       'label_vous_inscrire' => 'Vaša účasť',
+       'lien_desinscrire' => 'Odstrániť',
+       'lien_desinscrire_tous' => 'Vymazať všetky prihlásenia',
+       'lien_retirer_evenement' => 'Odstrániť',
+       'liste_inscrits' => 'Zoznam zaregistrovaných',
+
+       // M
+       'mois_precedent' => 'predchádzajúci mesiac',
+       'mois_suivant' => 'ďalší mesiac',
+
+       // N
+       'nb_repetitions' => '@nb@ opakovaní',
+
+       // P
+       'participation_incertaine_prise_en_compte' => 'Vaša prípadná účasť bola zaznamenaná',
+       'participation_prise_en_compte' => 'Vaša účasť bola zaznamenaná',
+       'probleme_technique' => 'Vyskytol sa technický problém. Skúste neskôr, prosím.',
+
+       // R
+       'repetition' => 'Opakovanie',
+       'repetition_de' => 'Opakovanie',
+       'retour_evenement' => 'Vrátiť sa na udalosť',
+       'rubrique_activer_agenda' => 'Aktivovať Kalendár udalostí pre túto rubriku',
+       'rubrique_dans_une_rubrique_mode_agenda' => 'Táto rubrika vám umožňuje využívať udalosti, keďže sa nachádza v rubrike, pre ktorú bol Kalendár udalostí aktivovaný',
+       'rubrique_desactiver_agenda' => 'Deaktivovať Kalendár udalostí pre túto rubriku',
+       'rubrique_liste_evenements_de' => 'Udalosti rubriky',
+       'rubrique_mode_agenda' => 'Režim Kalendár udalostí  bol aktivovaný pre túto rubriku a jej vetvu',
+       'rubrique_sans_gestion_evenement' => 'Režim Kalendár udalostí  nie je pre túto rubriku aktivovaný',
+       'rubriques' => 'Rubriky s kalendárom udalostí',
+
+       // S
+       'sans_titre' => '(bez titulku)',
+
+       // T
+       'telecharger' => 'Stiahnuť (csv)',
+       'telecharger_oui' => 'Iba pozitívne reakcie',
+       'telecharger_toutes' => 'Všetky reakcie',
+       'telecharger_toutes_tous_evenements' => 'Všetky reakcie cez prihlásenie',
+       'texte_agenda' => 'Kalendár udalostí',
+       'texte_evenement_statut' => 'Táto udalosť je:',
+       'texte_logo_objet' => 'Logo udalosti',
+       'titre_cadre_ajouter_evenement' => 'Pridať udalosť',
+       'titre_cadre_modifier_evenement' => 'Upraviť udalosť',
+       'titre_sur_l_agenda' => 'V Kalendári udalostí ',
+       'titre_sur_l_agenda_aussi' => 'A tiež...',
+       'toutes_rubriques' => 'Všetky',
+
+       // U
+       'une_repetition' => '1 opakovanie',
+
+       // V
+       'voir_evenements_rubrique' => 'Zobraziť udalosti rubriky'
+);
+
+?>
diff --git a/www/plugins/agenda_3_5/lang/configureragenda.xml b/www/plugins/agenda_3_5/lang/configureragenda.xml
new file mode 100644 (file)
index 0000000..33b685f
--- /dev/null
@@ -0,0 +1,4 @@
+<traduction module="configureragenda" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/agenda/trunk/lang/" reference="fr">
+       <langue code="fr" url="http://trad.spip.net/tradlang_module/configureragenda?lang_cible=fr" total="18" traduits="18" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       </langue>
+</traduction>
diff --git a/www/plugins/agenda_3_5/lang/configureragenda_fr.php b/www/plugins/agenda_3_5/lang/configureragenda_fr.php
new file mode 100644 (file)
index 0000000..b8d18be
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// Fichier source, a modifier dans svn://zone.spip.org/spip-zone/_plugins_/agenda/trunk/lang/
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // L
+       'label_affichage_debut' => 'Début de la liste',
+       'label_affichage_debut_date_jour' => 'Date du jour',
+       'label_affichage_debut_date_veille' => 'Date de la veille',
+       'label_affichage_debut_debut_mois' => 'Début du mois',
+       'label_affichage_debut_debut_mois_1' => 'Début d’année',
+       'label_affichage_debut_debut_mois_prec' => 'Début du mois précédent',
+       'label_affichage_debut_debut_semaine' => 'Début de la semaine',
+       'label_affichage_debut_debut_semaine_prec' => 'Début de la semaine précédente',
+       'label_affichage_debut_mois_passe' => '@mois@ précédent',
+       'label_affichage_duree' => 'Lister les événements sur',
+       'label_descriptif' => 'Descriptif',
+       'label_insert_head_css_1' => 'Insérer automatiquement les styles par défaut de l’agenda',
+       'label_titre' => 'Titre de la page',
+       'label_url_evenement' => 'Affichage d’un événement',
+       'label_url_evenement_article' => 'sur la page de l’article associé',
+       'label_url_evenement_evenement' => 'sur une page dédiée pour chaque événement',
+       'legend_presentation_agenda' => 'Présentation de l’agenda',
+
+       // T
+       'titre_configuration' => 'Affichage de l’Agenda'
+);
+
+?>
diff --git a/www/plugins/agenda_3_5/lang/migreragenda.xml b/www/plugins/agenda_3_5/lang/migreragenda.xml
new file mode 100644 (file)
index 0000000..efd7d32
--- /dev/null
@@ -0,0 +1,4 @@
+<traduction module="migreragenda" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/agenda/trunk/lang/" reference="fr">
+       <langue code="fr" url="http://trad.spip.net/tradlang_module/migreragenda?lang_cible=fr" total="19" traduits="19" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       </langue>
+</traduction>
diff --git a/www/plugins/agenda_3_5/lang/migreragenda_fr.php b/www/plugins/agenda_3_5/lang/migreragenda_fr.php
new file mode 100644 (file)
index 0000000..0c2283f
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// Fichier source, a modifier dans svn://zone.spip.org/spip-zone/_plugins_/agenda/trunk/lang/
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // B
+       'bouton_lancer_migration' => 'Lancer la migration',
+       'bouton_migrer' => 'Prévisualiser la migration',
+
+       // E
+       'erreur_choix_incorrect' => 'Ce choix n’est pas permis',
+       'explication_migration_agenda_article_1' => 'Si votre site contient un agenda basé sur des articles,
+vous pouvez utiliser cet outil pour le transformer automatiquement en événements.',
+       'explication_migration_agenda_article_2' => 'Dans la rubrique agenda sélectionnée, un événement sera créé et renseigné pour dater chaque article, selon les réglages ci-dessous.',
+       'explication_migration_agenda_article_fin' => 'Seuls les articles publiés et n’ayant pas déjà d’événement seront migrés.
+       Aucune donnée ne sera supprimée sur les articles : si le résultat ne vous convient pas, il suffit de désinstaller le plugin Agenda pour retrouver votre rubrique comme avant la migration.',
+
+       // I
+       'info_migration_articles' => 'Articles à migrer :',
+       'info_migration_articles_reussi' => 'Articles migrés :',
+
+       // L
+       'label_champ_date' => 'Date de publication',
+       'label_champ_date_debut' => 'Date de début',
+       'label_champ_date_fin' => 'Date de fin',
+       'label_champ_date_redac' => 'Date de rédaction antérieure',
+       'label_groupes_mots' => 'Associer les mots des groupes suivants',
+       'label_horaire' => 'Horaire',
+       'label_horaire_non' => 'Pas d’horaire (événements par journées)',
+       'label_horaire_oui' => 'Prendre en compte l’heure',
+       'label_rubrique_source' => 'Rubrique Agenda à migrer',
+       'label_toute_la_branche_oui' => 'Migrer aussi toutes les sous-rubriques',
+
+       // T
+       'titre_migrer_agenda' => 'Migrer un Agenda d’articles'
+);
+
+?>
diff --git a/www/plugins/agenda_3_5/lang/paquet-agenda.xml b/www/plugins/agenda_3_5/lang/paquet-agenda.xml
new file mode 100644 (file)
index 0000000..984c57c
--- /dev/null
@@ -0,0 +1,16 @@
+<traduction module="paquet-agenda" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/agenda/trunk/lang/" reference="fr">
+       <langue code="en" url="http://trad.spip.net/tradlang_module/paquet-agenda?lang_cible=en" total="3" traduits="3" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="kent1" lien="http://trad.spip.net/auteur/kent1" />
+       </langue>
+       <langue code="es" url="http://trad.spip.net/tradlang_module/paquet-agenda?lang_cible=es" total="3" traduits="3" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Raquel S. Bujaldón" lien="http://trad.spip.net/auteur/raquel-s-bujaldon" />
+       </langue>
+       <langue code="fr" url="http://trad.spip.net/tradlang_module/paquet-agenda?lang_cible=fr" total="3" traduits="3" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       </langue>
+       <langue code="nl" url="http://trad.spip.net/tradlang_module/paquet-agenda?lang_cible=nl" total="3" traduits="3" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
+       </langue>
+       <langue code="sk" url="http://trad.spip.net/tradlang_module/paquet-agenda?lang_cible=sk" total="3" traduits="3" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="jaro" lien="http://trad.spip.net/auteur/jaro" />
+       </langue>
+</traduction>
diff --git a/www/plugins/agenda_3_5/lang/paquet-agenda_en.php b/www/plugins/agenda_3_5/lang/paquet-agenda_en.php
new file mode 100644 (file)
index 0000000..d3e9409
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-agenda?lang_cible=en
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'agenda_description' => 'Events agenda',
+       'agenda_nom' => 'Agenda',
+       'agenda_slogan' => 'Events agenda'
+);
+
+?>
diff --git a/www/plugins/agenda_3_5/lang/paquet-agenda_es.php b/www/plugins/agenda_3_5/lang/paquet-agenda_es.php
new file mode 100644 (file)
index 0000000..340def3
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-agenda?lang_cible=es
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'agenda_description' => 'Agenda de Eventos',
+       'agenda_nom' => 'Agenda',
+       'agenda_slogan' => 'Agenda de Eventos'
+);
+
+?>
diff --git a/www/plugins/agenda_3_5/lang/paquet-agenda_fr.php b/www/plugins/agenda_3_5/lang/paquet-agenda_fr.php
new file mode 100644 (file)
index 0000000..f6fba01
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// Fichier source, a modifier dans svn://zone.spip.org/spip-zone/_plugins_/agenda/trunk/lang/
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'agenda_description' => 'Agenda Evénementiel',
+       'agenda_nom' => 'Agenda',
+       'agenda_slogan' => 'Agenda Evénementiel'
+);
+
+?>
diff --git a/www/plugins/agenda_3_5/lang/paquet-agenda_nl.php b/www/plugins/agenda_3_5/lang/paquet-agenda_nl.php
new file mode 100644 (file)
index 0000000..f21f2f3
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-agenda?lang_cible=nl
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'agenda_description' => 'Evenementenagenda',
+       'agenda_nom' => 'Agenda',
+       'agenda_slogan' => 'Evenementenagenda'
+);
+
+?>
diff --git a/www/plugins/agenda_3_5/lang/paquet-agenda_sk.php b/www/plugins/agenda_3_5/lang/paquet-agenda_sk.php
new file mode 100644 (file)
index 0000000..20fa3df
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-agenda?lang_cible=sk
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'agenda_description' => 'Kalendár udalostí',
+       'agenda_nom' => 'Diár',
+       'agenda_slogan' => 'Kalendár udalostí'
+);
+
+?>
diff --git a/www/plugins/agenda_3_5/lang/paquet-albums.xml b/www/plugins/agenda_3_5/lang/paquet-albums.xml
new file mode 100644 (file)
index 0000000..f2b1b69
--- /dev/null
@@ -0,0 +1,2 @@
+<traduction module="paquet-albums" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/agenda/trunk/lang/" reference="fr">
+</traduction>
diff --git a/www/plugins/agenda_3_5/modeles/evenement_vevent.html b/www/plugins/agenda_3_5/modeles/evenement_vevent.html
new file mode 100644 (file)
index 0000000..28aa078
--- /dev/null
@@ -0,0 +1 @@
+#INCLURE{fond=inclure/resume/evenement,id_evenement=#ENV{id,#ENV{id_evenement}}}
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/paquet.xml b/www/plugins/agenda_3_5/paquet.xml
new file mode 100644 (file)
index 0000000..26c68f0
--- /dev/null
@@ -0,0 +1,51 @@
+<paquet
+       prefix="agenda"
+       categorie="date"
+       version="3.14.5"
+       etat="stable"
+       compatibilite="[3.0.0;3.1.*]"
+       logo="prive/themes/spip/images/evenement-32.png"
+       schema="0.27.0"
+       documentation="http://contrib.spip.net/article2858"
+>      
+
+       <nom>Agenda</nom>
+       <!-- Agenda Evenementiel -->
+
+       <auteur lien="http://www.yterium.net">Cedric MORIN</auteur>
+       <auteur lien="http://contrib.spip.net/b_b">b_b</auteur>
+       <auteur lien="http://spip.tetue.net">Tetue</auteur>
+       <auteur lien="http://www.cahri.com">Julien Tessier</auteur>
+
+       <copyright>2006-2014</copyright>
+
+       <licence lien="http://www.gnu.org/licenses/gpl-3.0.html">GPL 3</licence>
+
+       <traduire module="agenda" reference="fr" gestionnaire="salvatore" />
+       <traduire module="configureragenda" reference="fr" gestionnaire="salvatore" />
+       <traduire module="migreragenda" reference="fr" gestionnaire="salvatore" />
+       
+       <pipeline nom="autoriser" inclure="agenda_autoriser.php" />
+       <pipeline nom="declarer_tables_interfaces" inclure="base/agenda_evenements.php" />
+       <pipeline nom="declarer_tables_auxiliaires" inclure="base/agenda_evenements.php" />
+       <pipeline nom="declarer_tables_objets_sql" inclure="base/agenda_evenements.php" />
+
+       <pipeline nom="affiche_milieu" inclure="agenda_pipelines.php" />
+       <pipeline nom="compositions_declarer_heritage"  inclure="agenda_pipelines.php" />
+       <pipeline nom="insert_head_css" inclure="agenda_pipelines.php" />
+       <pipeline nom="optimiser_base_disparus" inclure="agenda_pipelines.php" />
+       <pipeline nom="post_edition" inclure="agenda_pipelines.php" />
+       <pipeline nom="post_edition_lien" inclure="agenda_pipelines.php" />
+       <pipeline nom="quete_calendrier_prive" inclure="agenda_pipelines.php" />
+       <pipeline nom="revisions_chercher_label" inclure="agenda_pipelines.php" />
+
+       <utilise nom="Fulltext" compatibilite="[1.0.0;[" />
+       <utilise nom="mots" compatibilite="[2.4.6;[" />
+       <necessite nom="calendriermini" compatibilite="[2.3.7;[" />
+
+       <menu nom="evenements" titre="agenda:evenements" parent="menu_edition" icone="images/evenement-16.png" />
+       <menu nom="evenement_creer" titre="agenda:creer_evenement" parent="outils_rapides" action="evenement_edit" icone="images/evenement-new-16.png" />
+
+       <chemin path="./" />
+       <chemin path="squelettes" type='public'/>
+</paquet>
diff --git a/www/plugins/agenda_3_5/prive/objets/contenu/article-evenements.html b/www/plugins/agenda_3_5/prive/objets/contenu/article-evenements.html
new file mode 100644 (file)
index 0000000..28fa90a
--- /dev/null
@@ -0,0 +1,12 @@
+#SET{sinon,''}
+[(#AUTORISER{creerevenementdans, article, #ID_ARTICLE}|oui)
+       [(#SET{sinon,<:agenda:info_aucun_evenement:>})]
+]
+
+[(#REM) afficher les evenements de cet article]
+<div id="agenda">
+       <INCLURE{fond=prive/objets/liste/evenements-post,nb=5,sinon=#GET{sinon},statut=#LISTE{publie,prop},env}>
+       [(#AUTORISER{creerevenementdans, article, #ID_ARTICLE}|oui)
+               [(#URL_ECRIRE{evenement_edit,id_evenement=new}|parametre_url{id_article,#ID_ARTICLE}|parametre_url{redirect,#SELF}|icone_horizontale{<:agenda:creer_evenement:>,evenement-24.png,new,creer_evenement})]
+       ]
+</div>
diff --git a/www/plugins/agenda_3_5/prive/objets/contenu/evenement.html b/www/plugins/agenda_3_5/prive/objets/contenu/evenement.html
new file mode 100644 (file)
index 0000000..780d67b
--- /dev/null
@@ -0,0 +1,46 @@
+<BOUCLE_afficher_contenu(EVENEMENTS){id_evenement=#ENV{id}}{statut==.*}>
+<div class="champ contenu_titre[ (#TITRE*|strlen|?{'',vide})]">
+<div class='label'><:info_titre:></div>
+<div dir='#LANG_DIR' class='#EDIT{titre} titre'>#TITRE</div>
+</div>
+<div class="champ contenu_dates">
+<div class='label'><:agenda:info_dates:></div>
+<div dir='#LANG_DIR' class='dates'>[(#DATE_DEBUT|affdate_debut_fin{#DATE_FIN,#HORAIRE})]</div>
+</div>
+<B_repetitions>
+<div class="champ contenu_repetitions">
+       <div class='label'>[(#GRAND_TOTAL|singulier_ou_pluriel{agenda:une_repetition,agenda:nb_repetitions})]</div>
+       <div dir='#LANG_DIR' class='repetitions'>(<BOUCLE_repetitions(EVENEMENTS){par date_debut}{id_evenement_source=#ID_EVENEMENT}{', '}{statut==.*}>[(#DATE_DEBUT|affdate_court)]</BOUCLE_repetitions>)</div>
+</div>
+</B_repetitions>
+<div class="champ contenu_descriptif[ (#DESCRIPTIF*|strlen|?{'',vide})]">
+<div class='label'><:info_descriptif:></div>
+<div dir='#LANG_DIR' class='#EDIT{descriptif} descriptif'>[(#DESCRIPTIF|image_reduire{500,0})]</div>
+</div>
+<div class="champ contenu_lieu[ (#LIEU*|strlen|?{'',vide})]">
+<div class='label'><:agenda:info_lieu:></div>
+<div dir='#LANG_DIR' class='#EDIT{lieu} lieu'>[(#LIEU|image_reduire{500,0})]</div>
+</div>
+<div class="champ contenu_adresse[ (#ADRESSE*|strlen|?{'',vide})]">
+<div class='label'><:agenda:evenement_adresse:></div>
+<div dir='#LANG_DIR' class='#EDIT{adresse} adresse'>[(#ADRESSE|image_reduire{500,0})]</div>
+</div>
+<div class="champ contenu_places[ (#INSCRIPTION*|?{'',vide})]">
+<div class='label'><:agenda:info_inscription:></div>
+<BOUCLE_decompte_oui(evenements_participants){id_evenement}{reponse=oui} />#SET{rep_oui,#TOTAL_BOUCLE}<//B_decompte_oui>
+<BOUCLE_decompte_non(evenements_participants){id_evenement}{reponse=non} />#SET{rep_non,#TOTAL_BOUCLE}<//B_decompte_non>
+<BOUCLE_decompte_nsp(evenements_participants){id_evenement}{reponse=?} />#SET{rep_nsp,#TOTAL_BOUCLE}<//B_decompte_nsp>
+[<div dir='#LANG_DIR' class='#EDIT{places} places'>(#PLACES|singulier_ou_pluriel{agenda:info_1_place,agenda:info_nb_places})
+       <span class="reponses">(<:agenda:info_reponses_inscriptions:>
+               [(#GET{rep_oui}) ]<:agenda:info_reponse_inscription_oui:> |
+               [(#GET{rep_non}) ]<:agenda:info_reponse_inscription_non:> |
+               [(#GET{rep_nsp}) ]<:agenda:info_reponse_inscription_nsp:>)
+       </span>
+</div>]
+<div><a href="[(#URL_ECRIRE{agenda_inscriptions}|parametre_url{id_evenement,#ID_EVENEMENT})]"><:agenda:liste_inscrits:></a></div>
+</div>
+[<div class="champ contenu_notes">
+<div class='label'><:info_notes:></div>
+<div dir='#LANG_DIR' class='#EDIT{notes} notes'>(#NOTES)</div>
+</div>
+]</BOUCLE_afficher_contenu>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/prive/objets/infos/evenement.html b/www/plugins/agenda_3_5/prive/objets/infos/evenement.html
new file mode 100644 (file)
index 0000000..2304c66
--- /dev/null
@@ -0,0 +1,33 @@
+<BOUCLE_art(EVENEMENTS){id_evenement=#ENV{id}}{statut?}>
+<div class='infos'>
+#SET{texte_objet,#VAL{evenement}|objet_info{texte_objet}|_T}
+<div class='numero'><:titre_cadre_numero_objet{objet=#GET{texte_objet}}:><p>#ID_EVENEMENT</p></div>
+
+[(#FORMULAIRE_INSTITUER_OBJET{evenement,#ID_EVENEMENT})]
+
+[(#REM)
+
+       Bouton voir en ligne
+
+]
+<BOUCLE_has(ARTICLES){id_article}{statut==.*}>
+       <ul class="liste-items articles">
+               <li class="item"><a href="[(#ID_ARTICLE|generer_url_entite{article})]">#TITRE</a></li>
+       </ul>
+</BOUCLE_has>
+
+<BOUCLE_epublie(EVENEMENTS){id_evenement}>
+       [(#VAL{redirect}
+               |generer_url_action{type=evenement&id=#ID_EVENEMENT}
+               |parametre_url{var_mode,calcul}
+               |icone_horizontale{<:icone_voir_en_ligne:>,racine})]
+</BOUCLE_epublie>
+       [(#AUTORISER{previsualiser,evenement,#ID_EVENEMENT,'',#ARRAY{statut,#STATUT}})
+               [(#VAL{redirect}
+                       |generer_url_action{type=evenement&id=#ID_ARTICLE}
+                       |parametre_url{var_mode,preview}
+                       |icone_horizontale{<:previsualiser:>,preview})]
+       ]
+<//B_epublie>
+</div>
+</BOUCLE_art>
diff --git a/www/plugins/agenda_3_5/prive/objets/liste/evenement_participants.html b/www/plugins/agenda_3_5/prive/objets/liste/evenement_participants.html
new file mode 100755 (executable)
index 0000000..1b858cd
--- /dev/null
@@ -0,0 +1,46 @@
+<B_inscrits>
+#ANCRE_PAGINATION
+<div class="liste-objets auteurs">
+<table class='spip liste'>
+[<caption><strong class="caption">(#ENV*{titre,#GRAND_TOTAL|singulier_ou_pluriel{agenda:info_un_inscrit,agenda:info_nb_inscrits,nb}})</strong></caption>]
+       <thead>
+               <tr class='first_row'>
+                       <th class='statut' scope='col'>[(#TRI{statut,<span title="<:lien_trier_statut|attribut_html:>">#</span>,ajax})]</th>
+                       <th class='nom' scope='col'>[(#TRI{nom,<span title="<:lien_trier_nom|attribut_html:>"><:nom:></span>,ajax})]</th>
+                       <th class='mail' scope='col'>[(#TRI{email,<:email:>,ajax})]</th>
+                       <th class='date' scope='col'>[(#TRI{date,<:agenda:evenement_date_inscription:>,ajax})]</th>
+                       <th class='reponse' scope='col'>[(#TRI{reponse,<:agenda:info_reponse_inscriptions:>,ajax})]</th>[
+                       (#AUTORISER{modifier,evenement,#ID_EVENEMENT})<th class='reponse' scope='col'>Action</th>]
+               </tr>
+       </thead>
+       <tbody>
+       <BOUCLE_inscrits(evenements_participants){id_evenement}{tri #ENV{par,date},#GET{defaut_tri}}{pagination #ENV{nb,15}}>
+               <tr class="[(#COMPTEUR_BOUCLE|alterner{row_odd,row_even})]">
+                       <td class='statut'>[(#STATUT|puce_statut{auteur,#ID_AUTEUR})]</td>
+                       <td class='nom'>
+                [(#NOM|sinon{
+                <a href="[(#ID_AUTEUR|generer_url_entite{auteur})]">[(#RANG). ][(#INFO_NOM{auteur,#ID_AUTEUR}|sinon{<:texte_vide:>})]</a>
+                })]
+            </td>
+                       <td class='mail'>[(#EMAIL|sinon{#INFO_EMAIL{auteur,#ID_AUTEUR}})]</td>
+                       <td class='date'>[(#DATE|affdate_jourcourt)]</td>
+                       <td class='reponse'>#REPONSE</td>[
+                       (#AUTORISER{modifier,evenement,#ID_EVENEMENT})<td class='delete'>
+                [(#BOUTON_ACTION{<:agenda:lien_desinscrire:>, [(#URL_ACTION_AUTEUR{supprimer_evenement_participant,#ID_EVENEMENT-#ID_EVENEMENT_PARTICIPANT,#SELF})], ajax, <:agenda:confirm_suppression_inscription:>})]
+            </td>]
+               </tr>
+       </BOUCLE_inscrits>
+       </tbody>
+</table>
+[<p class='pagination'>(#PAGINATION{prive})</p>]
+</div>
+[(#AUTORISER{modifier,evenement,#ID_EVENEMENT})
+    [(#BOUTON_ACTION{<:agenda:lien_desinscrire_tous:>, [(#URL_ACTION_AUTEUR{supprimer_evenement_participant,#ID_EVENEMENT-tous,#SELF})], ajax, <:agenda:confirm_suppression_inscription_toutes:>})]
+]
+
+</B_inscrits>
+<h2><:agenda:aucun_inscrit:></h2>
+<//B_inscrits>
+
+
diff --git a/www/plugins/agenda_3_5/prive/objets/liste/evenements-post.html b/www/plugins/agenda_3_5/prive/objets/liste/evenements-post.html
new file mode 100644 (file)
index 0000000..0197a75
--- /dev/null
@@ -0,0 +1,11 @@
+[(#REM) Se placer au debut du mois en cours par defaut]
+#SET{date_debut,#ENV{date_debut,#ENV{date}}|affdate{Y-m-d 00:00:00}|agenda_dateplus{-1}}
+<BOUCLE_un(EVENEMENTS){statut==.*}{id_evenement}{id_article?}{id_rubrique?}{id_mot?}{id_evenement_source?}>
+[(#REM) Si un evenement passe, on commence par le jour de cet evenement]
+       #SET{id_evenement,#ID_EVENEMENT}
+</BOUCLE_un>
+       <BOUCLE_debut(EVENEMENTS){evenement_a_venir #GET{date_debut}}{statut==.*}{id_article?}{id_rubrique?}{id_mot?}{id_evenement_source?}{par date_debut}{0,1}>
+               #SET{id_evenement,#ID_EVENEMENT}
+       </BOUCLE_debut>
+<//B_un>
+[(#INCLURE{fond=prive/objets/liste/evenements,debut_liste_evt=#ENV{debut_liste_evt,@#GET{id_evenement}},id_evenement=#EVAL{null},env})]
diff --git a/www/plugins/agenda_3_5/prive/objets/liste/evenements.html b/www/plugins/agenda_3_5/prive/objets/liste/evenements.html
new file mode 100644 (file)
index 0000000..42dc489
--- /dev/null
@@ -0,0 +1,49 @@
+[(#SET{defaut_tri,#ARRAY{
+       date_debut,#ENV{date_sens,1},
+       titre,1,
+       id_evenement,1,
+       points,-1
+}})
+]<B_liste_evt>
+#ANCRE_PAGINATION
+<div class="liste-objets evenements">
+<table class='spip liste'>
+[<caption><strong class="caption">(#ENV*{titre,#GRAND_TOTAL|singulier_ou_pluriel{agenda:info_un_evenement,agenda:info_nombre_evenements}})</strong></caption>]
+       <thead>
+               <tr class='first_row'>
+                       <th class='statut' scope='col'>[(#TRI{statut,<span title="<:lien_trier_statut|attribut_html:>">#</span>,ajax})]</th>
+                       <th class='date' scope='col'>[(#TRI{date_debut,<:date:>,ajax})]</th>
+                       <th class='article secondaire' scope='col'></th>
+                       <th class='titre principale' scope='col'>[(#TRI{titre,<:info_titre:>,ajax})]</th>
+                       <th class='lieu' scope='col secondaire'><:agenda:evenement_lieu:></th>
+                       <th class='id' scope='col'>[(#TRI{id_evenement,<:info_numero_abbreviation:>,ajax})]</th>
+               </tr>
+       </thead>
+       <tbody>
+       <BOUCLE_liste_evt(EVENEMENTS){id_evenement?}{id_article?}{id_rubrique?}{id_mot?}{id_evenement_source?}{where?}{statut?}{recherche?}{tri #ENV{par,date_debut},#GET{defaut_tri}}{pagination #ENV{nb,15}}>
+               <tr class="[(#COMPTEUR_BOUCLE|alterner{row_odd,row_even})][ (#EXPOSE)][(#ID_EVENEMENT_SOURCE|oui)repetition]">
+                       <td class='statut'>[(#STATUT|puce_statut{evenement,#ID_EVENEMENT})]</td>
+                       <td class='date'>[(#DATE_DEBUT|affdate_jourcourt|unique{liste_evt})]</td>
+                       <td class='article secondaire'>[<a (#ID_ARTICLE|oui) href="[(#ID_ARTICLE|generer_url_entite{article})]" title="[(#INFO_TITRE{article,#ID_ARTICLE}|attribut_html)]">[(#CHEMIN_IMAGE{article-16.png}|balise_img{#INFO_TITRE{article,#ID_ARTICLE}})]</a>]</td>
+                       <td class='titre principale'><a href="[(#ID_EVENEMENT_SOURCE|?{#ID_EVENEMENT_SOURCE,#ID_EVENEMENT}|generer_url_entite{evenement})]"
+                                                                                                                                                                                                                                                                                                               title="<:info_numero_abbreviation|attribut_html:> #ID_EVENEMENT">[(#RANG). ]#TITRE</a>
+                               <p class="date">[(#DATE_DEBUT|affdate_debut_fin{#DATE_FIN,#HORAIRE})]</p>
+                       </td>
+                       <td class='lieu secondaire'>#LIEU</td>
+                       #SET{id_evenement,#ID_EVENEMENT_SOURCE|?{#ID_EVENEMENT_SOURCE,#ID_EVENEMENT}}
+                       <td class='id'>[(#ID_EVENEMENT_SOURCE|oui)
+                               [(#CHEMIN_IMAGE{repetition-16.png}|balise_img{<:agenda:repetition:>,''}|inserer_attribut{title,<:agenda:repetition:>})]
+                               ][(#AUTORISER{modifier,evenement,#GET{id_evenement}}|?{[
+                               <a href="[(#URL_ECRIRE{evenement_edit}|parametre_url{id_evenement,#GET{id_evenement}}|parametre_url{redirect,#SELF})]">(#GET{id_evenement})</a>],
+                               [(#GET{id_evenement})]
+                       })]
+                       </td>
+               </tr>
+       </BOUCLE_liste_evt>
+       </tbody>
+</table>
+[<p class='pagination'>(#PAGINATION{prive})</p>]
+</div>
+</B_liste_evt>[
+<div class="liste-objets evenements caption-wrap"><strong class="caption">(#ENV*{sinon,''})</strong></div>
+]<//B_liste_evt>
diff --git a/www/plugins/agenda_3_5/prive/squelettes/contenu/agenda_inscriptions.html b/www/plugins/agenda_3_5/prive/squelettes/contenu/agenda_inscriptions.html
new file mode 100755 (executable)
index 0000000..c9b7c0d
--- /dev/null
@@ -0,0 +1,14 @@
+#BOITE_OUVRIR
+<BOUCLE_evt(EVENEMENTS){id_evenement}>
+<h1 class="grostitre"><:agenda:liste_inscrits:></h1>
+<h2 class="#EDIT{titre}">#TITRE</h2>
+<div dir='#LANG_DIR' class='dates'>[(#DATE_DEBUT|affdate_debut_fin{#DATE_FIN,#HORAIRE})]</div>
+[<div class='descriptif secondaire #EDIT{descriptif}'><:agenda:evenement_descriptif:> : (#DESCRIPTIF|PtoBR)</div>]
+[<div class='lieu secondaire #EDIT{lieu}'><:agenda:evenement_lieu:> : (#LIEU)</div>]
+<INCLURE{fond=prive/objets/liste/evenement_participants,id_evenement=#ENV{id_evenement},tri_inscrits=#ENV{tri_inscrits}}>
+</BOUCLE_evt>
+</B_evt>
+<h2><:agenda:aucun_evenement:></h2>
+<//B_evt>
+#BOITE_FERMER
diff --git a/www/plugins/agenda_3_5/prive/squelettes/contenu/configurer_agenda.html b/www/plugins/agenda_3_5/prive/squelettes/contenu/configurer_agenda.html
new file mode 100644 (file)
index 0000000..d47dd30
--- /dev/null
@@ -0,0 +1,9 @@
+[(#AUTORISER{configurer_agenda}|sinon_interdire_acces)]
+<h1 class="grostitre"><:agenda:agenda:></h1>
+[(#BOITE_OUVRIR{[(#VAL{agenda:explication_synchro_flux_ical_titre}|_T)],'info'})]
+<:agenda:explication_synchro_flux_ical:>
+#BOITE_FERMER
+<div class="ajax">#FORMULAIRE_CONFIGURER_AGENDA</div>
+<div class="ajax">#FORMULAIRE_CONFIGURER_CALENDRIERMINI</div>
+<h2><:agenda:rubriques:></h2>
+<div class="ajax">#FORMULAIRE_MIGRER_AGENDA</div>
diff --git a/www/plugins/agenda_3_5/prive/squelettes/contenu/evenement_edit.html b/www/plugins/agenda_3_5/prive/squelettes/contenu/evenement_edit.html
new file mode 100644 (file)
index 0000000..48e97a3
--- /dev/null
@@ -0,0 +1,32 @@
+[(#ID_EVENEMENT|intval|oui)
+       [(#AUTORISER{modifier,evenement,#ID_EVENEMENT}|sinon_interdire_acces)]]
+[(#ID_EVENEMENT|intval|non)
+       [(#AUTORISER{creer,evenement,0,'',#ARRAY{id_article,#ENV{id_article}}}|sinon_interdire_acces)]]
+
+#SET{retour,#ENV{redirect}|sinon{#ENV{id_article}|?{#URL_ECRIRE{article,id_article=#ID_ARTICLE},#ID_EVENEMENT|?{#ID_EVENEMENT|generer_url_entite{evenement},#URL_ECRIRE{evenements}}}}}
+<div class='cadre-formulaire-editer'>
+<div class="entete-formulaire">
+       [(#GET{retour}|icone_verticale{<:icone_retour:>,evenement,'',left retour[(#ENV{retourajax,''}|oui)ajax preload]})]
+       [[(#ID_EVENEMENT|?{<:agenda:titre_cadre_modifier_evenement:>,<:agenda:titre_cadre_ajouter_evenement:>})]
+       <h1>(#ENV{titre,#INFO_TITRE{evenement,#ID_EVENEMENT}|sinon{<:agenda:info_nouvel_evenement:>}})</h1>]
+</div>
+
+#SET{redirect,#ENV{redirect,#ID_EVENEMENT|generer_url_entite{evenement}}}
+[(#ENV{retourajax,''}|oui)
+       #SET{redirect,'javascript:if (window.jQuery) jQuery(".entete-formulaire .retour a").followLink();'}
+       <div class="ajax">
+       <script type="text/javascript">/*<!\[CDATA\[*/var date_picker_loading = false;/*\]\]>*/</script>
+]
+       #FORMULAIRE_EDITER_EVENEMENT{#ENV{id_evenement,oui},#ENV{id_article},#GET{redirect},#ENV{associer_objet}}
+[(#ENV{retourajax,''}|oui)
+       </div>
+       <script type="text/javascript">/*<!\[CDATA\[*/reloadExecPage('#ENV{exec}', '#navigation,#chemin');/*\]\]>*/</script>
+]
+[(#ENV{id_evenement,''}|intval|oui)
+<div class="ajax">
+       #FORMULAIRE_EDITER_LIENS{mots,evenement,#ENV{id_evenement}}
+</div>
+]
+
+</div>
+
diff --git a/www/plugins/agenda_3_5/prive/squelettes/contenu/evenements.html b/www/plugins/agenda_3_5/prive/squelettes/contenu/evenements.html
new file mode 100644 (file)
index 0000000..9584d51
--- /dev/null
@@ -0,0 +1,16 @@
+[(#AUTORISER{menu,_evenements}|sinon_interdire_acces)]
+<h1 class="grostitre"><:agenda:agenda:></h1>
+<BOUCLE_expose(RUBRIQUES){id_rubrique}{statut==.*}>
+#BOITE_OUVRIR{'','note'}
+       <a class='annule_filtre' href="[(#SELF|parametre_url{id_rubrique,''})]" title="<:info_tout_afficher|attribut_html:>">[(#CHEMIN_IMAGE{fermer-16.png}|balise_img|inserer_attribut{alt,<:info_tout_afficher:>})]</a>
+       <:agenda:rubrique_liste_evenements_de:>
+       <h2 class='objet_titre'><a href='[(#ID_RUBRIQUE|generer_url_entite{rubrique})]'>#TITRE</a></h2>
+#BOITE_FERMER
+</BOUCLE_expose>
+
+<div id="liste_des_evenements">
+       [(#INCLURE{fond=prive/objets/liste/evenements-post}{env})]
+</div>
+[(#AUTORISER{creer,evenement}|oui)
+[(#URL_ECRIRE{evenement_edit,id_evenement=new}|parametre_url{id_article,#ID_ARTICLE}|icone_verticale{<:agenda:creer_evenement:>,evenement-24.png,new,right})]
+]
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/prive/squelettes/extra/agenda_inscriptions.html b/www/plugins/agenda_3_5/prive/squelettes/extra/agenda_inscriptions.html
new file mode 100644 (file)
index 0000000..d3fff83
--- /dev/null
@@ -0,0 +1,23 @@
+[(#BOITE_OUVRIR{[(#CHEMIN_IMAGE{calendrier-24.png}|balise_img{'',cadre-icone})],'info'})]
+<h3><:agenda:info_inscription:></h3>
+<BOUCLE_decompte_oui(evenements_participants){id_evenement}{reponse=oui} />#SET{rep_oui,#TOTAL_BOUCLE}<//B_decompte_oui>
+<BOUCLE_decompte_non(evenements_participants){id_evenement}{reponse=non} />#SET{rep_non,#TOTAL_BOUCLE}<//B_decompte_non>
+<BOUCLE_decompte_nsp(evenements_participants){id_evenement}{reponse=?} />#SET{rep_nsp,#TOTAL_BOUCLE}<//B_decompte_nsp>
+<BOUCLE_evt(EVENEMENTS){id_evenement}>
+[<div class="label"><span dir="#LANG_DIR" class="#EDIT{places} places">(#PLACES|singulier_ou_pluriel{agenda:info_1_place,agenda:info_nb_places})</span></div>]
+</BOUCLE_evt>
+<div class="label"><:agenda:info_reponses_inscriptions:></div>
+<ul class="liste-items">
+    <li class="item">[(#GET{rep_oui}) ]<:agenda:info_reponse_inscription_oui:></li>
+    <li class="item">[(#GET{rep_non}) ]<:agenda:info_reponse_inscription_non:></li>
+    <li class="item">[(#GET{rep_nsp}) ]<:agenda:info_reponse_inscription_nsp:></li>
+</ul>
+#SET{args,#ARRAY{id_evenement,#ID_EVENEMENT}}
+<h3><:agenda:telecharger:></h3>
+<ul class="liste-items">
+    <li class="item"><a href="[(#URL_PAGE{transmettre,[(#VAL{evenement_participants}|param_low_sec{#GET{args}, '', 'transmettre'})]}|parametre_url{reponse,oui})]" class="noajax"><:agenda:telecharger_oui:></a></li>
+    <li class="item"><a href="[(#URL_PAGE{transmettre,[(#VAL{evenement_participants}|param_low_sec{#GET{args}, '', 'transmettre'})]})]" class="noajax"><:agenda:telecharger_toutes:></a></li>
+#SET{args,#ARRAY{}}
+    <li class="item"><a href="[(#URL_PAGE{transmettre,[(#VAL{evenements_participants}|param_low_sec{#GET{args}, '', 'transmettre'})]})]" class="noajax"><:agenda:telecharger_toutes_tous_evenements:></a></li>
+</ul>
+#BOITE_FERMER
diff --git a/www/plugins/agenda_3_5/prive/squelettes/inclure/agenda-navigation-mois.html b/www/plugins/agenda_3_5/prive/squelettes/inclure/agenda-navigation-mois.html
new file mode 100644 (file)
index 0000000..1a986b0
--- /dev/null
@@ -0,0 +1,12 @@
+#SET{self,#ENV{self}|parametre_url{debut_mois,#EVAL{_request('debut_mois')}}}
+<B_mois>
+[(#REM) navigation par mois]
+<ul class="liste-items mois">
+<BOUCLE_mois(EVENEMENTS){!par date_debut}{pagination 12}{id_article?}{id_rubrique?}{id_mot?}{id_evenement_source?}{fusion_par_mois date_debut}{statut==.*}>
+<li class="item[(#ENV{date_debut,''}|=={#DATE_DEBUT|affdate{Y-m-01}}|oui)on]"><a
+       href="[(#GET{self}|parametre_url{date_debut,#DATE_DEBUT|affdate{Y-m-01}})]"
+       onclick='return update_agenda(this);'>[(#DATE_DEBUT|affdate_mois_annee{})]</a></li>
+</BOUCLE_mois>
+</ul>
+[<p class='pagination'>(#PAGINATION{prive})</p>]
+</B_mois>
diff --git a/www/plugins/agenda_3_5/prive/squelettes/inclure/agenda-rubriques.html b/www/plugins/agenda_3_5/prive/squelettes/inclure/agenda-rubriques.html
new file mode 100644 (file)
index 0000000..0371454
--- /dev/null
@@ -0,0 +1,13 @@
+#SET{self,#SELF|parametre_url{debut_rubriques,#EVAL{_request('debut_rubriques')}}}
+<B_rubriques>
+[(#BOITE_OUVRIR{[<:agenda:rubriques:>(#CHEMIN_IMAGE{rubrique-24.png}|balise_img{'',cadre-icone})],simple})]
+[(#REM) navigation par rubriques agenda]
+[<p class='pagination'>(#PAGINATION{page})</p>]
+<ul class='liste_items'>
+<li class='item[ (#ENV{id_rubrique}|non)on]'>[(#GET{self}|parametre_url{id_rubrique,''}|lien_ou_expose{<:agenda:toutes_rubriques:>,#ENV{id_rubrique,0}|=={0}})]</li>
+<BOUCLE_rubriques(RUBRIQUES){par titre}{pagination 10}{agenda=1}>
+<li class='item[ (#EXPOSE)]'>[(#GET{self}|parametre_url{id_rubrique,#ID_RUBRIQUE}|lien_ou_expose{#TITRE|supprimer_numero{},#ENV{id_rubrique}|=={#ID_RUBRIQUE}})] (<a href='#URL_RUBRIQUE'><:info_numero_abbreviation:>#ID_RUBRIQUE</a>)</li>
+</BOUCLE_rubriques>
+</ul>
+#BOITE_FERMER
+</B_rubriques>
diff --git a/www/plugins/agenda_3_5/prive/squelettes/navigation/agenda_inscriptions.html b/www/plugins/agenda_3_5/prive/squelettes/navigation/agenda_inscriptions.html
new file mode 100755 (executable)
index 0000000..ac724bc
--- /dev/null
@@ -0,0 +1,5 @@
+<BOUCLE_evt(EVENEMENTS){id_evenement}>
+[(#BOITE_OUVRIR{[(#CHEMIN_IMAGE{calendrier-24.png}|balise_img{'',cadre-icone})]})]
+    <h3><a href="[(#URL_ECRIRE{evenement}|parametre_url{id_evenement,#ID_EVENEMENT})]" class="noajax"><:agenda:retour_evenement:></a></h3>
+#BOITE_FERMER
+</BOUCLE_evt>
diff --git a/www/plugins/agenda_3_5/prive/squelettes/navigation/evenement.html b/www/plugins/agenda_3_5/prive/squelettes/navigation/evenement.html
new file mode 100644 (file)
index 0000000..0c3d615
--- /dev/null
@@ -0,0 +1,14 @@
+<BOUCLE_nav(EVENEMENTS){id_evenement}{statut==.*}{si #ENV{exec}|=={evenement}}>
+#BOITE_OUVRIR{'','info'}
+#PIPELINE{boite_infos,#ARRAY{data,'',args,#ARRAY{'type','evenement','id',#ENV{id_evenement}}}}
+#BOITE_FERMER
+
+<div class="ajax">
+#FORMULAIRE_EDITER_LOGO{evenement,#ID_EVENEMENT,'',#ENV**}
+</div>
+
+#PIPELINE{afficher_config_objet,#ARRAY{args,#ARRAY{type,evenement,id,#ID_EVENEMENT},data,''}}
+
+</BOUCLE_nav>
+[(#ENV{exec}|=={evenement_edit}|oui)<INCLURE{fond=prive/squelettes/navigation/evenement_edit,env} />]
+<//B_nav>
diff --git a/www/plugins/agenda_3_5/prive/squelettes/navigation/evenements.html b/www/plugins/agenda_3_5/prive/squelettes/navigation/evenements.html
new file mode 100644 (file)
index 0000000..a2c1eb3
--- /dev/null
@@ -0,0 +1,25 @@
+[(#BOITE_OUVRIR{[(#CHEMIN_IMAGE{calendrier-24.png}|balise_img{'',cadre-icone})],'simple'})]
+#SET{self,#SELF|parametre_url{id_evenement|date_debut|debut_liste_evt,''}|parametre_url{debut_mois,#EVAL{_request('debut_mois')}}}
+<ul class='liste-items agenda-nav'>
+<BOUCLE_debut(EVENEMENTS){par date_debut}{0,1}{id_article?}{id_rubrique?}{id_evenement_source?}{statut==.*}>
+<li class="item[(#ENV{date_debut,''}|=={#DATE_DEBUT|affdate{Y-01-01}}|oui)on]"><a
+       href="[(#GET{self}|parametre_url{date_debut,#DATE_DEBUT|affdate{Y-01-01}})]"
+       onclick='return update_agenda(this);'><:agenda:evenements_depuis_debut:></a></li>
+</BOUCLE_debut>
+<li class="item[(#ENV{date_debut,''}|non)on]"><a
+       href="[(#GET{self})]"
+       onclick='return update_agenda(this);'><:agenda:evenements_a_venir:></a></li>
+</ul>
+[(#INCLURE{fond=prive/squelettes/inclure/agenda-navigation-mois}{ajax}{env}{self=#GET{self}})]
+#BOITE_FERMER
+[(#INCLURE{fond=prive/squelettes/inclure/agenda-rubriques}{ajax}{env}{self=#GET{self}})]
+<script type="text/javascript">
+function update_agenda(link){
+       var date_debut = parametre_url(jQuery(link).attr('href'),'date_debut');
+       $("#liste_des_evenements").ajaxReload({args:{date_debut:date_debut,debut_liste_evt:""},history:true});
+       jQuery(link).closest('.box').find('li.on').removeClass('on');
+       jQuery(link).closest('li').addClass('on');
+       return false;
+}
+
+</script>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/prive/style_prive_plugin_agenda.html b/www/plugins/agenda_3_5/prive/style_prive_plugin_agenda.html
new file mode 100644 (file)
index 0000000..7d0c867
--- /dev/null
@@ -0,0 +1,102 @@
+[(#REM)
+
+       Ce squelette definit les styles de l'espace prive
+
+       Note: l'entete "Vary:" sert a repousser l'entete par
+       defaut "Vary: Cookie,Accept-Encoding", qui est (un peu)
+       genant en cas de "rotation du cookie de session" apres
+       un changement d'IP (effet de clignotement).
+
+       ATTENTION: il faut absolument le charset sinon Firefox croit que
+       c'est du text/html !
+       <style>
+]
+#CACHE{3600*100,cache-client}
+#HTTP_HEADER{Content-Type: text/css; charset=iso-8859-15}
+#HTTP_HEADER{Vary: Accept-Encoding}
+
+/*.pagination a.creer {float:#ENV{left};}*/
+/* vevent */
+/*
+li.item div.vevent h3 {margin:0;font-size:1.1em;}
+li.item div.vevent p {margin:2px 0 0;}
+li.item div.vevent p.date {font-size: 0.98em; }
+ul.evenements li.item.court p,ul.evenements li.item.court .actions { display: none; }
+li.item div.vevent p.category {font-size:0.9em;color:#999;}
+*/
+/* liste des evenements */
+/*
+.plier_deplier { float: #ENV{left}; font-size: 0.9em; }
+.liste-items.evenements li.item { padding-left: 130px; }
+.liste-items.evenements li.court { border-width: 1px; }
+.liste-items.evenements li.item .jour { width: 120px; margin-left: -130px; float: #ENV{left}; display: inline; position: relative; overflow: hidden; white-space: nowrap; font-size: 98%; }
+
+li.item .repetitions {margin-top:0.5em;font-size:0.9em;}
+li.court .repetitions {display: none;}
+li.item .repetitions ul,
+li.item .repetitions li {display:inline;margin:0;padding:0;}
+*/
+
+.liste-items.evenements .actions {text-align:right;font-size:0.9em;margin:5px 0 0;}
+.liste-objets.evenements p.date {margin-bottom: 0;}
+.liste-objets.evenements .on td,.liste-objets.evenements .on th {background-color: [#(#ENV{claire}|couleur_eclaircir{0.75})]}
+.liste-objets.evenements tr.repetition {filter:alpha(opacity=70); -moz-opacity:0.7; opacity: 0.7;}
+.liste-objets.evenements .repetition .id a {display: block;}
+/* sur la fiche rubriques */
+.rubrique .agenda-statut img.statut {float:#ENV{right};margin-#ENV{left}:5px;}
+
+/* sur la fiche article */
+.fiche_objet #agenda {position: relative;}
+.fiche_objet #agenda .creer_evenement {position: absolute;top:-1px;right: 5px;}
+
+/* sur la fiche evenement */
+.evenement .fiche_objet .formulaire_dater {display: none;}
+.evenement #wysiwyg .contenu_lieu .label {display: inline;font-weight: bold;}
+.evenement #wysiwyg .contenu_lieu .lieu {display: inline;}
+.evenement #wysiwyg .contenu_adresse .label {display: block;}
+.evenement .fiche_objet .contenu_dates {padding-#ENV{left}:20px;background:url(#CHEMIN_IMAGE{calendrier-16.png}) no-repeat center #ENV{left};}
+.evenement #wysiwyg .contenu_places {margin-top: 1em;}
+.evenement #wysiwyg .contenu_places .label {display: inline;font-weight: bold;}
+.evenement #wysiwyg .contenu_places .places {display: inline;}
+.evenement .fiche_objet .contenu_repetitions {padding-#ENV{left}:20px;}
+.evenement #wysiwyg .contenu_repetitions .label {display: inline;color:inherit;}
+.evenement .fiche_objet .contenu_repetitions .repetitions {display: inline;color:#999;}
+
+/* edition d'un evenement */
+.formulaire_editer_evenement .editer_parent {}
+.formulaire_editer_evenement .editer_parent label {margin-#ENV{left}:0;display:block;float:left;padding:2px 0;width:130px;}
+
+.formulaire_editer_evenement .editer_horaire {padding-top:0;margin-top:0;}
+.formulaire_editer_evenement .editer_horaire label {margin:0;display:inline;width:auto;float:none;}
+.formulaire_editer_evenement li.editer_date,
+.formulaire_editer_evenement li.editer_date li {clear:left;}
+.formulaire_editer_evenement li.editer_date_debut_fin label { /*vertical-align: top;*/ }
+
+.formulaire_editer_evenement input.date {width:6.5em !important;padding-right:20px;-moz-box-sizing: content-box;-webkit-box-sizing: content-box;-ms-box-sizing: content-box;box-sizing: content-box;}
+.formulaire_editer_evenement span.afficher_horaire label {float:none;display:inline;padding:0;margin:0;}
+.formulaire_editer_evenement input.heure {width:4.5em !important;}
+
+.formulaire_editer_evenement li.editer_date label.heure {display:none;}
+.formulaire_editer_evenement li.editer_date label {display:inline;float:none;margin:0;}
+
+.formulaire_editer_evenement .editer_inscription .choix {display:inline;}
+.formulaire_editer_evenement .editer_inscription .choix input.text {width:50px;margin-left:1em;/*vertical-align:top;*/}
+
+/* le picker */
+
+/* begin: jQuery UI Datepicker moving pixels fix */
+       table.ui-datepicker-calendar {border-collapse: separate;}
+       .ui-datepicker-calendar td {border: 1px solid transparent;}
+/* end: jQuery UI Datepicker moving pixels fix */
+/* begin: jQuery UI Datepicker emphasis on selected dates */
+.ui-datepicker .ui-datepicker-calendar .ui-state-highlight a {
+       background: #ENV{foncee}; /* a color that fits the widget theme */
+       color: white; /* a color that is readeable with the color above */
+}
+/* end: jQuery UI Datepicker emphasis on selected dates */
+
+#repetitions_picker {}
+.editer_repetitions textarea {width:100%;height:4em;color:#999;border:1px solid #999;}
+
+/* Calendrier */
+ul.menu li .pagination a, ul.menu li .pagination strong.on { display:inline; }
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-16.png b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-16.png
new file mode 100644 (file)
index 0000000..bd06328
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-16.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-24.png b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-24.png
new file mode 100644 (file)
index 0000000..ba15877
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-24.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-32.png b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-32.png
new file mode 100644 (file)
index 0000000..52fcf84
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-32.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-add-16.png b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-add-16.png
new file mode 100644 (file)
index 0000000..6745d21
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-add-16.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-add-24.png b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-add-24.png
new file mode 100644 (file)
index 0000000..07fcbe6
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-add-24.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-add-32.png b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-add-32.png
new file mode 100644 (file)
index 0000000..4b7935d
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-add-32.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-del-16.png b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-del-16.png
new file mode 100644 (file)
index 0000000..4dc9d1c
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-del-16.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-del-24.png b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-del-24.png
new file mode 100644 (file)
index 0000000..04665d2
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-del-24.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-del-32.png b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-del-32.png
new file mode 100644 (file)
index 0000000..7ee0806
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-del-32.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-edit-16.png b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-edit-16.png
new file mode 100644 (file)
index 0000000..d8b20f8
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-edit-16.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-edit-24.png b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-edit-24.png
new file mode 100644 (file)
index 0000000..0c942c9
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-edit-24.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-edit-32.png b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-edit-32.png
new file mode 100644 (file)
index 0000000..d155573
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-edit-32.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-new-16.png b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-new-16.png
new file mode 100644 (file)
index 0000000..90e7d9c
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-new-16.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-new-24.png b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-new-24.png
new file mode 100644 (file)
index 0000000..1f76e78
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-new-24.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-new-32.png b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-new-32.png
new file mode 100644 (file)
index 0000000..80e4048
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-new-32.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-non-32.png b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-non-32.png
new file mode 100644 (file)
index 0000000..8d8aa42
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-non-32.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-ok-32.png b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-ok-32.png
new file mode 100644 (file)
index 0000000..187f521
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/agenda-ok-32.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-16.png b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-16.png
new file mode 100644 (file)
index 0000000..03f8f88
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-16.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-24.png b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-24.png
new file mode 100644 (file)
index 0000000..557ccda
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-24.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-32.png b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-32.png
new file mode 100644 (file)
index 0000000..8c314ac
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-32.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-add-16.png b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-add-16.png
new file mode 100644 (file)
index 0000000..6fac578
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-add-16.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-add-24.png b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-add-24.png
new file mode 100644 (file)
index 0000000..fdbb71e
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-add-24.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-add-32.png b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-add-32.png
new file mode 100644 (file)
index 0000000..058f547
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-add-32.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-del-16.png b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-del-16.png
new file mode 100644 (file)
index 0000000..b938925
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-del-16.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-del-24.png b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-del-24.png
new file mode 100644 (file)
index 0000000..433e740
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-del-24.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-del-32.png b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-del-32.png
new file mode 100644 (file)
index 0000000..60b8c70
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-del-32.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-edit-16.png b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-edit-16.png
new file mode 100644 (file)
index 0000000..59b1461
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-edit-16.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-edit-24.png b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-edit-24.png
new file mode 100644 (file)
index 0000000..1a6343b
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-edit-24.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-edit-32.png b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-edit-32.png
new file mode 100644 (file)
index 0000000..ba6c3b3
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-edit-32.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-new-16.png b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-new-16.png
new file mode 100644 (file)
index 0000000..5cfa59e
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-new-16.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-new-24.png b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-new-24.png
new file mode 100644 (file)
index 0000000..877deff
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-new-24.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-new-32.png b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-new-32.png
new file mode 100644 (file)
index 0000000..8535fd3
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/evenement-new-32.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/repetition-16.png b/www/plugins/agenda_3_5/prive/themes/spip/images/repetition-16.png
new file mode 100644 (file)
index 0000000..61e580f
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/repetition-16.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/repetition-24.png b/www/plugins/agenda_3_5/prive/themes/spip/images/repetition-24.png
new file mode 100644 (file)
index 0000000..3a931ae
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/repetition-24.png differ
diff --git a/www/plugins/agenda_3_5/prive/themes/spip/images/repetition-32.png b/www/plugins/agenda_3_5/prive/themes/spip/images/repetition-32.png
new file mode 100644 (file)
index 0000000..b2359c9
Binary files /dev/null and b/www/plugins/agenda_3_5/prive/themes/spip/images/repetition-32.png differ
diff --git a/www/plugins/agenda_3_5/prive/transmettre/evenement_participants.html b/www/plugins/agenda_3_5/prive/transmettre/evenement_participants.html
new file mode 100755 (executable)
index 0000000..1cbaad1
--- /dev/null
@@ -0,0 +1,16 @@
+<BOUCLE0(AUTEURS){tout}{id_auteur=#ENV{id}}{lang_select}><?php
+if ([(#ID_AUTEUR|securiser_acces{#ENV{cle},transmettre,#ENV{op}, #ENV{args}}|?{1,0})]) {
+?><B_participants_oui>"<:nom:>","<:email:>","<:agenda:evenement_date_inscription:>"
+<BOUCLE_participants_oui(evenements_participants){id_evenement}{si #ENV{reponse}}{reponse=#ENV{reponse}}>"[(#NOM|sinon{#INFO_NOM{auteur,#ID_AUTEUR}})]","[(#EMAIL|sinon{#INFO_EMAIL{auteur,#ID_AUTEUR}})]","[(#DATE|affdate{'d/m/Y H:i:s'})]"
+</BOUCLE_participants_oui>
+</B_participants_oui>
+<B_participants>"<:nom:>","<:email:>","<:agenda:evenement_date_inscription:>","<:agenda:reponse:>"
+<BOUCLE_participants(evenements_participants){id_evenement}>"[(#NOM|sinon{#INFO_NOM{auteur,#ID_AUTEUR}})]","[(#EMAIL|sinon{#INFO_EMAIL{auteur,#ID_AUTEUR}})]","[(#DATE|affdate{'d/m/Y H:i:s'})]","#REPONSE"
+</BOUCLE_participants>
+<//B_participants_oui>
+<?php
+} else {
+include_spip('inc/minipres'); 
+echo minipres();
+}?></BOUCLE0>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/prive/transmettre/evenements_participants.html b/www/plugins/agenda_3_5/prive/transmettre/evenements_participants.html
new file mode 100755 (executable)
index 0000000..51a5217
--- /dev/null
@@ -0,0 +1,10 @@
+<BOUCLE0(AUTEURS){tout}{id_auteur=#ENV{id}}{lang_select}><?php
+if ([(#ID_AUTEUR|securiser_acces{#ENV{cle},transmettre,#ENV{op}, #ENV{args}}|?{1,0})]) {
+?>"<:numero:>","<:agenda:evenement:>","<:nom:>","<:email:>","<:agenda:evenement_date_inscription:>","<:agenda:reponse:>"
+<BOUCLE_evt(EVENEMENTS){par id_evenement}><BOUCLE_participants(evenements_participants){id_evenement}>"#ID_EVENEMENT","#INFO_TITRE{evenement,#ID_EVENEMENT}","[(#NOM|sinon{#INFO_NOM{auteur,#ID_AUTEUR}})]","[(#EMAIL|sinon{#INFO_EMAIL{auteur,#ID_AUTEUR}})]","[(#DATE|affdate{'d/m/Y H:i:s'})]","#REPONSE"
+</BOUCLE_participants></BOUCLE_evt>
+<?php
+} else {
+include_spip('inc/minipres');
+echo minipres();
+}?></BOUCLE0>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/public/agenda.php b/www/plugins/agenda_3_5/public/agenda.php
new file mode 100644 (file)
index 0000000..aea759c
--- /dev/null
@@ -0,0 +1,397 @@
+<?php\r
+/**\r
+ * Plugin Agenda 4 pour Spip 3.0\r
+ * Licence GPL 3\r
+ *\r
+ * 2006-2011\r
+ * Auteurs : cf paquet.xml\r
+ */\r
+\r
+if (!defined("_ECRIRE_INC_VERSION")) return;\r
+\r
+/**\r
+ * #URL_EVENEMENT envoie sur la page de l'evenement\r
+ * ou sur la page de l'article avec un &id_evenement=xxx\r
+ * selon la configuration de l'agenda\r
+ *\r
+ * @param object $p\r
+ * @return object\r
+ */\r
+function balise_URL_EVENEMENT_dist($p) {\r
+\r
+       include_spip("inc/config");\r
+       include_spip("balise/url_");\r
+\r
+       if (lire_config("agenda/url_evenement",'evenement')!=='article'){\r
+               $code = generer_generer_url('evenement', $p);\r
+       }\r
+       else {\r
+               $_ide = interprete_argument_balise(1,$p);\r
+               if (!$_ide)\r
+                       $_ide = champ_sql('id_evenement', $p);\r
+               $_ida = "generer_info_entite($_ide,'evenement','id_article')";\r
+\r
+               $code = generer_generer_url_arg('article', $p, $_ida);\r
+               $code = "parametre_url($code,'id_evenement',$_ide,'&')";\r
+       }\r
+\r
+       $code = champ_sql('url_evenement', $p, $code);\r
+       $p->code = $code;\r
+       if (!$p->etoile)\r
+               $p->code = "vider_url($code)";\r
+       $p->interdire_scripts = false;\r
+\r
+       return $p;\r
+}\r
+\r
+\r
+/**\r
+ * fonction sous jacente pour les 3 criteres\r
+ * fusion_par_jour, fusion_par_mois, fusion_par_annee\r
+ * \r
+ * @param string $format\r
+ * @param strinf $as\r
+ * @param string $idb\r
+ * @param object $boucles\r
+ * @param object $crit\r
+ */\r
+function agenda_critere_fusion_par_xx($format, $as, $idb, &$boucles, $crit){\r
+       $boucle = &$boucles[$idb];\r
+       $type = $boucle->type_requete;\r
+       $_date = isset($crit->param[0]) ? calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent)\r
+         : "'".(isset($GLOBALS['table_date'][$type])?$GLOBALS['table_date'][$type]:"date")."'";\r
+\r
+       $date = $boucle->id_table. '.' .substr($_date,1,-1);\r
+\r
+       // annuler une eventuelle fusion sur cle primaire !\r
+       foreach($boucles[$idb]->group as $k=>$g)\r
+               if ($g==$boucle->id_table.'.'.$boucle->primary)\r
+                       unset($boucles[$idb]->group[$k]);\r
+       $boucles[$idb]->group[]  = 'DATE_FORMAT('.$boucle->id_table.'.".'.$_date.'.", ' . "'$format')";\r
+       $boucles[$idb]->select[] = 'DATE_FORMAT('.$boucle->id_table.'.".'.$_date.'.", ' . "'$format') AS $as";\r
+}\r
+\r
+/**\r
+ * {fusion_par_jour date_debut}\r
+ * {fusion_par_jour date_fin}\r
+ * \r
+ * @param string $idb\r
+ * @param object $boucles\r
+ * @param object $crit\r
+ */\r
+function critere_fusion_par_jour_dist($idb, &$boucles, $crit) {\r
+       agenda_critere_fusion_par_xx('%Y-%m-%d','jour',$idb, $boucles, $crit);\r
+}\r
+\r
+/**\r
+ * {fusion_par_mois date_debut}\r
+ * {fusion_par_mois date_fin}\r
+ *\r
+ * @param string $idb\r
+ * @param object $boucles\r
+ * @param object $crit\r
+ */\r
+function critere_fusion_par_mois_dist($idb, &$boucles, $crit) {\r
+       agenda_critere_fusion_par_xx('%Y-%m','mois',$idb, $boucles, $crit);\r
+}\r
+\r
+/**\r
+ * {fusion_par_annee date_debut}\r
+ * {fusion_par_annee date_fin}\r
+ *\r
+ * @param string $idb\r
+ * @param object $boucles\r
+ * @param object $crit\r
+ */\r
+function critere_fusion_par_annee_dist($idb, &$boucles, $crit) {\r
+       agenda_critere_fusion_par_xx('%Y','annee',$idb, $boucles, $crit);\r
+}\r
+\r
+/**\r
+ * {evenement_a_venir}\r
+ * {evenement_a_venir #ENV{date}}\r
+ * \r
+ * @param string $idb\r
+ * @param object $boucles\r
+ * @param object $crit\r
+ */\r
+function critere_evenement_a_venir_dist($idb, &$boucles, $crit) {\r
+       $boucle = &$boucles[$idb];\r
+       $id_table = $boucle->id_table;\r
+       \r
+       $_dateref = agenda_calculer_date_reference($idb, $boucles, $crit);\r
+       $_date = "$id_table.date_debut";\r
+       $op = $crit->not ? "<=":">";\r
+       \r
+       // si on ne sait pas si les heures comptent, on utilise toute la journee.\r
+       // sinon, on s'appuie sur le champ 'horaire=oui'\r
+       // pour savoir si les dates utilisent les heures ou pas.        \r
+       $where_futur_sans_heure =\r
+               array("'$op'", "'$_date'", "sql_quote(date('Y-m-d 23:59:59', strtotime($_dateref)))");\r
+               \r
+       if (array_key_exists('horaire', $boucle->show['field'])) {\r
+               $where =\r
+                       array("'OR'",\r
+                               array("'AND'",\r
+                                       array("'='", "'horaire'", "sql_quote('oui')"),\r
+                                       array("'$op'","'$_date'","sql_quote($_dateref)")\r
+                               ),              \r
+                               array("'AND'",\r
+                                       array("'!='", "'horaire'", "sql_quote('oui')"),\r
+                                       $where_futur_sans_heure\r
+                               )\r
+                       );\r
+       } else {\r
+               $where = $where_futur_sans_heure;\r
+       }\r
+       \r
+       \r
+       $boucle->where[] = $where;\r
+}\r
+\r
+/**\r
+ * {evenement_passe}\r
+ * {evenement_passe #ENV{date}}\r
+ *\r
+ * @param string $idb\r
+ * @param object $boucles\r
+ * @param object $crit\r
+ */\r
+function critere_evenement_passe_dist($idb, &$boucles, $crit) {\r
+       $boucle = &$boucles[$idb];\r
+       $id_table = $boucle->id_table;\r
+\r
+       $_dateref = agenda_calculer_date_reference($idb, $boucles, $crit);\r
+       $_date = "$id_table.date_fin";\r
+       $op = $crit->not ? ">=":"<";\r
+       \r
+       // si on ne sait pas si les heures comptent, on utilise toute la journee.\r
+       // sinon, on s'appuie sur le champ 'horaire=oui'\r
+       // pour savoir si les dates utilisent les heures ou pas.        \r
+       $where_passe_sans_heure =\r
+               array("'$op'", "'$_date'", "sql_quote(date('Y-m-d 00:00:00', strtotime($_dateref)))");\r
+               \r
+       if (array_key_exists('horaire', $boucle->show['field'])) {\r
+               $where =\r
+                       array("'OR'",\r
+                               array("'AND'",\r
+                                       array("'='", "'horaire'", "sql_quote('oui')"),\r
+                                       array("'$op'","'$_date'","sql_quote($_dateref)")\r
+                               ),              \r
+                               array("'AND'",\r
+                                       array("'!='", "'horaire'", "sql_quote('oui')"),\r
+                                       $where_passe_sans_heure\r
+                               )\r
+                       );\r
+       } else {\r
+               $where = $where_passe_sans_heure;\r
+       }\r
+       \r
+       \r
+       $boucle->where[] = $where;\r
+}\r
+\r
+/**\r
+ * {evenement_en_cours}\r
+ * {evenement_en_cours #ENV{date}}\r
+ *\r
+ * @param string $idb\r
+ * @param object $boucles\r
+ * @param object $crit\r
+ */\r
+function critere_evenement_en_cours_dist($idb, &$boucles, $crit) {\r
+       $boucle = &$boucles[$idb];\r
+       $id_table = $boucle->id_table;\r
+\r
+       $_dateref = agenda_calculer_date_reference($idb, $boucles, $crit);\r
+       $_date_debut = "$id_table.date_debut";\r
+       $_date_fin = "$id_table.date_fin";\r
+\r
+       // si on ne sait pas si les heures comptent, on utilise toute la journee.\r
+       // sinon, on s'appuie sur le champ 'horaire=oui'\r
+       // pour savoir si les dates utilisent les heures ou pas.        \r
+       $where_jour_sans_heure =\r
+               array("'AND'",\r
+                       array("'<='", "'$_date_debut'", "sql_quote(date('Y-m-d 23:59:59', strtotime($_dateref)))"),\r
+                       array("'>='", "'$_date_fin'", "sql_quote(date('Y-m-d 00:00:00', strtotime($_dateref)))")\r
+               );\r
+               \r
+       if (array_key_exists('horaire', $boucle->show['field'])) {\r
+               $where =\r
+                       array("'OR'",\r
+                               array("'AND'",\r
+                                       array("'='", "'horaire'", "sql_quote('oui')"),\r
+                                       array("'AND'",\r
+                                               array("'<='", "'$_date_debut'", "sql_quote($_dateref)"),\r
+                                               array("'>='", "'$_date_fin'", "sql_quote($_dateref)")\r
+                                       )\r
+                               ),              \r
+                               array("'AND'",\r
+                                       array("'!='", "'horaire'", "sql_quote('oui')"),\r
+                                       $where_jour_sans_heure\r
+                               )\r
+                       );\r
+       } else {\r
+               $where = $where_jour_sans_heure;\r
+       }\r
+\r
+       if ($crit->not)\r
+               $where = array("'NOT'",$where);\r
+       $boucle->where[] = $where;\r
+}\r
+\r
+/**\r
+ * {evenementrelatif #ENV{choix}}\r
+ * {evenementrelatif #ENV{choix}, #ENV{date}}\r
+ * #ENV{choix} peut prendre 6 valeurs : tout, a_venir, en_cours, passe, en_cours_a_venir ou passe_en_cours\r
+ * \r
+ * @param string $idb\r
+ * @param object $boucles\r
+ * @param object $crit\r
+ */\r
+function critere_evenementrelatif_dist($idb, &$boucles, $crit) {\r
+       $boucle = &$boucles[$idb];\r
+       $id_table = $boucle->id_table;\r
+       if (isset($crit->param[1]))\r
+               $_dateref = calculer_liste($crit->param[1], array(), $boucles, $boucles[$idb]->id_parent);\r
+       else\r
+               $_dateref = "date('Y-m-d H:i:00')";\r
+       $not = $crit->not ? 'oui' : '';\r
+       $choix = isset($crit->param[0]) ? calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent) : "''";\r
+       $horaire = array_key_exists('horaire', $boucle->show['field']) ? 'oui' : '';\r
+       \r
+       $boucle->where[] = "agenda_calculer_critere_evenementrelatif('$id_table',$_dateref,'$not',$choix,'$horaire')";\r
+}\r
+\r
+/**\r
+ * Fonction interne utilisee par le critere {evenementrelatif}\r
+ * @param string $id_table\r
+ * @param string $_dateref\r
+ * @param string $not\r
+ * @param string $choix\r
+ * @param string $horaire\r
+ * @return array\r
+ */\r
+function agenda_calculer_critere_evenementrelatif($id_table,$_dateref,$not,$choix,$horaire){\r
+       $_date_debut = "$id_table.date_debut";\r
+       $_date_fin = "$id_table.date_fin";\r
+       if ($choix == 'en_cours_a_venir') {\r
+               $choix = 'passe';\r
+               $not = ($not) ? '' : 'oui';\r
+       }\r
+       if ($choix == 'passe_en_cours') {\r
+               $choix = 'a_venir';\r
+               $not = ($not) ? '' : 'oui';\r
+       }\r
+       \r
+       switch($choix) {\r
+               case 'a_venir':\r
+                       $op_a_venir = $not ? "<=":">";\r
+                       $where_a_venir_sans_heure =\r
+                               array($op_a_venir, $_date_debut, sql_quote(date('Y-m-d 23:59:59', strtotime($_dateref))));\r
+                       if ($horaire) {\r
+                               $where =\r
+                               array('OR',\r
+                                       array('AND',\r
+                                               array('=', 'horaire', sql_quote('oui')),\r
+                                               array($op_a_venir,$_date_debut,sql_quote($_dateref))\r
+                                       ),              \r
+                                       array('AND',\r
+                                               array('!=', 'horaire', sql_quote('oui')),\r
+                                               $where_a_venir_sans_heure\r
+                                       )\r
+                               );\r
+                       } else {\r
+                               $where = $where_a_venir_sans_heure;\r
+                       }\r
+                       return $where;\r
+                       break;\r
+\r
+               case 'passe':\r
+                       $op_passe = $not ? ">=":"<";\r
+                       $where_passe_sans_heure =\r
+                               array($op_passe, $_date_fin, sql_quote(date('Y-m-d 00:00:00', strtotime($_dateref))));\r
+                       if ($horaire) {\r
+                               $where =\r
+                                       array('OR',\r
+                                               array('AND',\r
+                                                       array('=', 'horaire', sql_quote('oui')),\r
+                                                       array($op_passe,$_date_fin,sql_quote($_dateref))\r
+                                               ),              \r
+                                               array('AND',\r
+                                                       array('!=', 'horaire', sql_quote('oui')),\r
+                                                       $where_passe_sans_heure\r
+                                               )\r
+                                       );\r
+                       } else {\r
+                               $where = $where_passe_sans_heure;\r
+                       }\r
+                       return $where;\r
+                       break;\r
+\r
+               case 'en_cours':\r
+                       $where_en_cours_sans_heure =\r
+                               array('AND',\r
+                                       array('<=', $_date_debut, sql_quote(date('Y-m-d 23:59:59', strtotime($_dateref)))),\r
+                                       array('>=', $_date_fin, sql_quote(date('Y-m-d 00:00:00', strtotime($_dateref))))\r
+                               );\r
+                                               if ($horaire) {\r
+                               $where =\r
+                                       array('OR',\r
+                                               array('AND',\r
+                                                       array('=', 'horaire', sql_quote('oui')),\r
+                                                       array('AND',\r
+                                                               array('<=', $_date_debut, sql_quote($_dateref)),\r
+                                                               array('>=', $_date_fin, sql_quote($_dateref))\r
+                                                       )\r
+                                               ),              \r
+                                               array('AND',\r
+                                                       array('!=', 'horaire', sql_quote('oui')),\r
+                                                       $where_en_cours_sans_heure\r
+                                               )\r
+                                       );\r
+                       } else {\r
+                               $where = $where_en_cours_sans_heure;\r
+                       }\r
+                       return ($not) ? array('NOT' , $where) : $where;\r
+                       break;\r
+\r
+               default:\r
+                       return array();\r
+                       break;\r
+       }\r
+}\r
+\r
+/**\r
+ * Fonction privee pour mutualiser de code des criteres_evenement_*\r
+ * Retourne le code php pour obtenir la date de reference de comparaison\r
+ * des evenements a trouver \r
+ *\r
+ * @param string $idb\r
+ * @param object $boucles\r
+ * @param object $crit\r
+ * \r
+ * @return string code PHP concernant la date.\r
+**/\r
+function agenda_calculer_date_reference($idb, &$boucles, $crit) {\r
+       if (isset($crit->param[0]))\r
+               return calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent);\r
+       else\r
+               return "date('Y-m-d H:i:00')";\r
+}\r
+\r
+\r
+/**\r
+ * Balise #NB_INSCRITS\r
+ * pour afficher le nombre d'inscrits (qui ont repondu oui) a un evenement\r
+ *\r
+ * @param Object $p\r
+ * @return object\r
+ */\r
+function balise_NB_INSCRITS_dist($p) {\r
+       $id_evenement = champ_sql('id_evenement', $p);\r
+       $p->code = "sql_countsel('spip_evenements_participants','id_evenement='.intval($id_evenement).' AND reponse=\'oui\'')";\r
+       return $p;\r
+}\r
+\r
+?>\r
diff --git a/www/plugins/agenda_3_5/squelettes/agenda-ical.html b/www/plugins/agenda_3_5/squelettes/agenda-ical.html
new file mode 100644 (file)
index 0000000..bf40977
--- /dev/null
@@ -0,0 +1,19 @@
+#HTTP_HEADER{Content-type:text/calendar; charset=utf-8}#CACHE{3600}BEGIN:VCALENDAR
+VERSION:2.0
+X-WR-TIMEZONE:Europe/Paris
+CALSCALE:GREGORIAN
+PRODID:-//SPIP/Plugin #PLUGIN{AGENDA,nom}//NONSGML v1.0//FR
+<BOUCLE_art(ARTICLES){id_article}>
+X-WR-CALNAME;VALUE=TEXT:[(#NOM_SITE_SPIP|filtrer_ical)] - [(#TITRE|supprimer_tags|filtrer_ical)][
+X-WR-CALDESC:(#INTRODUCTION|supprimer_tags|filtrer_ical)]
+X-WR-RELCALID:[(#URL_ARTICLE|filtrer_ical)]
+</BOUCLE_art>
+</B_art>
+X-WR-CALNAME;VALUE=TEXT:[(#NOM_SITE_SPIP|filtrer_ical)] -- Agenda[
+X-WR-CALDESC:(#DESCRIPTION_SITE_SPIP|supprimer_tags|filtrer_ical)]
+X-WR-RELCALID:[(#URL_SITE_SPIP|filtrer_ical)]
+<//B_art>
+<BOUCLE_evenement2(EVENEMENTS) {branche ?}{id_article ?} {statut=publie}{par date_fin} {age_fin<=0} {0,50} {doublons}>[(#INCLURE{fond=inc/un-evenement-ical}{evtitre=#TITRE}{id_evenement}{date_debut}{horaire}{date_fin}{id_article}{descriptif}{lieu}{date_creation}{maj})]
+</BOUCLE_evenement2><BOUCLE_evenement3(EVENEMENTS) {branche ?}{id_article ?} {statut=publie}{par date_fin} {age_fin>=0} {inverse} {0,50} {doublons}>[(#INCLURE{fond=inc/un-evenement-ical}{evtitre=#TITRE}{id_evenement}{date_debut}{horaire}{date_fin}{id_article}{descriptif}{lieu}{date_creation}{maj})]
+</BOUCLE_evenement3><BOUCLE_evenement(EVENEMENTS) {branche ?}{id_article ?} {statut=publie}{par date_fin} {inverse} {0,50} {doublons}>[(#INCLURE{fond=inc/un-evenement-ical}{evtitre=#TITRE}{id_evenement}{date_debut}{horaire}{date_fin}{id_article}{descriptif}{lieu}{date_creation}{maj})]
+</BOUCLE_evenement>END:VCALENDAR
diff --git a/www/plugins/agenda_3_5/squelettes/agenda-rss.html b/www/plugins/agenda_3_5/squelettes/agenda-rss.html
new file mode 100644 (file)
index 0000000..3741a95
--- /dev/null
@@ -0,0 +1,30 @@
+[(#HTTP_HEADER{Content-type: text/xml[; charset=(#CHARSET)]})]<?xml 
+version="1.0"[ encoding="(#CHARSET)"]?>
+<rss version="2.0" [(#REM) rss 2.0.9)]
+       xmlns:dc="http://purl.org/dc/elements/1.1/"
+       xmlns:content="http://purl.org/rss/1.0/modules/content/"
+>
+
+<channel[ xml:lang="(#LANG)"]>
+       [<title>Agenda (#NOM_SITE_SPIP|texte_backend)</title>]
+       <link>#URL_SITE_SPIP/</link>
+       [<description>(#DESCRIPTIF_SITE_SPIP|supprimer_tags|texte_backend)</description>]
+       <language>#LANG</language>
+       <generator>SPIP - www.spip.net</generator>
+       
+[      <image>
+               <title>[(#NOM_SITE_SPIP|texte_backend)]</title>
+               <url>(#LOGO_SITE_SPIP||image_reduire{150,150}|extraire_attribut{src}|url_absolue|texte_backend)</url>
+               <link>#URL_SITE_SPIP/</link>
+               [<height>(#LOGO_SITE_SPIP||image_reduire{150,150}|extraire_attribut{height})</height>]
+               [<width>(#LOGO_SITE_SPIP||image_reduire{150,150}|extraire_attribut{width})</width>]
+       </image>
+]
+       
+       <BOUCLE_evenement(EVENEMENTS) {branche ?}{id_article ?}{!evenement_passe}{par date_debut} {0,60}>
+               <INCLURE{fond=inc-rss-item-evenement,id_evenement} />
+       </BOUCLE_evenement>
+
+</channel>
+
+</rss>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/agenda-zpip.html b/www/plugins/agenda_3_5/squelettes/agenda-zpip.html
new file mode 100644 (file)
index 0000000..428f283
--- /dev/null
@@ -0,0 +1 @@
+<INCLURE{fond=structure}{env}{type=page}{composition=agenda} />
diff --git a/www/plugins/agenda_3_5/squelettes/aside/agenda.html b/www/plugins/agenda_3_5/squelettes/aside/agenda.html
new file mode 100644 (file)
index 0000000..99c2ac9
--- /dev/null
@@ -0,0 +1,16 @@
+[(#REM)
+
+  Squelette
+  (c) 2009 xxx
+  Distribue sous licence GPL
+
+]
+<B_mois>
+#ANCRE_PAGINATION
+<ul class="liste-items">
+<BOUCLE_mois(EVENEMENTS){!par date_debut}{id_article?}{id_rubrique?}{id_mot?}{id_evenement_source?}{fusion_par_mois date_debut}{pagination 10}>
+<li class="item">[(#SELF|parametre_url{date_debut,#DATE_DEBUT|affdate{Y-m-01}}|lien_ou_expose{#DATE_DEBUT|affdate_mois_annee{},#ENV{date_debut,''}|=={#DATE_DEBUT|affdate{Y-m-01}}})]</li>
+</BOUCLE_mois>
+</ul>
+[<div class="pagination">(#PAGINATION)</div>]
+</B_mois>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/breadcrumb/agenda.html b/www/plugins/agenda_3_5/squelettes/breadcrumb/agenda.html
new file mode 100644 (file)
index 0000000..58b70bc
--- /dev/null
@@ -0,0 +1,4 @@
+<ul class="breadcrumb">
+       <li><a href="#URL_SITE_SPIP/"><:accueil_site:></a><span class="divider"> &gt; </span></li>
+       <li class="active">[(#CONFIG{agenda/titre}|sinon{<:agenda:titre_sur_l_agenda:>}|typo)]</li>
+</ul>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/calendrier_mini_event.json.html b/www/plugins/agenda_3_5/squelettes/calendrier_mini_event.json.html
new file mode 100644 (file)
index 0000000..a89efa8
--- /dev/null
@@ -0,0 +1,18 @@
+#HTTP_HEADER{Content-type:text/javascript;}
+[<BOUCLE_ev(EVENEMENTS)
+       {!evenement_passe #ENV{start}|todate}{!evenement_a_venir #ENV{end}|todate}{par date_debut}
+       {id_article?}
+       {id_rubrique?}
+       {id_mot?}
+       {id_evenement_source?}
+       {', '}>
+[(#ARRAY{
+id,#ID_EVENEMENT,
+title,[(#TITRE|html2unicode|unicode2charset|textebrut)],
+allDay,[(#HORAIRE|=={oui}|non)],
+start,#DATE_DEBUT,
+end,#DATE_FIN,
+url,#URL_EVENEMENT*,
+className,calendrier-couleur6,
+description,[(#DESCRIPTIF|html2unicode|unicode2charset)]
+}|json_encode)]</BOUCLE_ev>]
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/calendrier_mini_event.json_fonctions.php b/www/plugins/agenda_3_5/squelettes/calendrier_mini_event.json_fonctions.php
new file mode 100644 (file)
index 0000000..134d3e8
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+/**
+ * Fichier de fonction du json du calendrier mini
+ *
+ * @package SPIP\Agenda\Fonctions
+**/
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+include_spip('calendrier_mini.json_fonctions');
+
+?>
diff --git a/www/plugins/agenda_3_5/squelettes/content/agenda.html b/www/plugins/agenda_3_5/squelettes/content/agenda.html
new file mode 100644 (file)
index 0000000..263c0c5
--- /dev/null
@@ -0,0 +1,21 @@
+[(#REM)
+/**
+ * Plugin Agenda 4 pour Spip 3.0
+ * Licence GPL 3
+ *
+ * 2006-2011
+ * Auteurs : cf paquet.xml
+ */
+]
+<section>
+
+       <header class="cartouche">
+               <h1 class="#EDIT{meta-agenda/titre}">[(#CONFIG{agenda/titre}|sinon{<:agenda:titre_sur_l_agenda:>}|typo)]</h1>
+       </header>
+
+       [<div class="chapo #EDIT{meta-agenda/descriptif}">(#CONFIG{agenda/descriptif}|propre)</div>]
+
+       <div class="main">
+               <INCLURE{fond=inclure/agenda-liste,env,ajax} />
+       </div>
+</section>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/content/article-evenement.html b/www/plugins/agenda_3_5/squelettes/content/article-evenement.html
new file mode 100644 (file)
index 0000000..83d20fb
--- /dev/null
@@ -0,0 +1,13 @@
+[(#REM)
+/**
+ * Plugin Agenda 4 pour Spip 3.0
+ * Licence GPL 3
+ *
+ * 2006-2011
+ * Auteurs : cf paquet.xml
+ */
+]
+<BOUCLE_ev(EVENEMENTS){id_evenement}>
+<div class="evenement one well">#INCLURE{fond=inclure/resume/evenement,id_evenement}</div>
+<INCLURE{fond=content/article,id_article,id_evenement,env} />
+</BOUCLE_ev>
diff --git a/www/plugins/agenda_3_5/squelettes/content/evenement.html b/www/plugins/agenda_3_5/squelettes/content/evenement.html
new file mode 100644 (file)
index 0000000..ed77ee5
--- /dev/null
@@ -0,0 +1,13 @@
+[(#REM)
+/**
+ * Plugin Agenda 4 pour Spip 3.0
+ * Licence GPL 3
+ *
+ * 2006-2011
+ * Auteurs : cf paquet.xml
+ */
+]
+[(#CONFIG{agenda/url_evenement,evenement}|=={article}|non|sinon_interdire_acces{#URL_EVENEMENT*,301})]
+<BOUCLE_ev(EVENEMENTS){id_evenement}>
+<INCLURE{fond=content/article-evenement,id_article,id_evenement,env} />
+</BOUCLE_ev>
diff --git a/www/plugins/agenda_3_5/squelettes/content/jour.html b/www/plugins/agenda_3_5/squelettes/content/jour.html
new file mode 100644 (file)
index 0000000..c7834d6
--- /dev/null
@@ -0,0 +1,35 @@
+[(#REM)
+/**
+ * Plugin Agenda 4 pour Spip 3.0
+ * Licence GPL 3
+ *
+ * 2006-2011
+ * Auteurs : cf paquet.xml
+ */
+]
+#SET{date_debut,#ENV{date_debut,#ENV{date}}|affdate{Y-m-d 00:00:00}}
+#SET{date_fin,#ENV{date_debut,#ENV{date}}|affdate{Y-m-d 23:59:59}}
+
+<section>
+
+       <header class="cartouche">
+               <h1><:agenda:titre_sur_l_agenda:>
+               <small>[(#GET{date_debut}|affdate)]</small>
+               </h1>
+       </header>
+
+       <div class="main">
+               <B_ev>
+                       <div class="liste long evenements">
+                               <ul class="liste-items evenements">
+                               <BOUCLE_ev(EVENEMENTS){!evenement_passe #GET{date_debut}}{!evenement_a_venir #GET{date_fin}}{par date_debut}{id_article?}{id_rubrique?}{id_mot?}{id_evenement_source?}>
+                                       [(#TOTAL_BOUCLE|>{1}|sinon_interdire_acces{#URL_EVENEMENT})]
+                                       <li class="item">#INCLURE{fond=inclure/resume/evenement,id_evenement}</li>
+                               </BOUCLE_ev>
+                               </ul>
+                       </div>
+               </B_ev>
+               <p><:agenda:info_aucun_evenement:></p>
+               <//B_ev>
+       </div>
+</section>
diff --git a/www/plugins/agenda_3_5/squelettes/content/rubrique-agenda.html b/www/plugins/agenda_3_5/squelettes/content/rubrique-agenda.html
new file mode 100644 (file)
index 0000000..1860294
--- /dev/null
@@ -0,0 +1,24 @@
+[(#REM)
+/**
+ * Plugin Agenda 4 pour Spip 3.0
+ * Licence GPL 3
+ *
+ * 2006-2011
+ * Auteurs : cf paquet.xml
+ */
+]
+<BOUCLE_rub(RUBRIQUES){id_rubrique}>
+<section>
+
+       <header class="cartouche">
+               <h1 class="#EDIT{titre}">[(#LOGO_RUBRIQUE|image_reduire)]#TITRE</h1>
+       </header>
+
+       <div class="main">
+               [<div class="#EDIT{texte} chapo">(#TEXTE)</div>]
+
+               <INCLURE{fond=inclure/agenda-liste,id_rubrique,env,ajax} />
+       </div>
+
+</section>
+</BOUCLE_rub>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/contenu/page-agenda.html b/www/plugins/agenda_3_5/squelettes/contenu/page-agenda.html
new file mode 100644 (file)
index 0000000..157d953
--- /dev/null
@@ -0,0 +1,18 @@
+[(#REM)
+
+  Squelette
+  (c) 2009 xxx
+  Distribue sous licence GPL
+
+]
+
+<B_ev>
+       [<h1>(#TOTAL_BOUCLE|singulier_ou_pluriel{agenda:info_un_evenement,agenda:info_nombre_evenements})</h1>]
+       <ul class="liste-items evenements">
+       <BOUCLE_ev(EVENEMENTS){!evenement_passe #ENV{date_debut,#ENV{date}}}{!evenement_a_venir #ENV{date_debut,#ENV{date}}|agenda_moisdecal{1,'Y-m-d H:i:00'}}{par date_debut}{id_article?}{id_rubrique?}{id_mot?}{id_evenement_source?}>
+               <li class="item">#INCLURE{fond=inclure/resume/evenement,id_evenement}</li>
+       </BOUCLE_ev>
+       </ul>
+</B_ev>
+<h1><:agenda:info_aucun_evenement:></h1>
+<//B_ev>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/contenu/page-jour.html b/www/plugins/agenda_3_5/squelettes/contenu/page-jour.html
new file mode 100644 (file)
index 0000000..6372cc4
--- /dev/null
@@ -0,0 +1,18 @@
+[(#REM)
+
+  Squelette pour affichage de la liste des evenements d'une journee
+  (c) 2012 xxx
+  Distribue sous licence GPL
+
+]#SET{date_debut,#ENV{date_debut,#ENV{date}}|affdate{Y-m-d 00:00:00}}
+#SET{date_fin,#ENV{date_debut,#ENV{date}}|affdate{Y-m-d 23:59:59}}
+<B_ev>
+       [<h1>[(#GET{date_debut}|affdate): ](#TOTAL_BOUCLE|singulier_ou_pluriel{agenda:info_un_evenement,agenda:info_nombre_evenements})</h1>]
+       <ul class="liste-items evenements">
+<BOUCLE_ev(EVENEMENTS){!evenement_passe #GET{date_debut}}{!evenement_a_venir #GET{date_fin}}{par date_debut}{id_article?}{id_rubrique?}{id_mot?}{id_evenement_source?}>
+               <li class="item">#INCLURE{fond=inclure/resume/evenement,id_evenement}</li>
+       </BOUCLE_ev>
+       </ul>
+</B_ev>
+<h1>[(#ENV{date}|affdate): ]<:agenda:info_aucun_evenement:></h1>
+<//B_ev>
diff --git a/www/plugins/agenda_3_5/squelettes/extra/agenda.html b/www/plugins/agenda_3_5/squelettes/extra/agenda.html
new file mode 100644 (file)
index 0000000..a0b1474
--- /dev/null
@@ -0,0 +1,16 @@
+[(#REM)
+
+  Squelette
+  (c) 2009 xxx
+  Distribue sous licence GPL
+
+]
+#SET{id_article,#ENV{id_article,''}}
+<BOUCLE_ev(EVENEMENTS){id_evenement}>
+#CALENDRIER_MINI{#DATE_DEBUT,date_debut,#URL_PAGE{jour},#URL_PAGE{calendrier_mini_event.json,id_article=#ID_ARTICLE}}
+#SET{id_article,#ID_ARTICLE}
+</BOUCLE_ev>
+#CALENDRIER_MINI{#DATE_DEBUT,date_debut,#URL_PAGE{jour},#URL_PAGE{calendrier_mini_event.json}|parametre_url{id_article,#GET{id_article}}|parametre_url{id_rubrique,#GET{id_rubrique}}}
+<//B_ev>
+
+<INCLURE{fond=inclure/agenda-evenements-meme-article,id_article=#GET{id_article},env,ajax} />
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/extra/evenement.html b/www/plugins/agenda_3_5/squelettes/extra/evenement.html
new file mode 100644 (file)
index 0000000..a704a01
--- /dev/null
@@ -0,0 +1,8 @@
+[(#REM)
+
+  Squelette
+  (c) 2009 xxx
+  Distribue sous licence GPL
+
+]
+<INCLURE{fond=extra1/agenda,env} />
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/extra/jour.html b/www/plugins/agenda_3_5/squelettes/extra/jour.html
new file mode 100644 (file)
index 0000000..a704a01
--- /dev/null
@@ -0,0 +1,8 @@
+[(#REM)
+
+  Squelette
+  (c) 2009 xxx
+  Distribue sous licence GPL
+
+]
+<INCLURE{fond=extra1/agenda,env} />
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/extra/rubrique-agenda.html b/www/plugins/agenda_3_5/squelettes/extra/rubrique-agenda.html
new file mode 100644 (file)
index 0000000..a704a01
--- /dev/null
@@ -0,0 +1,8 @@
+[(#REM)
+
+  Squelette
+  (c) 2009 xxx
+  Distribue sous licence GPL
+
+]
+<INCLURE{fond=extra1/agenda,env} />
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/extra1/agenda.html b/www/plugins/agenda_3_5/squelettes/extra1/agenda.html
new file mode 100644 (file)
index 0000000..a0b1474
--- /dev/null
@@ -0,0 +1,16 @@
+[(#REM)
+
+  Squelette
+  (c) 2009 xxx
+  Distribue sous licence GPL
+
+]
+#SET{id_article,#ENV{id_article,''}}
+<BOUCLE_ev(EVENEMENTS){id_evenement}>
+#CALENDRIER_MINI{#DATE_DEBUT,date_debut,#URL_PAGE{jour},#URL_PAGE{calendrier_mini_event.json,id_article=#ID_ARTICLE}}
+#SET{id_article,#ID_ARTICLE}
+</BOUCLE_ev>
+#CALENDRIER_MINI{#DATE_DEBUT,date_debut,#URL_PAGE{jour},#URL_PAGE{calendrier_mini_event.json}|parametre_url{id_article,#GET{id_article}}|parametre_url{id_rubrique,#GET{id_rubrique}}}
+<//B_ev>
+
+<INCLURE{fond=inclure/agenda-evenements-meme-article,id_article=#GET{id_article},env,ajax} />
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/extra1/evenement.html b/www/plugins/agenda_3_5/squelettes/extra1/evenement.html
new file mode 100644 (file)
index 0000000..a704a01
--- /dev/null
@@ -0,0 +1,8 @@
+[(#REM)
+
+  Squelette
+  (c) 2009 xxx
+  Distribue sous licence GPL
+
+]
+<INCLURE{fond=extra1/agenda,env} />
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/extra1/jour.html b/www/plugins/agenda_3_5/squelettes/extra1/jour.html
new file mode 100644 (file)
index 0000000..a704a01
--- /dev/null
@@ -0,0 +1,8 @@
+[(#REM)
+
+  Squelette
+  (c) 2009 xxx
+  Distribue sous licence GPL
+
+]
+<INCLURE{fond=extra1/agenda,env} />
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/extra1/rubrique-agenda.html b/www/plugins/agenda_3_5/squelettes/extra1/rubrique-agenda.html
new file mode 100644 (file)
index 0000000..a704a01
--- /dev/null
@@ -0,0 +1,8 @@
+[(#REM)
+
+  Squelette
+  (c) 2009 xxx
+  Distribue sous licence GPL
+
+]
+<INCLURE{fond=extra1/agenda,env} />
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/ical-agenda.html b/www/plugins/agenda_3_5/squelettes/ical-agenda.html
new file mode 100644 (file)
index 0000000..8fea8d1
--- /dev/null
@@ -0,0 +1 @@
+#INCLURE{fond=agenda-ical}
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/inc-rss-item-evenement.html b/www/plugins/agenda_3_5/squelettes/inc-rss-item-evenement.html
new file mode 100644 (file)
index 0000000..f4bc59d
--- /dev/null
@@ -0,0 +1,29 @@
+<BOUCLE_evenement(EVENEMENTS) {id_evenement}>
+<item>
+       [<title>(#TITRE|texte_backend)</title>]
+       <link>[(#URL_EVENEMENT|url_absolue)]</link>
+       [<guid isPermaLink="true">(#URL_EVENEMENT|url_absolue)</guid>]
+       <date>#DATE_DEBUT</date>
+       [<dc:date>(#DATE_DEBUT|date_iso)</dc:date>]
+       <dc:format>text/html</dc:format>
+       [<dc:language>(#LANG)</dc:language>]
+
+       #SET{intro,''}<BOUCLE_art(ARTICLES){id_article}>#SET{intro,#INTRODUCTION{#ENV{coupe,300}}</BOUCLE_art>
+       [(#REM) Le bloc qui suit diffuse l'evenement a la mode RSS ]
+       <description>
+               [(#DATE_DEBUT|agenda_affdate_debut_fin{#DATE_FIN,#HORAIRE}|texte_backend)][ - (#LIEU|PtoBR|texte_backend)]
+               [(#DESCRIPTIF|sinon{#GET{intro}}|texte_backend)]
+       </description>
+
+       [(#REM) Le bloc qui suit diffuse aussi le texte integral de l'evenement,
+       ce qui permet une syndication plus riche (mais plus "lourde").
+       Fonction desactivable depuis les reglages du site.]
+       [(#CONFIG{syndication_integrale}|=={oui}|?{' ',''})<content:encoded>
+       [(#LOGO_ARTICLE{right}|image_reduire{150,150}|texte_backend)]
+       &lt;div class='rss_texte'&gt;
+               [(#DATE_DEBUT|agenda_affdate_debut_fin{#DATE_FIN,#HORAIRE}|texte_backend)][ - (#LIEU|PtoBR|texte_backend)]
+               [(#DESCRIPTIF|sinon{#GET{intro}}|texte_backend)]
+       &lt;/div&gt;
+       </content:encoded>]
+</item>
+</BOUCLE_evenement>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/inclure/agenda-evenements-meme-article.html b/www/plugins/agenda_3_5/squelettes/inclure/agenda-evenements-meme-article.html
new file mode 100644 (file)
index 0000000..b8df95e
--- /dev/null
@@ -0,0 +1,12 @@
+<B_et_aussi>
+<div class="liste">
+<h2><:agenda:titre_sur_l_agenda_aussi:></h2>
+#ANCRE_PAGINATION
+<ul class="liste-items evenements short">
+<BOUCLE_et_aussi(EVENEMENTS){par date_debut}{!evenement_passe #ENV{date_debut,#ENV{date}}}{id_article=#ENV{id_article}}{id_evenement!=#ENV{id_evenement,0}}{pagination 10}>
+<li class="item evenement">#INCLURE{fond=inclure/resume/evenement,id_evenement}</li>
+</BOUCLE_et_aussi>
+</ul>
+[<p class="pagination">(#PAGINATION)</p>]
+</div>
+</B_et_aussi>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/inclure/agenda-liste.html b/www/plugins/agenda_3_5/squelettes/inclure/agenda-liste.html
new file mode 100644 (file)
index 0000000..00273a6
--- /dev/null
@@ -0,0 +1,49 @@
+[(#REM)
+/**
+ * Plugin Agenda 4 pour Spip 3.0
+ * Licence GPL 3
+ *
+ * 2006-2011
+ * Auteurs : cf paquet.xml
+ */
+]
+#SET{affichage_duree,#ENV{affichage_duree,#CONFIG{agenda/affichage_duree,12}}}
+#SET{affichage_debut,#CONFIG{agenda/affichage_debut,#ENV{affichage_debut,date_jour}}}
+#SET{date_debut,#ENV{date_debut,#ENV{date}}|agenda_date_debut_liste{#GET{affichage_debut}}}
+#SET{date_prev,#GET{date_debut}|agenda_moisdecal{#GET{affichage_duree}|mult{-1},'Y-m-d H:i:00'}}
+#SET{date_fin,#GET{date_debut}|agenda_moisdecal{#GET{affichage_duree},'Y-m-d H:i:00'}}
+
+[(#REM) Ne pas afficher l'annee de depart, elle est dans le h2)]
+[(#GET{date_debut}|annee|unique{annee}|non)]
+<div id="agenda-liste" class="liste long evenements">
+<h2>[(#GET{date_debut}|affdate_periode{#GET{affichage_duree},#GET{affichage_debut}})]</h2>
+<B_ev>
+       <ul class="liste-items evenements">
+       <BOUCLE_ev(EVENEMENTS){!evenement_passe #GET{date_debut}}{!evenement_a_venir #GET{date_fin}}{par date_debut}{id_article?}{id_rubrique?}{id_mot?}{id_evenement_source?}>
+               [<li class="item item-header month[(#DATE_DEBUT|agenda_date_passee)fini]">(#GET{affichage_duree}|>{1}?{[(#DATE_DEBUT|nom_mois|unique{mois})[ (#DATE_DEBUT|annee|unique{annee})]]})</li>]
+               <li class="item[(#DATE_FIN|agenda_date_passee)fini]">
+                       #INCLURE{fond=inclure/resume/evenement,id_evenement}
+               </li>
+       </BOUCLE_ev>
+       </ul>
+</B_ev>
+       <p><:agenda:info_aucun_evenement:></p>
+<//B_ev>
+
+<div class="pagination">
+       #SET{hasprev,''}
+       <BOUCLE_prev(EVENEMENTS){id_article?}{id_rubrique?}{id_mot?}{id_evenement_source?}{!evenement_a_venir #GET{date_prev}}{0,1}>#SET{hasprev,1}</BOUCLE_prev>
+       #SET{hasnext,''}
+       <BOUCLE_next(EVENEMENTS){id_article?}{id_rubrique?}{id_mot?}{id_evenement_source?}{!evenement_passe #GET{date_fin}}{0,1}>#SET{hasnext,1}</BOUCLE_next>
+       [(#GET{hasprev}|=={1}|ou{#GET{hasnext}|=={1}}|oui)
+       [(#INCLURE{fond=modeles/pagination_precedent_suivant,
+       label_precedent=#GET{date_prev}|affdate_periode{#GET{affichage_duree}},
+       label_suivant=#GET{date_fin}|affdate_periode{#GET{affichage_duree}},
+       nombre_pages=#VAL{1}|plus{#GET{hasprev}|?{1,0}}|plus{#GET{hasnext}|?{1,0}},
+       page_courante=#GET{hasprev}|?{2,1},
+       url_precedent=#SELF|parametre_url{date_debut,#GET{date_prev}|affdate{Y-m-d}},
+       url_suivant=#SELF|parametre_url{date_debut,#GET{date_fin}|affdate{Y-m-d}},
+       ancre=agenda-liste,
+       separateur=' | '})]]
+</div>
+</div>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/inclure/resume/evenement.html b/www/plugins/agenda_3_5/squelettes/inclure/resume/evenement.html
new file mode 100644 (file)
index 0000000..45dbc99
--- /dev/null
@@ -0,0 +1,26 @@
+<BOUCLE_vevent(EVENEMENTS){id_evenement=#ENV{id,#ENV{id_evenement}}}{tout}>
+[(#SET{annee,#DATE_DEBUT|annee})][(#SET{mois,#DATE_DEBUT|nom_mois})][(#SET{jour,#DATE_DEBUT|jour})]
+<article class="entry evenement vevent id_#ID_EVENEMENT" id="evenement_#ID_EVENEMENT" itemscope itemtype="http://schema.org/Event">
+       <span class="banner #EDIT{date_debut}"><span class="label label-inverse"><:agenda:date_fmt_agenda_label{annee=#GET{annee},mois=#GET{mois},jour=#GET{jour}}:></span></span>
+       <strong class="h3-like summary entry-title #EDIT{titre}"><a itemprop="url" class="url" href="[(#URL_EVENEMENT)]">[(#LOGO_EVENEMENT|image_reduire{150,100}|inserer_attribut{itemprop,image})]<span itemprop="name">#TITRE</span><span
+                       class="lire-la-suite hide"><i class="icon-chevron-right" title="<:zpip:lire_la_suite|attribut_html:><:zpip:lire_la_suite_de|attribut_html:>&laquo;[(#TITRE|attribut_html)]&raquo;"></i></span></a></strong>
+       <meta itemprop="startDate" content="[(#DATE_DEBUT|date_iso)]" />
+       <meta itemprop="endDate" content="[(#DATE_FIN|date_iso)]" />
+       [<p class="info-publi"><time><i class="icon-calendar"></i> (#DATE_DEBUT|agenda_affdate_debut_fin{#DATE_FIN,#HORAIRE*,'hcal'})</time></p>]
+       #SET{intro,''}<BOUCLE_art(ARTICLES){si #ENV{sinon_intro_article,oui}|=={non}|ou{#ENV{intro}|=={non}}|non}{id_article}>#SET{intro,#INTRODUCTION{#ENV{coupe,300}}</BOUCLE_art>
+       [(#ENV{intro,oui}|=={non}|non|et{#DESCRIPTIF}|oui)[(#SET{intro,[<div class="introduction #EDIT{descriptif}" itemprop="description">(#DESCRIPTIF|sinon{#GET{intro}})</div>]})]]
+       [(#GET{intro}|ou{#LIEU}|ou{#ADRESSE}|oui)
+       <div class="entry-content">
+               #GET{intro}
+               [<p class="location" itemprop="location"><span class="lieu #EDIT{lieu}">(#LIEU)</span>[<br /><span class="adresse #EDIT{adresse}">(#ADRESSE|PtoBR)</span>]</p>]
+       </div>]
+       <B_mots>
+       <p class="meta-publi">
+       [(#INSCRIPTION|?{' ',''})<span class="inscrits"><span class="sep">|</span> <i class="icon-user" title="#NB_INSCRITS[/#PLACES(#PLACES|>{0}|oui)] <:agenda:inscrits|attribut_html:>"></i> #NB_INSCRITS[/#PLACES(#PLACES|>{0}|oui)]</span>]
+       <span class="sep">|</span> <BOUCLE_mots(MOTS){id_evenement}{par num titre,titre}{', '}><span class="category"><i class="icon-tag"></i> #TITRE</span></BOUCLE_mots>
+       </p>
+       </B_mots>
+       [(#INSCRIPTION|?{' ',''})<p class="meta-publi"><span class="inscrits"><span class="sep">|</span> <i class="icon-user" title="#NB_INSCRITS[/#PLACES(#PLACES|>{0}|oui)] <:agenda:inscrits|attribut_html:>"></i> #NB_INSCRITS[/#PLACES(#PLACES|>{0}|oui)]</span></p>]
+       <//B_mots>
+</article>
+</BOUCLE_vevent>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/navigation/page-agenda.html b/www/plugins/agenda_3_5/squelettes/navigation/page-agenda.html
new file mode 100644 (file)
index 0000000..9fc1f66
--- /dev/null
@@ -0,0 +1,22 @@
+[(#REM)
+
+  Squelette
+  (c) 2009 xxx
+  Distribue sous licence GPL
+
+]
+<BOUCLE_ev(EVENEMENTS){id_evenement}>
+#CALENDRIER_MINI{#DATE_DEBUT,date_debut,#URL_PAGE{jour},#URL_PAGE{calendrier_mini_event.json}}
+</BOUCLE_ev>
+#CALENDRIER_MINI{#DATE_DEBUT,date_debut,#URL_PAGE{jour},#URL_PAGE{calendrier_mini_event.json}}
+<//B_ev>
+
+<B_mois>
+#ANCRE_PAGINATION
+<ul class="liste-items">
+<BOUCLE_mois(EVENEMENTS){!par date_debut}{evenement_passe #ENV{date}}{id_article?}{id_rubrique?}{id_mot?}{id_evenement_source?}{fusion_par_mois date_debut}{pagination 10}>
+<li class="item">[(#SELF|parametre_url{date_debut,#DATE_DEBUT|affdate{Y-m-01}}|lien_ou_expose{#DATE_DEBUT|affdate_mois_annee{},#ENV{date_debut,''}|=={#DATE_DEBUT|affdate{Y-m-01}}})]</li>
+</BOUCLE_mois>
+</ul>
+[<p class="pagination">(#PAGINATION)</p>]
+</B_mois>
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/squelettes/style_public_plugin_agenda.html b/www/plugins/agenda_3_5/squelettes/style_public_plugin_agenda.html
new file mode 100644 (file)
index 0000000..4b54c5b
--- /dev/null
@@ -0,0 +1,26 @@
+#CACHE{0}
+[(#REM)
+
+       Ce squelette definit les styles de l'espace prive
+
+       Note: l'entete "Vary:" sert a repousser l'entete par
+       defaut "Vary: Cookie,Accept-Encoding", qui est (un peu)
+       genant en cas de "rotation du cookie de session" apres
+       un changement d'IP (effet de clignotement).
+
+       ATTENTION: il faut absolument le charset sinon Firefox croit que
+       c'est du text/html !
+]
+#CACHE{3600*100,cache-client}
+#HTTP_HEADER{Content-Type: text/css; charset=iso-8859-15}
+#HTTP_HEADER{Vary: Accept-Encoding}
+
+#SET{claire,##ENV{couleur_claire,edf3fe}}
+#SET{foncee,##ENV{couleur_foncee,3874b0}}
+#SET{left,#ENV{ltr}|choixsiegal{left,left,right}}
+#SET{right,#ENV{ltr}|choixsiegal{left,right,left}}
+
+[(#INCLURE{fond=prive/style_prive_plugin_agenda}
+       {couleur_claire=#GET{claire}}
+       {couleur_foncee=#GET{foncee}}
+       {ltr=#GET{left}})]
\ No newline at end of file
diff --git a/www/plugins/agenda_3_5/svn.revision b/www/plugins/agenda_3_5/svn.revision
new file mode 100644 (file)
index 0000000..3cdaede
--- /dev/null
@@ -0,0 +1,10 @@
+<svn_revision>
+<text_version>
+Origine: file:///home/svn/repository/spip-zone/_plugins_/agenda/trunk
+Revision: 86947
+Dernier commit: 2014-12-30 22:00:02 +0100 
+</text_version>
+<origine>file:///home/svn/repository/spip-zone/_plugins_/agenda/trunk</origine>
+<revision>86947</revision>
+<commit>2014-12-30 22:00:02 +0100 </commit>
+</svn_revision>
\ No newline at end of file
diff --git a/www/plugins/calendrier_mini-2.0/balise/calendrier_mini.php b/www/plugins/calendrier_mini-2.0/balise/calendrier_mini.php
new file mode 100644 (file)
index 0000000..36fff63
--- /dev/null
@@ -0,0 +1,102 @@
+<?php
+
+/**
+ * Balise #CALENDRIER_MINI
+ * Auteur James (c) 2006-2012
+ * Plugin pour SPIP 3.0.0
+ * Licence GNU/GPL
+ */
+
+if (!defined("_ECRIRE_INC_VERSION")) return;   #securite
+
+include_spip('calendriermini_fonctions');
+
+function balise_CALENDRIER_MINI($p) {
+       return calculer_balise_dynamique($p,'CALENDRIER_MINI', array(VAR_DATE, 'id_rubrique','id_article', 'id_mot'));
+}
+function balise_CALENDRIER_MINI_stat($args, $filtres) {
+ //les parametres passe en {...}, les filtres sont des vraiss filtres
+       return $args;
+}
+
+/**
+ * Syntaxe raccourcie du plugin
+ * #CALENDRIER_MINI
+ * #CALENDRIER_MINI{#SELF}
+ * #CALENDRIER_MINI{#SELF,#URL_PAGE{calendrier_mini.json}}
+ *
+ * Syntaxe ancienne (ou plugin agenda)
+ * #CALENDRIER_MINI{#ENV{date}}
+ * #CALENDRIER_MINI{#ENV{date},date}
+ * #CALENDRIER_MINI{#ENV{date},date,#SELF}
+ * #CALENDRIER_MINI{#ENV{date},date,#SELF,#URL_PAGE{calendrier_mini.json}}
+ *
+ * Quand l'url json est explicitée dans les arguments, la collecte automatisée de id_rubrique, id_article et id_mot est desactivée
+ * car dans ce cas il suffit simplement de les expliciter sur l'url json pour les prendre en compte
+ *
+ * @param string $date
+ *   date automatique collectee par VAR_DATE
+ * @param int $id_rubrique
+ * @param int $id_article
+ * @param int $id_mot
+ * @param null $self_or_date_or_nothing
+ * @param null $urljson_or_var_date_or_nothing
+ * @param null $self_or_nothing
+ * @param null $urljson_or_nothing
+ * @return array
+ */
+function balise_CALENDRIER_MINI_dyn($date, $id_rubrique = 0, $id_article = 0, $id_mot = 0,
+                                    $self_or_date_or_nothing = null, $urljson_or_var_date_or_nothing = null, $self_or_nothing = null, $urljson_or_nothing = null) {
+       $var_date = VAR_DATE;
+       $url = null;
+       $url_json = null;
+
+       if(!is_null($self_or_date_or_nothing)){
+               // est-ce une date ou une url ?
+               if (!function_exists('recup_date'))
+                       include_spip('inc/filtres');
+               if (!strlen($self_or_date_or_nothing) OR
+                       (preg_match(",^[\d\s:-]+$,",$self_or_date_or_nothing))
+                 AND list($annee, $mois, $jour, $heures, $minutes, $secondes) = recup_date($self_or_date_or_nothing)
+                 AND $annee){
+                       // si c'est une date on est dans l'ancienne syntaxe
+                       $date = $self_or_date_or_nothing;
+                       $var_date = $urljson_or_var_date_or_nothing;
+                       $url = $self_or_nothing;
+                       $url_json = $urljson_or_nothing;
+               }
+               else {
+                       // sinon on est sur la nouvelle syntaxe
+                       $url = $self_or_date_or_nothing;
+                       $url_json = $urljson_or_var_date_or_nothing;
+               }
+       }
+
+       $args = array(
+               'date' => $date?$date:date('Y-m'),
+               'var_date' => $var_date,
+               'self' => $url?$url:self(),
+       );
+
+       // si pas de url_json explicite, la renseigner et peupler automatiquement les
+       if (is_null($url_json)){
+               $url_json = generer_url_public("calendrier_mini.json");
+               if (!is_null($id_rubrique))
+                       $args['id_rubrique'] = $id_rubrique;
+               if (!is_null($id_article))
+                       $args['id_article'] = $id_article;
+               if (!is_null($id_mot))
+                       $args['id_mot'] = $id_mot;
+       }
+
+       if (defined('_VAR_MODE') and _VAR_MODE == "recalcul")
+               $url_json = parametre_url($url_json,'var_mode','recalcul');
+
+       $args['urljson'] = $url_json;
+
+       /* tenir compte de la langue, c'est pas de la tarte */
+       return array('formulaires/calendrier_mini', 3600, $args);
+}
+
+?>
diff --git a/www/plugins/calendrier_mini-2.0/calendrier_mini.json.html b/www/plugins/calendrier_mini-2.0/calendrier_mini.json.html
new file mode 100644 (file)
index 0000000..f41a5f7
--- /dev/null
@@ -0,0 +1,6 @@
+#HTTP_HEADER{Content-type:text/javascript;}
+[<BOUCLE_periode(ARTICLES){id_mot?}{branche?}
+       {agenda date, periode,
+       #ENV{start}|todate|affdate{Y}, #ENV{start}|todate|affdate{m}, #ENV{start}|todate|affdate{d},
+       #ENV{end}|todate|affdate{Y}, #ENV{end}|todate|affdate{m}, #ENV{end}|todate|affdate{d}}{', '}>
+[(#ARRAY{id,#ID_ARTICLE,title,[(#TITRE|html2unicode|unicode2charset|textebrut)],allDay,#EVAL{false},start,#DATE,end,#DATE,url,#URL_ARTICLE,className,calendrier-couleur6,description,[(#INTRODUCTION|html2unicode|unicode2charset)]}|json_encode)]</BOUCLE_periode>]
\ No newline at end of file
diff --git a/www/plugins/calendrier_mini-2.0/calendrier_mini.json_fonctions.php b/www/plugins/calendrier_mini-2.0/calendrier_mini.json_fonctions.php
new file mode 100644 (file)
index 0000000..de9fe80
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+/**
+ * Fichier de fonction du json du calendrier mini
+ *
+ * @package SPIP\CalendrierMini\Fonctions
+**/
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+include_spip('inc/json');
+
+/**
+ * Transforme un timestamp en date au format SQL
+ *
+ * @param int $t Timestamp
+ * @return string Date au format SQL
+**/
+function todate($t){return date('Y-m-d H:i:s',$t);}
+
+?>
diff --git a/www/plugins/calendrier_mini-2.0/calendriermini_fonctions.php b/www/plugins/calendrier_mini-2.0/calendriermini_fonctions.php
new file mode 100644 (file)
index 0000000..1cff092
--- /dev/null
@@ -0,0 +1,57 @@
+<?php\r
+\r
+/**\r
+ * Balises et critères du calendrier mini\r
+ *\r
+ * @package SPIP\CalendrierMini\Fonctions\r
+**/\r
+\r
+if (!defined("_ECRIRE_INC_VERSION")) return;   #securite\r
+\r
+if (!defined('VAR_DATE')) define('VAR_DATE', 'archives');\r
+\r
+function balise_DATE_ARCHIVES($p) {\r
+       $p->code = "_request('".VAR_DATE."')";\r
+\r
+       #$p->interdire_scripts = true;\r
+       return $p;\r
+}\r
+\r
+function critere_archives($idb, &$boucles, $crit) {\r
+       $boucle = &$boucles[$idb];\r
+       $objet = objet_type($boucle->id_table);\r
+       $date = objet_info($objet,'date');\r
+       $champ_date = "'" . $boucle->id_table ."." .\r
+       $date . "'";\r
+       $boucle->where[] = array(\r
+               'REGEXP',\r
+               $champ_date, \r
+               "sql_quote(('^' . interdire_scripts(entites_html(\$Pile[0]['".VAR_DATE."']))))"\r
+       );\r
+}\r
+\r
+/**\r
+ * Crée un array d'un intervalle de jour entre la date de début $start et la date de fin $end\r
+ * \r
+ * $start datetime SQL - La date de début\r
+ * $end datetime SQL La date de fin\r
+ */\r
+function calendriermini_intervalle($start,$end=false){\r
+       $jours = array();\r
+       $starttime = strtotime($start);\r
+       $startdate = date('Y-m-d',$starttime);\r
+       $jours[] = $startdate;\r
+       if(!$end){\r
+               return $jours;\r
+       }\r
+       $endtime = strtotime($end);\r
+       $enddate = date('Y-m-d',strtotime($end));\r
+       $starttime = $starttime + (3600*24);\r
+       while(($date_test = date('Y-m-d',$starttime)) < $enddate){\r
+               $jours[] = $date_test;\r
+               $starttime = $starttime + (3600*24);\r
+       }\r
+       $jours[] = $enddate;\r
+       return array_unique($jours);\r
+}\r
+?>\r
diff --git a/www/plugins/calendrier_mini-2.0/css/img/month_next.png b/www/plugins/calendrier_mini-2.0/css/img/month_next.png
new file mode 100644 (file)
index 0000000..75a8d7a
Binary files /dev/null and b/www/plugins/calendrier_mini-2.0/css/img/month_next.png differ
diff --git a/www/plugins/calendrier_mini-2.0/css/img/month_prev.png b/www/plugins/calendrier_mini-2.0/css/img/month_prev.png
new file mode 100644 (file)
index 0000000..4ee1fb4
Binary files /dev/null and b/www/plugins/calendrier_mini-2.0/css/img/month_prev.png differ
diff --git a/www/plugins/calendrier_mini-2.0/css/img/month_prev_next-32x16.png b/www/plugins/calendrier_mini-2.0/css/img/month_prev_next-32x16.png
new file mode 100644 (file)
index 0000000..03837b9
Binary files /dev/null and b/www/plugins/calendrier_mini-2.0/css/img/month_prev_next-32x16.png differ
diff --git a/www/plugins/calendrier_mini-2.0/css/minical.css b/www/plugins/calendrier_mini-2.0/css/minical.css
new file mode 100644 (file)
index 0000000..16d1972
--- /dev/null
@@ -0,0 +1,56 @@
+/* CSS Document */
+.calendriermini{margin-bottom:1em;}
+.calendriermini .calendar-container {height: 17em;position: relative;}
+.calendriermini .calendar-container .image_loading {position: absolute;top:0;right:0;}
+
+.js .calendriermini .calendar-container .alt {display: none;}
+
+.calendriermini table{width: 100%;font-size: 0.8em;text-align: center;margin: 0 auto;border-collapse: collapse;}
+.calendriermini table caption{margin: 0 auto;  padding:0;}
+.calendriermini table th{padding:0;}
+.calendriermini table td {width: 14%;line-height: 2em;padding:0;}
+.calendriermini table td a{display: block;background: #fff;border: 1px solid #EEE;font-weight: bold;text-decoration: none;color:inherit;}
+.calendriermini .ui-datepicker-other-month {opacity: .5; filter:Alpha(Opacity=50);}
+.calendriermini .ui-datepicker-other-month a {font-weight: normal;background:#eee;}
+.calendriermini .ui-state-highlight a {background: #EAEA98;border-color: #EAEA98;}
+.calendriermini .ui-datepicker-today a {border-color:#bbbb99;}
+
+.calendriermini table .ui-state-highlight a:hover{background: #DBB8DC;color: #636;border-color:#DBB8DC;}
+.calendriermini table .ui-state-highlight.ui-datepicker-today a:hover{border-color:#636;}
+.calendriermini span{}
+
+.calendriermini .ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+.calendriermini .ui-icon { width: 16px; height: 16px; background-repeat:no-repeat;background-position: center; }
+.calendriermini .ui-datepicker-header { position:relative; padding:.2em 0; }
+.calendriermini .ui-datepicker-prev, .calendriermini .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
+.calendriermini .ui-datepicker-prev-hover, .calendriermini .ui-datepicker-next-hover { top: 1px; }
+.calendriermini .ui-datepicker-prev { left:2px; }
+.calendriermini .ui-datepicker-next { right:2px; }
+.calendriermini .ui-datepicker-prev .ui-icon {background-position: top left;}
+.calendriermini .ui-datepicker-next .ui-icon {background-position: top right;}
+.calendriermini .ui-datepicker-prev-hover { left:1px; }
+.calendriermini .ui-datepicker-next-hover { right:1px; }
+.calendriermini .ui-datepicker-prev span, .calendriermini .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px;  }
+.calendriermini .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
+.calendriermini .ui-datepicker-title select { font-size:1em; margin:1px 0; }
+.calendriermini select.ui-datepicker-month-year {width: 100%;}
+.calendriermini select.ui-datepicker-month,
+.calendriermini select.ui-datepicker-year { width: 49%;}
+.calendriermini .ui-datepicker-buttonpane { background-image: none; margin: 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; font-size: 0.8em;}
+.calendriermini .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
+
+
+/* RTL support */
+/*
+.ui-datepicker-rtl { direction: rtl; }
+.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+*/
\ No newline at end of file
diff --git a/www/plugins/calendrier_mini-2.0/demos/minical_demo.html b/www/plugins/calendrier_mini-2.0/demos/minical_demo.html
new file mode 100644 (file)
index 0000000..024daf4
--- /dev/null
@@ -0,0 +1,91 @@
+#CACHE{7200}
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+[(#REM) Cf.: http://paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/
+]<!--[if lt IE 7 ]> <html dir="#LANG_DIR" lang="#LANG" xmlns="http://www.w3.org/1999/xhtml" xml:lang="#LANG" class="[(#LANG_DIR)][ (#LANG)] no-js ie ie6"> <![endif]-->
+<!--[if IE 7 ]> <html dir="#LANG_DIR" lang="#LANG" xmlns="http://www.w3.org/1999/xhtml" xml:lang="#LANG" class="[(#LANG_DIR)][ (#LANG)] no-js ie ie7"> <![endif]-->
+<!--[if IE 8 ]> <html dir="#LANG_DIR" lang="#LANG" xmlns="http://www.w3.org/1999/xhtml" xml:lang="#LANG" class="[(#LANG_DIR)][ (#LANG)] no-js ie ie8"> <![endif]-->
+<!--[if IE 9 ]> <html dir="#LANG_DIR" lang="#LANG" xmlns="http://www.w3.org/1999/xhtml" xml:lang="#LANG" class="[(#LANG_DIR)][ (#LANG)] no-js ie ie9"> <![endif]-->
+<!--[if (gt IE 9)|!(IE)]><!--> <html dir="#LANG_DIR" lang="#LANG" xmlns="http://www.w3.org/1999/xhtml" xml:lang="#LANG" class="[(#LANG_DIR)][ (#LANG)] no-js"> <!--<![endif]-->
+<head>
+<script type='text/javascript'>/*<![CDATA[*/(function(H){H.className=H.className.replace(/\bno-js\b/,'js')})(document.documentElement);/*]]>*/</script>
+       <title>Exemple #[(#REM)]CALENDRIER_MINI</title>
+       <INCLURE{fond=squelettes-dist/inclure/head} />
+       <link rel="stylesheet" href="[(#CHEMIN{minical.css}|direction_css)]" type="text/css" media="projection, screen, tv" />
+</head>
+
+<body class="pas_surlignable page_demo_minical">
+<div class="page">
+       <INCLURE{fond=inclure/header} />
+       <INCLURE{fond=inclure/nav,env} />
+
+       <div class="main">
+               [(#REM) Contenu principal : contenu de l'article ]
+               <div class="wrapper hfeed">
+               <div class="content hentry" id="content">
+
+                       [(#REM) Les deux derniers articles ]
+                       <B_articles_recents>
+                       <div class="liste-articles">
+                               [(#ANCRE_PAGINATION)]
+                               [(#ENV{debut_articles_recents}|?{'',' '})<h2 class="invisible"><:articles_recents:>[ - (#DATE_ARCHIVES|affdate)]</h2>]
+                               <ul>
+                                       <BOUCLE_articles_recents(ARTICLES) {pagination} {par date}{inverse} {archives ?}{branche ?} {lang ?}>
+                                       <li>
+                                               [(#LOGO_ARTICLE_RUBRIQUE|#URL_ARTICLE|image_reduire{150,100})]
+                                               [<p class="surtitre">(#SURTITRE)</p>]
+                                               <h3 class="titre"><a href="#URL_ARTICLE">#TITRE</a></h3>
+                                               [<p class="soustitre">(#SOUSTITRE)</p>]
+                                               <small>[(#DATE|affdate)][, <:par_auteur:> (#LESAUTEURS)]</small>
+                                               [<div class="texte">(#INTRODUCTION)</div>]
+                                               <br class="nettoyeur" />
+                                       </li>
+                                       </BOUCLE_articles_recents>
+                               </ul>
+                               [<p class="pagination">(#PAGINATION)</p>]
+                       </div>
+                       </B_articles_recents>[
+                       <h2>Rien pour (#DATE_ARCHIVES|affdate)[ (#ENV{id_rubrique}?{dans ce secteur})]</h2>
+                       ]<//B_articles_recents>
+
+                       <br class="nettoyeur" />
+
+               </div><!--.content-->
+               </div><!--.wrapper-->
+
+               [(#REM) Menu de navigation laterale ]
+               <div class="aside">
+                       #CALENDRIER_MINI
+                       <B_categories>
+                       <div id='categories' class="rubriques">
+                               <h2 class="menu-titre"><:categories|ucfirst:></h2>
+                               <ul>
+                                       <BOUCLE_categories(RUBRIQUES){racine}>[
+                                       <li>[(#EXPOSE{<b>})]
+                                       <a href="(#SELF|parametre_url{id_rubrique,#ID_RUBRIQUE})">#TITRE</a>
+                                       [(#EXPOSE{</b>})]</li>
+                                       ]</BOUCLE_categories>
+                               </ul>
+                       </div>
+                       </B_categories>
+          
+                       [(#MODELE{archives_mensuelles,id_rubrique,archives,self=#SELF,lang})]
+          
+                       [(#MODELE{select_archives_mensuelles,id_rubrique,archives,self=#SELF,lang})]
+          
+                       [<div>(#MENU_LANG)<small>(<a href="[(#SELF|parametre_url{lang,''})]">toutes les langues</a>)</small><br class="nettoyeur" /></div>]
+       
+                       <div id="links" class="syndic">
+                               <h2 class="menu-titre"><:liens|ucfirst:></h2>
+                               <ul>
+                                       <li><a href="http://zone.spip.org/trac/spip-zone/">Spip Zone</a></li>
+                                       <li><a href="#URL_PAGE{minical_doc}"><:documentation|ucfirst:></a></li>
+                               </ul>
+                       </div>
+               </div><!--.aside-->
+       </div><!--.main-->
+
+       <INCLURE{fond=inclure/footer,self=#SELF} />
+
+</div><!--.page-->
+</body>
+</html>
\ No newline at end of file
diff --git a/www/plugins/calendrier_mini-2.0/formulaires/calendrier_mini.html b/www/plugins/calendrier_mini-2.0/formulaires/calendrier_mini.html
new file mode 100644 (file)
index 0000000..f3f29c5
--- /dev/null
@@ -0,0 +1,62 @@
+[(#SET{urljson,[(#ENV{urljson}
+                                               |parametre_url{id_rubrique,#ENV{id_rubrique,#ENV{urljson}|parametre_url{id_rubrique}}}
+                                               |parametre_url{id_article,#ENV{id_article,#ENV{urljson}|parametre_url{id_article}}}
+                                               |parametre_url{id_mot,#ENV{id_mot,#ENV{urljson}|parametre_url{id_mot}}}
+                                               |parametre_url{lang,#ENV{lang,#ENV{urljson}|parametre_url{lang}}}
+                                               )]})
+]<div id="calendar" class="calendriermini minicalendar">
+       <h2 class="menu-titre"><:icone_calendrier:></h2>
+       <div class="calendar-container"
+                                       data-json="#GET{urljson}"
+                                       data-year="[(#ENV{date}|affdate{Y})]"
+                                       data-month="[(#ENV{date}|affdate{n})]"
+                                       data-url="#ENV{self}"
+                                       data-vardate="#ENV{var_date}"
+                                       >[(#REM)
+               Contenu alternatif statique pour les Bot et les utilisateurs sans JS
+               ]<div class="alt">
+                       #SET{start,#ENV{date}|affdate{Y-m-01}|strtotime}
+                       #SET{end,#VAL{Y-m-01}|date{#GET{start}|plus{2764800}}|strtotime}
+                       [(#SET{url,#INCLURE{fond=#GET{urljson}|parametre_url{page},
+                                           id_rubrique=#GET{urljson}|parametre_url{id_rubrique},
+                                           id_article=#GET{urljson}|parametre_url{id_articlee},
+                                           id_mot=#GET{urljson}|parametre_url{id_mot},
+                                           lang=#GET{urljson}|parametre_url{lang},
+                                           start=#GET{start},
+                                           end=#GET{end}}})]
+                       <strong>[(#ENV{date}|affdate_mois_annee)]&nbsp;:</strong>
+                       <B_alt>
+                       <ul class="liste-items">
+                       <BOUCLE_alt(DATA){source json,#GET{url}}><BOUCLE_dates(DATA){source table,#VALEUR{start}|calendriermini_intervalle{#VALEUR{end}}}>[
+                               <li class="item"><a href="[(#ENV{self}|parametre_url{#ENV{var_date},#VALEUR})]">(#VALEUR|affdate|unique)</a></li>
+                               ]</BOUCLE_dates></BOUCLE_alt>
+                       </ul>
+                       </B_alt>
+                       <p><:minical:aucune_date:></p>
+                       <//B_alt>
+                       <p class="pagination">
+                       #SET{prev,#VAL{Y-m-01}|date{#GET{start}|moins{172800}}}
+                       #SET{next,#VAL{Y-m-01}|date{#GET{end}}}
+                       <a href="[(#ENV{self}|parametre_url{#ENV{var_date},#GET{prev}|affdate{Y-m-d}})]">[(#GET{prev}|affdate_mois_annee)]</a>
+                       | <a href="[(#ENV{self}|parametre_url{#ENV{var_date},#GET{next}|affdate{Y-m-d}})]">[(#GET{next}|affdate_mois_annee)]</a>
+                       </p>
+               </div>
+       </div>
+</div>[
+(#REM) --------------------------------------------------------------------------------------------------
+
+Chargement du javascript du mini-calendrier
+
+]<script type='text/javascript'>/*<![CDATA[*/
+if (window.jQuery){jQuery(function(){
+               jQuery.getScript('[(#PRODUIRE{fond=javascript/calendrier_mini.js,lang=#ENV{lang}}|compacte)]',function(){minical.init('.minicalendar .calendar-container');});
+});}/*]]>*/</script>[
+(#REM) --------------------------------------------------------------------------------------------------
+
+Chargement de la css que l'on importe inline pour eviter un hit (performance)
++ fixer la css de background pour les boutons precedent/suivant
+
+]<style type="text/css">
+[(#INCLURE{css/minical.css}|compacte{css})]
+.calendriermini .ui-datepicker-header .ui-icon {background-image: url("#CHEMIN{css/img/month_prev_next-32x16.png}");}
+</style>
diff --git a/www/plugins/calendrier_mini-2.0/formulaires/configurer_calendriermini.html b/www/plugins/calendrier_mini-2.0/formulaires/configurer_calendriermini.html
new file mode 100644 (file)
index 0000000..fa4cc28
--- /dev/null
@@ -0,0 +1,79 @@
+<div class="formulaire_spip formulaire_configurer formulaire_#FORM">
+<h3 class="titrem"><:minical:config_titre_calendriermini:></h3>
+
+[<p class="reponse_formulaire reponse_formulaire_ok">(#ENV*{message_ok})</p>]
+[<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV*{message_erreur})</p>]
+
+<form method="post" action="#ENV{action}"><div>
+       #ACTION_FORMULAIRE{#ENV{action}}
+       <ul>
+               #SET{fl,minical}
+               #SET{name,jour1}#SET{obli,''}#SET{defaut,1}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}}
+               <li class="editer editer_[(#GET{name})][ (#GET{obli})][ (#GET{erreurs}|oui)erreur]">
+                       <label for="#GET{name}">[(#GET{fl}|concat{':label_',#GET{name}}|_T)]</label>[
+                       <span class='erreur_message'>(#GET{erreurs})</span>
+                       ]<select name="#GET{name}" class="select" id="#GET{name}">
+                               <BOUCLE_jours(DATA){enum 1,7}>
+                               #SET{val,#VALEUR|moins{1}}
+                               <option value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)selected="selected"]>[(#VAL{date_jour_}|concat{#VALEUR}|_T)]</option>
+                               </BOUCLE_jours>
+                       </select>
+               </li>
+               #SET{name,format_jour}#SET{defaut,initiale}#SET{obli,''}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}}
+               <li class="editer editer_[(#GET{name})][ (#GET{obli})][ (#GET{erreurs}|oui)erreur]">
+                       <label>[(#GET{fl}|concat{':label_',#GET{name}}|_T)]</label>[
+                       <span class='erreur_message'>(#GET{erreurs})</span>
+                       ]
+                       #SET{val,initiale}
+                       <div class="choix">
+                               <input type="radio" name="#GET{name}" class="radio" id="#GET{name}_#GET{val}" value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)checked="checked"] />
+                               <label for="#GET{name}_#GET{val}">[(#GET{fl}|concat{':label_',#GET{name},'_',#GET{val}}|_T)] (<:date_jour_1_initiale:> <:date_jour_2_initiale:> <:date_jour_3_initiale:> <:date_jour_4_initiale:>
+                                       <:date_jour_5_initiale:> <:date_jour_6_initiale:> <:date_jour_7_initiale:>)</label>
+                       </div>
+                       #SET{val,abbr}
+                       <div class="choix">
+                               <input type="radio" name="#GET{name}" class="radio" id="#GET{name}_#GET{val}" value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)checked="checked"] />
+                               <label for="#GET{name}_#GET{val}">[(#GET{fl}|concat{':label_',#GET{name},'_',#GET{val}}|_T)] (<:date_jour_1_abbr:> <:date_jour_2_abbr:> <:date_jour_3_abbr:> <:date_jour_4_abbr:>
+                                                                       <:date_jour_5_abbr:> <:date_jour_6_abbr:> <:date_jour_7_abbr:>)</label>
+                       </div>
+               </li>
+               #SET{name,affichage_hors_mois}#SET{defaut,1}#SET{obli,''}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}}
+               <li class="editer editer_[(#GET{name})][ (#GET{obli})][ (#GET{erreurs}|oui)erreur]">
+                       <label>[(#GET{fl}|concat{':label_',#GET{name}}|_T)]</label>[
+                       <span class='erreur_message'>(#GET{erreurs})</span>
+                       ]
+                       #SET{val,1}
+                       <div class="choix">
+                               <input type="radio" name="#GET{name}" class="radio" id="#GET{name}_#GET{val}" value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)checked="checked"] />
+                               <label for="#GET{name}_#GET{val}">[(#GET{fl}|concat{':label_',#GET{name},'_',#GET{val}}|_T)]</label>
+                       </div>
+                       #SET{val,0}
+                       <div class="choix">
+                               <input type="radio" name="#GET{name}" class="radio" id="#GET{name}_#GET{val}" value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)checked="checked"] />
+                               <label for="#GET{name}_#GET{val}">[(#GET{fl}|concat{':label_',#GET{name},'_',#GET{val}}|_T)]</label>
+                       </div>
+               </li>
+               #SET{name,changement_rapide}#SET{defaut,1}#SET{obli,''}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}}
+               <li class="editer editer_[(#GET{name})][ (#GET{obli})][ (#GET{erreurs}|oui)erreur]">
+                       <label>[(#GET{fl}|concat{':label_',#GET{name}}|_T)]</label>[
+                       <span class='erreur_message'>(#GET{erreurs})</span>
+                       ]
+                       #SET{val,1}
+                       <div class="choix">
+                               <input type="radio" name="#GET{name}" class="radio" id="#GET{name}_#GET{val}" value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)checked="checked"] />
+                               <label for="#GET{name}_#GET{val}">[(#GET{fl}|concat{':label_',#GET{name},'_',#GET{val}}|_T)]</label>
+                       </div>
+                       #SET{val,0}
+                       <div class="choix">
+                               <input type="radio" name="#GET{name}" class="radio" id="#GET{name}_#GET{val}" value="#GET{val}"[(#ENV{#GET{name},#GET{defaut}}|=={#GET{val}}|oui)checked="checked"] />
+                               <label for="#GET{name}_#GET{val}">[(#GET{fl}|concat{':label_',#GET{name},'_',#GET{val}}|_T)]</label>
+                       </div>
+               </li>
+
+
+       </ul>
+
+       <p class='boutons'><span class='image_loading'>&nbsp;</span>
+       <input type='submit' class='submit' value='<:bouton_enregistrer:>' /></p>
+</div></form>
+</div>
\ No newline at end of file
diff --git a/www/plugins/calendrier_mini-2.0/javascript/calendrier_mini.js.html b/www/plugins/calendrier_mini-2.0/javascript/calendrier_mini.js.html
new file mode 100644 (file)
index 0000000..6393793
--- /dev/null
@@ -0,0 +1,208 @@
+#HTTP_HEADER{Content-type:text/javascript}
+if (!jQuery.fn.datepicker){
+[(#INCLURE{javascript/ui/core.js}|sinon{#INCLURE{javascript/ui/jquery.ui.core.js}})]
+[(#INCLURE{javascript/ui/widget.js}|sinon{#INCLURE{javascript/ui/jquery.ui.widget.js}})]
+[(#INCLURE{javascript/ui/datepicker.js}|sinon{#INCLURE{javascript/ui/jquery.ui.datepicker.js}})]
+}
+if (!jQuery.fn.multiDatesPicker){
+#INCLURE{javascript/jquery-ui.multidatespicker.js}
+}
+[(#REM)<script>]
+var ajax_image_searching = "<img src='#CHEMIN{images/searching.gif}' width='16' height='16' />";
+var minical = {
+       options: {
+               buttonText: '<:afficher_calendrier|texte_script:>',
+               buttonImage: '#CHEMIN_IMAGE{calendrier-16.png}',
+               buttonImageOnly: true,
+               prevText: '<:precedent|texte_script:>',
+               nextText: '<:suivant|texte_script:>',
+               currentText: '<:date_aujourdhui|texte_script:>',
+               closeText: '<:bouton_fermer|texte_script:>',
+               monthNames: [
+                       '<:date_mois_1|texte_script:>','<:date_mois_2|texte_script:>','<:date_mois_3|texte_script:>','<:date_mois_4|texte_script:>','<:date_mois_5|texte_script:>','<:date_mois_6|texte_script:>',
+                       '<:date_mois_7|texte_script:>','<:date_mois_8|texte_script:>','<:date_mois_9|texte_script:>','<:date_mois_10|texte_script:>','<:date_mois_11|texte_script:>','<:date_mois_12|texte_script:>'],
+               monthNamesShort: [
+                       '<:date_mois_1_abbr|texte_script:>','<:date_mois_2_abbr|texte_script:>','<:date_mois_3_abbr|texte_script:>','<:date_mois_4_abbr|texte_script:>','<:date_mois_5_abbr|texte_script:>','<:date_mois_6_abbr|texte_script:>',
+                       '<:date_mois_7_abbr|texte_script:>','<:date_mois_8_abbr|texte_script:>','<:date_mois_9_abbr|texte_script:>','<:date_mois_10_abbr|texte_script:>','<:date_mois_11_abbr|texte_script:>','<:date_mois_12_abbr|texte_script:>'],
+               dayNames: [
+                       '<:date_jour_1|texte_script:>','<:date_jour_2|texte_script:>','<:date_jour_3|texte_script:>','<:date_jour_4:|texte_script:>',
+                       '<:date_jour_5|texte_script:>','<:date_jour_6|texte_script:>','<:date_jour_7|texte_script:>'],
+               dayNamesShort: [
+                       '<:date_jour_1_abbr|texte_script:>','<:date_jour_2_abbr|texte_script:>','<:date_jour_3_abbr|texte_script:>','<:date_jour_4_abbr|texte_script:>',
+                       '<:date_jour_5_abbr|texte_script:>','<:date_jour_6_abbr|texte_script:>','<:date_jour_7_abbr|texte_script:>'],
+               dayNamesMin: [
+                       '<:date_jour_1_initiale|texte_script:>','<:date_jour_2_initiale|texte_script:>','<:date_jour_3_initiale|texte_script:>','<:date_jour_4_initiale|texte_script:>',
+                       '<:date_jour_5_initiale|texte_script:>','<:date_jour_6_initiale|texte_script:>','<:date_jour_7_initiale|texte_script:>'],
+               dateFormat: 'yy-mm-dd',
+               firstDay: #CONFIG{calendriermini/jour1,1},
+               isRTL: [(#ENV{lang}|lang_dir|=={rtl}|?{true,false})],
+               changeMonth: [(#CONFIG{calendriermini/changement_rapide,1}|?{true,false})],
+               changeYear: [(#CONFIG{calendriermini/changement_rapide,1}|?{true,false})],
+               showOtherMonths: [(#CONFIG{calendriermini/affichage_hors_mois,1}|?{true,false})],
+               selectOtherMonths: [(#CONFIG{calendriermini/affichage_hors_mois,1}|?{true,false})]
+       },
+
+       add_tooltip_and_class: function($this,date,id,tooltip,className){
+               if (!$this.tooltip)
+                       $this.tooltip = {};
+               if (!$this.cn)
+                       $this.cn = {};
+
+               var d = date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate();
+               if (!$this.tooltip[d])
+                       $this.tooltip[d] = {};
+               tooltip = tooltip.replace(/"/g,'&quot;'); // echapper les " pour ne pas casser le html (secu, supprimer le html en amont !)
+               $this.tooltip[d][id] = tooltip;
+
+               if (className && typeof className != "undefined"){
+                       if (!$this.cn[d])
+                               $this.cn[d] = {};
+                       $this.cn[d][id] = className;
+               }
+       },
+
+       set_events: function(me,start,end,data){
+               var dates = [];
+               var $this = me.get(0);
+
+               if (data.length){
+                       var d,datebegin,dateend;
+                       for(var j=0;j<data.length;j++){
+                               d = data[j].start.split(" ");
+                               d = d[0].split("-");
+                               d = new Date(d[0],d[1]-1,d[2]);
+                               dates.push(d);
+                               minical.add_tooltip_and_class($this,d,data[j].id,data[j].title,data[j].className);
+
+                               if(data[j].end){
+                                       /* prendre la plus grande date de debut entre debut a afficher et l'interval donne */
+                                       datebegin = Math.max(start * 1000,d.getTime());
+                                       d = data[j].end.split(" ");
+                                       d = d[0].split("-");
+                                       d = new Date(d[0],d[1]-1,d[2]);
+                                       /* prendre la plus petite date de fin entre fin a afficher et l'interval donne */
+                                       dateend = Math.min(end * 1000, d.getTime());
+                                       if (dateend>datebegin){
+                                               dates.push(d);
+                                               minical.add_tooltip_and_class($this,d,data[j].id,data[j].title,data[j].className);
+                                               datebegin = datebegin+(3600*24*1000);
+                                               while(datebegin < dateend){
+                                                       d = new Date(datebegin);
+                                                       dates.push(d);
+                                                       minical.add_tooltip_and_class($this,d,data[j].id,data[j].title,data[j].className);
+                                                       datebegin = datebegin+(3600*24*1000);
+                                               }
+                                       }
+                               }
+                       }
+
+                       me.multiDatesPicker('addDates', dates);
+                       // toujours retirer la classe active qui n'a pas de sens pour l'affichage
+                       jQuery('.ui-state-active',me).removeClass('ui-state-active');
+               }
+       },
+
+       before_show_day: function(date) {
+               var d = date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate();
+               var c = [true, ''];
+               if (this.cn && this.cn[d]){
+                       for (var i in this.cn[d])
+                               c[1] += ' ' + this.cn[d][i];
+               }
+
+               if (this.tooltip && this.tooltip[d]){
+                       var glue = '';
+                       c[2] = '';
+                       for (var i in this.tooltip[d]) {
+                               c[2] += glue + this.tooltip[d][i];
+                               glue = "\n";
+      }
+               }
+               return c;
+       },
+
+       change_month_year: function(year, month, inst){
+               var me = inst;
+               //console.log("change_month_year:"+year+"/"+month);
+               if (typeof inst.input != "undefined")
+                       me = inst.input;
+               //console.log(me);
+               var t = new Date(year,month-1,1);
+               t = parseInt(t.getTime()/1000);
+               var start = t-7*24*3600;
+               var end = t+38*24*3600;
+               // stocker les year/month deja vus pour ne pas les recharger 2 fois quand on va et vient
+               var o = me.get(0);
+               if (typeof o.dateseen == "undefined")
+                       o.dateseen = {};
+               if (!o.dateseen[year+"/"+month]){
+                       //console.log(o.dateseen);
+                       o.dateseen[year+"/"+month] = true;
+                       minical.show_loading(me);
+                       jQuery.ajax({
+                               url:jQuery(me).attr("data-json"),
+                               data:{start:start,end:end},
+                               success:function(data, textStatus, jqXHR){
+                                       data = eval(data);
+                                       minical.set_events(me,start,end,data);
+                                       minical.hide_loading(me);
+                               }
+                       });
+               }
+               inst.currentDay=0; // annuler la date active : pas de sens ici
+       },
+       show_loading : function(me){
+               me.animateLoading();
+       },
+       hide_loading : function(me) {
+               me.endLoading(true);
+       },
+
+       scripts_loaded: {widget:false,datepicker:false,multidatepicker:false},
+       init : function(selecteur){
+               //if (!minical.scripts_loaded.widget || !minical.scripts_loaded.datepicker || !minical.scripts_loaded.multidatepicker)
+               //      return;
+               [(#CONFIG{calendriermini/format_jour,initiale}|=={abbr}|oui)
+               minical.options.dayNamesMin = minical.options.dayNamesShort;
+               ]
+               jQuery(function(){
+                       jQuery(selecteur).not('.loaded')
+                               .addClass('loaded')
+                               .multiDatesPicker(jQuery.extend(minical.options,
+                                       {
+                                               showButtonPanel: true,
+                                               prevText: '<:minical:mois_precedent|texte_script:>',
+                                               nextText: '<:minical:mois_suivant|texte_script:>',
+                                               create: minical.create,
+                                               beforeShowDay: minical.before_show_day,
+                                               onChangeMonthYear: minical.change_month_year,
+                                               onSelect: minical.on_select
+                                       })
+                               )
+                               .each(function(){
+                                       var me=jQuery(this);
+                                       // se mettre a la date demandee (pour afficher le bon mois)
+                                       me.datepicker("setDate" , me.attr('data-year')+"-"+me.attr('data-month')+"-1");
+                                       // et retirer la classe active qui n'a pas de sens pour l'affichage
+                                       jQuery('.ui-state-active',me).removeClass('ui-state-active');
+                                       minical.change_month_year(me.attr('data-year'),me.attr('data-month'),me);
+                               })
+                               .trigger('miniCalendarLoaded')
+                               .find(".alt").remove();
+               });
+       },
+       on_select : function(dateText, inst){
+               var me = inst.input;
+               if (dateText) {
+                       // annuler la selection !
+                       me.multiDatesPicker('toggleDate', dateText);
+                       var actif = (me.multiDatesPicker('gotDate', dateText) !==false);
+                       if (actif){
+                               var url = me.attr('data-url');
+                               url = parametre_url(url,me.attr('data-vardate'),dateText);
+                               //console.log(url);
+                               window.location = url;
+                       }
+               }
+       }
+}
diff --git a/www/plugins/calendrier_mini-2.0/javascript/jquery-ui.multidatespicker.js b/www/plugins/calendrier_mini-2.0/javascript/jquery-ui.multidatespicker.js
new file mode 100755 (executable)
index 0000000..a5787b0
--- /dev/null
@@ -0,0 +1,460 @@
+/*\r
+ * MultiDatesPicker v1.6.1\r
+ * http://multidatespickr.sourceforge.net/\r
+ * \r
+ * Copyright 2011, Luca Lauretta\r
+ * Dual licensed under the MIT or GPL version 2 licenses.\r
+ */\r
+(function( $ ){\r
+       $.extend($.ui, { multiDatesPicker: { version: "1.6.1" } });\r
+       \r
+       $.fn.multiDatesPicker = function(method) {\r
+               var mdp_arguments = arguments;\r
+               var ret = this;\r
+               var today_date = new Date();\r
+               var day_zero = new Date(0);\r
+               var mdp_events = {};\r
+               \r
+               function removeDate(date, type) {\r
+                       if(!type) type = 'picked';\r
+                       date = dateConvert.call(this, date);\r
+                       for(var i in this.multiDatesPicker.dates[type])\r
+                               if(!methods.compareDates(this.multiDatesPicker.dates[type][i], date))\r
+                                       return this.multiDatesPicker.dates[type].splice(i, 1).pop();\r
+               }\r
+               function removeIndex(index, type) {\r
+                       if(!type) type = 'picked';\r
+                       return this.multiDatesPicker.dates[type].splice(index, 1).pop();\r
+               }\r
+               function addDate(date, type, no_sort) {\r
+                       if(!type) type = 'picked';\r
+                       date = dateConvert.call(this, date);\r
+                       \r
+                       // @todo: use jQuery UI datepicker method instead\r
+                       date.setHours(0);\r
+                       date.setMinutes(0);\r
+                       date.setSeconds(0);\r
+                       date.setMilliseconds(0);\r
+                       \r
+                       if (methods.gotDate.call(this, date, type) === false) {\r
+                               this.multiDatesPicker.dates[type].push(date);\r
+                               if(!no_sort) this.multiDatesPicker.dates[type].sort(methods.compareDates);\r
+                       } \r
+               }\r
+               function sortDates(type) {\r
+                       if(!type) type = 'picked';\r
+                       this.multiDatesPicker.dates[type].sort(methods.compareDates);\r
+               }\r
+               function dateConvert(date, desired_type, date_format) {\r
+                       if(!desired_type) desired_type = 'object';/*\r
+                       if(!date_format && (typeof date == 'string')) {\r
+                               date_format = $(this).datepicker('option', 'dateFormat');\r
+                               if(!date_format) date_format = $.datepicker._defaults.dateFormat;\r
+                       }\r
+                       */\r
+                       return methods.dateConvert.call(this, date, desired_type, date_format);\r
+               }\r
+               \r
+               var methods = {\r
+                       init : function( options ) {\r
+                               var $this = $(this);\r
+                               this.multiDatesPicker.changed = false;\r
+                               \r
+                               var mdp_events = {\r
+                                       beforeShow: function(input, inst) {\r
+                                               this.multiDatesPicker.changed = false;\r
+                                               if(this.multiDatesPicker.originalBeforeShow) \r
+                                                       this.multiDatesPicker.originalBeforeShow.call(this, input, inst);\r
+                                       },\r
+                                       onSelect : function(dateText, inst) {\r
+                                               var $this = $(this);\r
+                                               this.multiDatesPicker.changed = true;\r
+                                               \r
+                                               if (dateText) {\r
+                                                       $this.multiDatesPicker('toggleDate', dateText);\r
+                                               }\r
+                                               \r
+                                               if (this.multiDatesPicker.mode == 'normal' && this.multiDatesPicker.dates.picked.length > 0 && this.multiDatesPicker.pickableRange) {\r
+                                                       var min_date = this.multiDatesPicker.dates.picked[0],\r
+                                                               max_date = new Date(min_date.getTime());\r
+                                                       \r
+                                                       methods.sumDays(max_date, this.multiDatesPicker.pickableRange-1);\r
+                                                               \r
+                                                       // counts the number of disabled dates in the range\r
+                                                       if(this.multiDatesPicker.adjustRangeToDisabled) {\r
+                                                               var c_disabled, \r
+                                                                       disabled = this.multiDatesPicker.dates.disabled.slice(0);\r
+                                                               do {\r
+                                                                       c_disabled = 0;\r
+                                                                       for(var i = 0; i < disabled.length; i++) {\r
+                                                                               if(disabled[i].getTime() <= max_date.getTime()) {\r
+                                                                                       if((min_date.getTime() <= disabled[i].getTime()) && (disabled[i].getTime() <= max_date.getTime()) ) {\r
+                                                                                               c_disabled++;\r
+                                                                                       }\r
+                                                                                       disabled.splice(i, 1);\r
+                                                                                       i--;\r
+                                                                               }\r
+                                                                       }\r
+                                                                       max_date.setDate(max_date.getDate() + c_disabled);\r
+                                                               } while(c_disabled != 0);\r
+                                                       }\r
+                                                       \r
+                                                       if(this.multiDatesPicker.maxDate && (max_date > this.multiDatesPicker.maxDate))\r
+                                                               max_date = this.multiDatesPicker.maxDate;\r
+                                                       \r
+                                                       $this\r
+                                                               .datepicker("option", "minDate", min_date)\r
+                                                               .datepicker("option", "maxDate", max_date);\r
+                                               } else {\r
+                                                       $this\r
+                                                               .datepicker("option", "minDate", this.multiDatesPicker.minDate)\r
+                                                               .datepicker("option", "maxDate", this.multiDatesPicker.maxDate);\r
+                                               }\r
+                                               \r
+                                               if(this.tagName == 'INPUT') { // for inputs\r
+                                                       $this.val(\r
+                                                               $this.multiDatesPicker('getDates', 'string')\r
+                                                       );\r
+                                               }\r
+                                               \r
+                                               if(this.multiDatesPicker.originalOnSelect && dateText)\r
+                                                       this.multiDatesPicker.originalOnSelect.call(this, dateText, inst);\r
+                                               \r
+                                               // thanks to bibendus83 -> http://sourceforge.net/tracker/?func=detail&atid=1495384&aid=3403159&group_id=358205\r
+                                               if ($this.datepicker('option', 'altField') != undefined && $this.datepicker('option', 'altField') != "") {\r
+                                                       $($this.datepicker('option', 'altField')).val(\r
+                                                               $this.multiDatesPicker('getDates', 'string')\r
+                                                       );\r
+                                               }\r
+                                       },\r
+                                       beforeShowDay : function(date) {\r
+                                               var $this = $(this),\r
+                                                       gotThisDate = $this.multiDatesPicker('gotDate', date) !== false,\r
+                                                       isDisabledCalendar = $this.datepicker('option', 'disabled'),\r
+                                                       isDisabledDate = $this.multiDatesPicker('gotDate', date, 'disabled') !== false,\r
+                                                       areAllSelected = this.multiDatesPicker.maxPicks == this.multiDatesPicker.dates.picked.length;\r
+                                               \r
+                                               var custom = [true, ''];\r
+                                               if(this.multiDatesPicker.originalBeforeShowDay)\r
+                                                       custom = this.multiDatesPicker.originalBeforeShowDay.call(this, date);\r
+                                               \r
+                                               var highlight_class = (gotThisDate ? 'ui-state-highlight' : '') + ((custom[1] && gotThisDate) ? ' ' : '') + custom[1];\r
+                                               var selectable_date = !(isDisabledCalendar || isDisabledDate || (areAllSelected && !highlight_class));\r
+                                               custom[0] = selectable_date && custom[0];\r
+                                               custom[1] = highlight_class;\r
+                                               return custom;\r
+                                       },\r
+                                       onClose: function(dateText, inst) {\r
+                                               if(this.tagName == 'INPUT' && this.multiDatesPicker.changed) {\r
+                                                       $(inst.dpDiv[0]).stop(false,true);\r
+                                                       setTimeout('$("#'+inst.id+'").datepicker("show")',50);\r
+                                               }\r
+                                               if(this.multiDatesPicker.originalOnClose) this.multiDatesPicker.originalOnClose.call(this, dateText, inst);\r
+                                       }\r
+                               };\r
+                               \r
+                               if(options) {\r
+                                       this.multiDatesPicker.originalBeforeShow = options.beforeShow;\r
+                                       this.multiDatesPicker.originalOnSelect = options.onSelect;\r
+                                       this.multiDatesPicker.originalBeforeShowDay = options.beforeShowDay;\r
+                                       this.multiDatesPicker.originalOnClose = options.onClose;\r
+                                       \r
+                                       $this.datepicker(options);\r
+                                       \r
+                                       this.multiDatesPicker.minDate = $.datepicker._determineDate(this, options.minDate, null);\r
+                                       this.multiDatesPicker.maxDate = $.datepicker._determineDate(this, options.maxDate, null);\r
+                                       \r
+                                       if(options.addDates) methods.addDates.call(this, options.addDates);\r
+                                       if(options.addDisabledDates)\r
+                                               methods.addDates.call(this, options.addDisabledDates, 'disabled');\r
+                                       \r
+                                       methods.setMode.call(this, options);\r
+                               } else {\r
+                                       $this.datepicker();\r
+                               }\r
+                               \r
+                               $this.datepicker('option', mdp_events);\r
+                               \r
+                               if(this.tagName == 'INPUT') $this.val($this.multiDatesPicker('getDates', 'string'));\r
+                               \r
+                               // Fixes the altField filled with defaultDate by default\r
+                               var altFieldOption = $this.datepicker('option', 'altField');\r
+                               if (altFieldOption) $(altFieldOption).val($this.multiDatesPicker('getDates', 'string'));\r
+                       },\r
+                       compareDates : function(date1, date2) {\r
+                               date1 = dateConvert.call(this, date1);\r
+                               date2 = dateConvert.call(this, date2);\r
+                               // return > 0 means date1 is later than date2 \r
+                               // return == 0 means date1 is the same day as date2 \r
+                               // return < 0 means date1 is earlier than date2 \r
+                               var diff = date1.getFullYear() - date2.getFullYear();\r
+                               if(!diff) {\r
+                                       diff = date1.getMonth() - date2.getMonth();\r
+                                       if(!diff) \r
+                                               diff = date1.getDate() - date2.getDate();\r
+                               }\r
+                               return diff;\r
+                       },\r
+                       sumDays : function( date, n_days ) {\r
+                               var origDateType = typeof date;\r
+                               obj_date = dateConvert.call(this, date);\r
+                               obj_date.setDate(obj_date.getDate() + n_days);\r
+                               return dateConvert.call(this, obj_date, origDateType);\r
+                       },\r
+                       dateConvert : function( date, desired_format, dateFormat ) {\r
+                               var from_format = typeof date;\r
+                               \r
+                               if(from_format == desired_format) {\r
+                                       if(from_format == 'object') {\r
+                                               try {\r
+                                                       date.getTime();\r
+                                               } catch (e) {\r
+                                                       $.error('Received date is in a non supported format!');\r
+                                                       return false;\r
+                                               }\r
+                                       }\r
+                                       return date;\r
+                               }\r
+                               \r
+                               var $this = $(this);\r
+                               if(typeof date == 'undefined') date = new Date(0);\r
+                               \r
+                               if(desired_format != 'string' && desired_format != 'object' && desired_format != 'number')\r
+                                       $.error('Date format "'+ desired_format +'" not supported!');\r
+                               \r
+                               if(!dateFormat) {\r
+                                       dateFormat = $.datepicker._defaults.dateFormat;\r
+                                       \r
+                                       // thanks to bibendus83 -> http://sourceforge.net/tracker/index.php?func=detail&aid=3213174&group_id=358205&atid=1495382\r
+                                       var dp_dateFormat = $this.datepicker('option', 'dateFormat');\r
+                                       if (dp_dateFormat) {\r
+                                               dateFormat = dp_dateFormat;\r
+                                       }\r
+                               }\r
+                               \r
+                               // converts to object as a neutral format\r
+                               switch(from_format) {\r
+                                       case 'object': break;\r
+                                       case 'string': date = $.datepicker.parseDate(dateFormat, date); break;\r
+                                       case 'number': date = new Date(date); break;\r
+                                       default: $.error('Conversion from "'+ desired_format +'" format not allowed on jQuery.multiDatesPicker');\r
+                               }\r
+                               // then converts to the desired format\r
+                               switch(desired_format) {\r
+                                       case 'object': return date;\r
+                                       case 'string': return $.datepicker.formatDate(dateFormat, date);\r
+                                       case 'number': return date.getTime();\r
+                                       default: $.error('Conversion to "'+ desired_format +'" format not allowed on jQuery.multiDatesPicker');\r
+                               }\r
+                               return false;\r
+                       },\r
+                       gotDate : function( date, type ) {\r
+                               if(!type) type = 'picked';\r
+                               for(var i = 0; i < this.multiDatesPicker.dates[type].length; i++) {\r
+                                       if(methods.compareDates.call(this, this.multiDatesPicker.dates[type][i], date) === 0) {\r
+                                               return i;\r
+                                       }\r
+                               }\r
+                               return false;\r
+                       },\r
+                       getDates : function( format, type ) {\r
+                               if(!format) format = 'string';\r
+                               if(!type) type = 'picked';\r
+                               switch (format) {\r
+                                       case 'object':\r
+                                               return this.multiDatesPicker.dates[type];\r
+                                       case 'string':\r
+                                       case 'number':\r
+                                               var o_dates = new Array();\r
+                                               for(var i in this.multiDatesPicker.dates[type])\r
+                                                       o_dates.push(\r
+                                                               dateConvert.call(\r
+                                                                       this, \r
+                                                                       this.multiDatesPicker.dates[type][i], \r
+                                                                       format\r
+                                                               )\r
+                                                       );\r
+                                               return o_dates;\r
+                                       \r
+                                       default: $.error('Format "'+format+'" not supported!');\r
+                               }\r
+                       },\r
+                       addDates : function( dates, type ) {\r
+                               if(dates.length > 0) {\r
+                                       if(!type) type = 'picked';\r
+                                       switch(typeof dates) {\r
+                                               case 'object':\r
+                                               case 'array':\r
+                                                       if(dates.length) {\r
+                                                               for(var i in dates)\r
+                                                                       if (typeof dates[i] != "function")\r
+                                                                               addDate.call(this, dates[i], type, true);\r
+                                                               sortDates.call(this, type);\r
+                                                               break;\r
+                                                       } // else does the same as 'string'\r
+                                               case 'string':\r
+                                               case 'number':\r
+                                                       addDate.call(this, dates, type);\r
+                                                       break;\r
+                                               default: \r
+                                                       $.error('Date format "'+ typeof dates +'" not allowed on jQuery.multiDatesPicker');\r
+                                       }\r
+                                       $(this).datepicker('refresh');\r
+                               } else {\r
+                                       $.error('Empty array of dates received.');\r
+                               }\r
+                       },\r
+                       removeDates : function( dates, type ) {\r
+                               if(!type) type = 'picked';\r
+                               var removed = [];\r
+                               if (Object.prototype.toString.call(dates) === '[object Array]') {\r
+                                       for(var i in dates.sort(function(a,b){return b-a})) {\r
+                                               removed.push(removeDate.call(this, dates[i], type));\r
+                                       }\r
+                               } else {\r
+                                       removed.push(removeDate.call(this, dates, type));\r
+                               }\r
+                               $(this).datepicker('refresh');\r
+                               return removed;\r
+                       },\r
+                       removeIndexes : function( indexes, type ) {\r
+                               if(!type) type = 'picked';\r
+                               var removed = [];\r
+                               if (Object.prototype.toString.call(indexes) === '[object Array]') {\r
+                                       for(var i in indexes.sort(function(a,b){return b-a})) {\r
+                                               removed.push(removeIndex.call(this, indexes[i], type));\r
+                                       }\r
+                               } else {\r
+                                       removed.push(removeIndex.call(this, indexes, type));\r
+                               }\r
+                               $(this).datepicker('refresh');\r
+                               return removed;\r
+                       },\r
+                       resetDates : function ( type ) {\r
+                               if(!type) type = 'picked';\r
+                               this.multiDatesPicker.dates[type] = [];\r
+                               $(this).datepicker('refresh');\r
+                       },\r
+                       toggleDate : function( date, type ) {\r
+                               if(!type) type = 'picked';\r
+                               \r
+                               switch(this.multiDatesPicker.mode) {\r
+                                       case 'daysRange':\r
+                                               this.multiDatesPicker.dates[type] = []; // deletes all picked/disabled dates\r
+                                               var end = this.multiDatesPicker.autoselectRange[1];\r
+                                               var begin = this.multiDatesPicker.autoselectRange[0];\r
+                                               if(end < begin) { // switch\r
+                                                       end = this.multiDatesPicker.autoselectRange[0];\r
+                                                       begin = this.multiDatesPicker.autoselectRange[1];\r
+                                               }\r
+                                               for(var i = begin; i < end; i++) \r
+                                                       methods.addDates.call(this, methods.sumDays(date, i), type);\r
+                                               break;\r
+                                       default:\r
+                                               if(methods.gotDate.call(this, date) === false) // adds dates\r
+                                                       methods.addDates.call(this, date, type);\r
+                                               else // removes dates\r
+                                                       methods.removeDates.call(this, date, type);\r
+                                               break;\r
+                               }\r
+                       }, \r
+                       setMode : function( options ) {\r
+                               var $this = $(this);\r
+                               if(options.mode) this.multiDatesPicker.mode = options.mode;\r
+                               \r
+                               switch(this.multiDatesPicker.mode) {\r
+                                       case 'normal':\r
+                                               for(option in options)\r
+                                                       switch(option) {\r
+                                                               case 'maxPicks':\r
+                                                               case 'minPicks':\r
+                                                               case 'pickableRange':\r
+                                                               case 'adjustRangeToDisabled':\r
+                                                                       this.multiDatesPicker[option] = options[option];\r
+                                                                       break;\r
+                                                               //default: $.error('Option ' + option + ' ignored for mode "'.options.mode.'".');\r
+                                                       }\r
+                                       break;\r
+                                       case 'daysRange':\r
+                                       case 'weeksRange':\r
+                                               var mandatory = 1;\r
+                                               for(option in options)\r
+                                                       switch(option) {\r
+                                                               case 'autoselectRange':\r
+                                                                       mandatory--;\r
+                                                               case 'pickableRange':\r
+                                                               case 'adjustRangeToDisabled':\r
+                                                                       this.multiDatesPicker[option] = options[option];\r
+                                                                       break;\r
+                                                               //default: $.error('Option ' + option + ' does not exist for setMode on jQuery.multiDatesPicker');\r
+                                                       }\r
+                                               if(mandatory > 0) $.error('Some mandatory options not specified!');\r
+                                       break;\r
+                               }\r
+                               \r
+                               /*\r
+                               if(options.pickableRange) {\r
+                                       $this.datepicker("option", "maxDate", options.pickableRange);\r
+                                       $this.datepicker("option", "minDate", this.multiDatesPicker.minDate);\r
+                               }\r
+                               */\r
+                               \r
+                               if(mdp_events.onSelect)\r
+                                       mdp_events.onSelect();\r
+                               $this.datepicker('refresh');\r
+                       }\r
+               };\r
+               \r
+               this.each(function() {\r
+                       if (!this.multiDatesPicker) {\r
+                               this.multiDatesPicker = {\r
+                                       dates: {\r
+                                               picked: [],\r
+                                               disabled: []\r
+                                       },\r
+                                       mode: 'normal',\r
+                                       adjustRangeToDisabled: true\r
+                               };\r
+                       }\r
+                       \r
+                       if(methods[method]) {\r
+                               var exec_result = methods[method].apply(this, Array.prototype.slice.call(mdp_arguments, 1));\r
+                               switch(method) {\r
+                                       case 'getDates':\r
+                                       case 'removeDates':\r
+                                       case 'gotDate':\r
+                                       case 'sumDays':\r
+                                       case 'compareDates':\r
+                                       case 'dateConvert':\r
+                                               ret = exec_result;\r
+                               }\r
+                               return exec_result;\r
+                       } else if( typeof method === 'object' || ! method ) {\r
+                               return methods.init.apply(this, mdp_arguments);\r
+                       } else {\r
+                               $.error('Method ' +  method + ' does not exist on jQuery.multiDatesPicker');\r
+                       }\r
+                       return false;\r
+               });\r
+               \r
+               if(method != 'gotDate' && method != 'getDates') {\r
+                       aaaa = 1;\r
+               }\r
+               \r
+               return ret;\r
+       };\r
+\r
+       var PROP_NAME = 'multiDatesPicker';\r
+       var dpuuid = new Date().getTime();\r
+       var instActive;\r
+\r
+       $.multiDatesPicker = {version: false};\r
+       //$.multiDatesPicker = new MultiDatesPicker(); // singleton instance\r
+       $.multiDatesPicker.initialized = false;\r
+       $.multiDatesPicker.uuid = new Date().getTime();\r
+       $.multiDatesPicker.version = $.ui.multiDatesPicker.version;\r
+\r
+       // Workaround for #4055\r
+       // Add another global to avoid noConflict issues with inline event handlers\r
+       window['DP_jQuery_' + dpuuid] = $;\r
+})( jQuery );
\ No newline at end of file
diff --git a/www/plugins/calendrier_mini-2.0/lang/minical.xml b/www/plugins/calendrier_mini-2.0/lang/minical.xml
new file mode 100644 (file)
index 0000000..900091e
--- /dev/null
@@ -0,0 +1,27 @@
+<traduction module="minical" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/calendrier_mini/trunk/lang/" reference="fr">
+       <langue code="ar" url="http://trad.spip.net/tradlang_module/minical?lang_cible=ar" total="14" traduits="3" relire="0" modifs="0" nouveaux="11" pourcent="21.43">
+               <traducteur nom="George" lien="http://trad.spip.net/auteur/جورج-قندلفت" />
+               <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
+       </langue>
+       <langue code="en" url="http://trad.spip.net/tradlang_module/minical?lang_cible=en" total="14" traduits="14" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="kent1" lien="http://trad.spip.net/auteur/kent1" />
+               <traducteur nom="Martin McCaffery" lien="http://trad.spip.net/auteur/martin-mccaffery" />
+       </langue>
+       <langue code="es" url="http://trad.spip.net/tradlang_module/minical?lang_cible=es" total="14" traduits="14" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Raquel S. Bujaldón" lien="http://trad.spip.net/auteur/raquel-s-bujaldon" />
+       </langue>
+       <langue code="fa" url="http://trad.spip.net/tradlang_module/minical?lang_cible=fa" total="14" traduits="3" relire="0" modifs="0" nouveaux="11" pourcent="21.43">
+               <traducteur nom="Davood Hossein" lien="http://trad.spip.net/auteur/davood-hossein" />
+       </langue>
+       <langue code="fr" url="http://trad.spip.net/tradlang_module/minical?lang_cible=fr" total="14" traduits="14" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       </langue>
+       <langue code="nl" url="http://trad.spip.net/tradlang_module/minical?lang_cible=nl" total="14" traduits="14" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
+       </langue>
+       <langue code="sk" url="http://trad.spip.net/tradlang_module/minical?lang_cible=sk" total="14" traduits="14" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="jaro" lien="http://trad.spip.net/auteur/jaro" />
+       </langue>
+       <langue code="sl" url="http://trad.spip.net/tradlang_module/minical?lang_cible=sl" total="14" traduits="3" relire="0" modifs="0" nouveaux="11" pourcent="21.43">
+               <traducteur nom="kent1" lien="http://trad.spip.net/auteur/kent1" />
+       </langue>
+</traduction>
diff --git a/www/plugins/calendrier_mini-2.0/lang/minical_ar.php b/www/plugins/calendrier_mini-2.0/lang/minical_ar.php
new file mode 100644 (file)
index 0000000..91b9bbf
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/minical?lang_cible=ar
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucune_date' => 'لا شيء لهذا الشهر',
+
+       // M
+       'mois_precedent' => 'الشهر السابق',
+       'mois_suivant' => 'الشهر التالي'
+);
+
+?>
diff --git a/www/plugins/calendrier_mini-2.0/lang/minical_en.php b/www/plugins/calendrier_mini-2.0/lang/minical_en.php
new file mode 100644 (file)
index 0000000..660e033
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/minical?lang_cible=en
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucune_date' => 'Nothing for this month',
+
+       // C
+       'config_titre_calendriermini' => 'Mini-Calendar',
+
+       // L
+       'label_affichage_hors_mois' => 'Days displayed',
+       'label_affichage_hors_mois_0' => 'Hide days of previous and next months',
+       'label_affichage_hors_mois_1' => 'Display days of previous and next months',
+       'label_changement_rapide' => 'Navigation',
+       'label_changement_rapide_0' => 'Enable quick selection of the month or year.',
+       'label_changement_rapide_1' => 'Enable quick selection of the month or year.',
+       'label_format_jour' => 'Days format',
+       'label_format_jour_abbr' => 'Short',
+       'label_format_jour_initiale' => 'Initial letter',
+       'label_jour1' => 'First day of the week',
+
+       // M
+       'mois_precedent' => 'Previous month',
+       'mois_suivant' => 'Next month'
+);
+
+?>
diff --git a/www/plugins/calendrier_mini-2.0/lang/minical_es.php b/www/plugins/calendrier_mini-2.0/lang/minical_es.php
new file mode 100644 (file)
index 0000000..77fa3d2
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/minical?lang_cible=es
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucune_date' => 'Nada para este mes',
+
+       // C
+       'config_titre_calendriermini' => 'Mini-Calendario',
+
+       // L
+       'label_affichage_hors_mois' => 'Días mostrados',
+       'label_affichage_hors_mois_0' => 'Ocultar los días del mes precedente y siguiente',
+       'label_affichage_hors_mois_1' => 'Mostrar los días del mes precedente y siguiente',
+       'label_changement_rapide' => 'Navegación',
+       'label_changement_rapide_0' => 'Desactivar la selección rápida del mes o del año',
+       'label_changement_rapide_1' => 'Activar la selección rápida del mes o del año',
+       'label_format_jour' => 'Formato de los días',
+       'label_format_jour_abbr' => 'Corto',
+       'label_format_jour_initiale' => 'Inicial',
+       'label_jour1' => 'Primer día de la semana',
+
+       // M
+       'mois_precedent' => 'Mes precedente',
+       'mois_suivant' => 'Mes siguiente'
+);
+
+?>
diff --git a/www/plugins/calendrier_mini-2.0/lang/minical_fa.php b/www/plugins/calendrier_mini-2.0/lang/minical_fa.php
new file mode 100644 (file)
index 0000000..4c4a739
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/minical?lang_cible=fa
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucune_date' => 'اين ماه برنامه‌اي نيست',
+
+       // M
+       'mois_precedent' => 'ماه قبل',
+       'mois_suivant' => 'ماه بعد'
+);
+
+?>
diff --git a/www/plugins/calendrier_mini-2.0/lang/minical_fr.php b/www/plugins/calendrier_mini-2.0/lang/minical_fr.php
new file mode 100644 (file)
index 0000000..b209d9b
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// Fichier source, a modifier dans svn://zone.spip.org/spip-zone/_plugins_/calendrier_mini/trunk/lang/
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucune_date' => 'Rien pour ce mois',
+
+       // C
+       'config_titre_calendriermini' => 'Mini-Calendrier',
+
+       // L
+       'label_affichage_hors_mois' => 'Jours affichés',
+       'label_affichage_hors_mois_0' => 'Masquer les jours des mois précédent et suivant',
+       'label_affichage_hors_mois_1' => 'Afficher les jours des mois précédent et suivant',
+       'label_changement_rapide' => 'Navigation',
+       'label_changement_rapide_0' => 'Désactiver la sélection rapide du mois ou de l’année',
+       'label_changement_rapide_1' => 'Activer la sélection rapide du mois ou de l’année',
+       'label_format_jour' => 'Format des jours',
+       'label_format_jour_abbr' => 'Court',
+       'label_format_jour_initiale' => 'Initiale',
+       'label_jour1' => 'Premier jour de la semaine',
+
+       // M
+       'mois_precedent' => 'Mois précédent',
+       'mois_suivant' => 'Mois suivant'
+);
+
+?>
diff --git a/www/plugins/calendrier_mini-2.0/lang/minical_nl.php b/www/plugins/calendrier_mini-2.0/lang/minical_nl.php
new file mode 100644 (file)
index 0000000..17cd6ba
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/minical?lang_cible=nl
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucune_date' => 'Niets voor deze maand',
+
+       // C
+       'config_titre_calendriermini' => 'Mini-Kalender',
+
+       // L
+       'label_affichage_hors_mois' => 'Getoonde dagen',
+       'label_affichage_hors_mois_0' => 'Verberg de dagen van de vorige en volgende maand',
+       'label_affichage_hors_mois_1' => 'Toon de dagen van de vorige en volgende maand',
+       'label_changement_rapide' => 'Navigatie',
+       'label_changement_rapide_0' => 'Desactiveer de snelkeuze van maand of jaar',
+       'label_changement_rapide_1' => 'Activeer de snelkeuze van maand of jaar',
+       'label_format_jour' => 'Dagformaat',
+       'label_format_jour_abbr' => 'Kort',
+       'label_format_jour_initiale' => 'Beginletter',
+       'label_jour1' => 'Eerste dag van de week',
+
+       // M
+       'mois_precedent' => 'Vorige maand',
+       'mois_suivant' => 'Volgende maand'
+);
+
+?>
diff --git a/www/plugins/calendrier_mini-2.0/lang/minical_sk.php b/www/plugins/calendrier_mini-2.0/lang/minical_sk.php
new file mode 100644 (file)
index 0000000..e496dd9
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/minical?lang_cible=sk
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucune_date' => 'Nič na tento mesiac',
+
+       // C
+       'config_titre_calendriermini' => 'Minikalendár',
+
+       // L
+       'label_affichage_hors_mois' => 'Zobrazené dni',
+       'label_affichage_hors_mois_0' => 'Schovať dni predchádzajúceho a ďalšieho mesiaca',
+       'label_affichage_hors_mois_1' => 'Zobraziť dni predchádzajúceho a nasledujúceho mesiaca',
+       'label_changement_rapide' => 'Navigácia',
+       'label_changement_rapide_0' => 'Deaktivovať rýchly výber mesiaca alebo roka',
+       'label_changement_rapide_1' => 'Aktivovať rýchly výber mesiaca alebo roka',
+       'label_format_jour' => 'Formát dní',
+       'label_format_jour_abbr' => 'Krátky',
+       'label_format_jour_initiale' => 'Začiatočné písmeno',
+       'label_jour1' => 'Prvý deň v týždni',
+
+       // M
+       'mois_precedent' => 'Predchádzajúci mesiac',
+       'mois_suivant' => 'Ďalší mesiac'
+);
+
+?>
diff --git a/www/plugins/calendrier_mini-2.0/lang/minical_sl.php b/www/plugins/calendrier_mini-2.0/lang/minical_sl.php
new file mode 100644 (file)
index 0000000..188f63b
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/minical?lang_cible=sl
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucune_date' => 'Nič v tem mesecu',
+
+       // M
+       'mois_precedent' => 'Prejšnji mesec',
+       'mois_suivant' => 'Naslednji mesec'
+);
+
+?>
diff --git a/www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini.xml b/www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini.xml
new file mode 100644 (file)
index 0000000..c795da1
--- /dev/null
@@ -0,0 +1,19 @@
+<traduction module="paquet-calendriermini" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/calendrier_mini/trunk/lang/" reference="fr">
+       <langue code="de" url="http://trad.spip.net/tradlang_module/paquet-calendriermini?lang_cible=de" total="3" traduits="3" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="klaus++" lien="http://trad.spip.net/auteur/klaus" />
+       </langue>
+       <langue code="en" url="http://trad.spip.net/tradlang_module/paquet-calendriermini?lang_cible=en" total="3" traduits="3" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="kent1" lien="http://trad.spip.net/auteur/kent1" />
+       </langue>
+       <langue code="es" url="http://trad.spip.net/tradlang_module/paquet-calendriermini?lang_cible=es" total="3" traduits="3" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Raquel S. Bujaldón" lien="http://trad.spip.net/auteur/raquel-s-bujaldon" />
+       </langue>
+       <langue code="fr" url="http://trad.spip.net/tradlang_module/paquet-calendriermini?lang_cible=fr" total="3" traduits="3" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       </langue>
+       <langue code="nl" url="http://trad.spip.net/tradlang_module/paquet-calendriermini?lang_cible=nl" total="3" traduits="3" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
+       </langue>
+       <langue code="sk" url="http://trad.spip.net/tradlang_module/paquet-calendriermini?lang_cible=sk" total="3" traduits="3" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="jaro" lien="http://trad.spip.net/auteur/jaro" />
+       </langue>
+</traduction>
diff --git a/www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini_de.php b/www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini_de.php
new file mode 100644 (file)
index 0000000..f602696
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-calendriermini?lang_cible=de
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'calendriermini_description' => '#CALENDRIER_MINI zeigt einen Kalender in der Art von DotClear an, der zu den Stilen dieses Blogsystems passt.
+_ Dazu kommen weitere Elemente wie Tags, Kriterien, Modelle, ...',
+       'calendriermini_nom' => 'Minikalender',
+       'calendriermini_slogan' => 'Stellt den Tag #CALENDRIER_MINI bereit'
+);
+
+?>
diff --git a/www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini_en.php b/www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini_en.php
new file mode 100644 (file)
index 0000000..3b8d5ba
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-calendriermini?lang_cible=en
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'calendriermini_description' => '#CALENDRIER_MINI displays a calendar designed as dotclear’s one and therefore compatible with the styles from this blog system.
+_ Other tools are added, such as tags, criteria, models...',
+       'calendriermini_nom' => 'Mini Calendar',
+       'calendriermini_slogan' => 'Allow to use a #CALENDRIER_MINI tag'
+);
+
+?>
diff --git a/www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini_es.php b/www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini_es.php
new file mode 100644 (file)
index 0000000..379d03d
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-calendriermini?lang_cible=es
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'calendriermini_description' => '#CALENDRIER_MINI muestra un calendario en el diseño derivado de dotclear y por lo tanto compatible con los estilos derivados de este sistema de blog.
+_ Le son agregados otros elementos como etiquetas, criterios, modelos...',
+       'calendriermini_nom' => 'Mini Calendario',
+       'calendriermini_slogan' => 'Permite el uso de una etiqueta #CALENDRIER_MINI'
+);
+
+?>
diff --git a/www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini_fr.php b/www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini_fr.php
new file mode 100644 (file)
index 0000000..700b1f3
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// Fichier source, a modifier dans svn://zone.spip.org/spip-zone/_plugins_/calendrier_mini/trunk/lang/
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'calendriermini_description' => '#CALENDRIER_MINI affiche un calendrier au design issu de dotclear et donc compatible avec les styles issus de ce système de blog.
+_ Lui sont adjoints d’autre éléments, tels que balises, critères, modèles...',
+       'calendriermini_nom' => 'Mini Calendrier',
+       'calendriermini_slogan' => 'Permet l’utilisation d’une balise #CALENDRIER_MINI'
+);
+
+?>
diff --git a/www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini_nl.php b/www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini_nl.php
new file mode 100644 (file)
index 0000000..eea8844
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-calendriermini?lang_cible=nl
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'calendriermini_description' => '#CALENDRIER_MINI toont een kalender in het dotclear ontwerp en is dus compatibel met de stijl van blogs.
+_ Er zijn extra elementen aan toegevoegd, zoals bakens, criteria, modellen...',
+       'calendriermini_nom' => 'Mini Kalender',
+       'calendriermini_slogan' => 'Maakt het gebruik van #CALENDRIER_MINI mogelijk'
+);
+
+?>
diff --git a/www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini_sk.php b/www/plugins/calendrier_mini-2.0/lang/paquet-calendriermini_sk.php
new file mode 100644 (file)
index 0000000..73cf016
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-calendriermini?lang_cible=sk
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'calendriermini_description' => '#CALENDRIER_MINI zobrazí kalendár s dizajnom od dotclear, a preto je kompatibilný so štýlmi tohto  publikačného systému pre blogy.
+_ Sám pomáha iným objektom, ako sú tagy, kritériá, šablóny atď.
+.',
+       'calendriermini_nom' => 'Minikalendár',
+       'calendriermini_slogan' => 'Umožňuje používať tag #CALENDRIER_MINI'
+);
+
+?>
diff --git a/www/plugins/calendrier_mini-2.0/minical-32.png b/www/plugins/calendrier_mini-2.0/minical-32.png
new file mode 100644 (file)
index 0000000..9880597
Binary files /dev/null and b/www/plugins/calendrier_mini-2.0/minical-32.png differ
diff --git a/www/plugins/calendrier_mini-2.0/modeles/archives_mensuelles.html b/www/plugins/calendrier_mini-2.0/modeles/archives_mensuelles.html
new file mode 100644 (file)
index 0000000..a8d8eb7
--- /dev/null
@@ -0,0 +1,16 @@
+<B_archives_mensuelles>
+<div id="archives" class="rubriques">
+  <h2 class="menu-titre"><:archives|ucfirst:></h2>
+  <ul>
+   <BOUCLE_archives_mensuelles(ARTICLES){branche ?}{lang}{par date}{inverse}>[
+   <li>
+   <a href="[(#ENV{self}|parametre_url{archives,[(#DATE|affdate{'Y-m'})]})]">[
+   (#ENV{archives}|=={[(#DATE|affdate{'Y-m'})]}|?{<b>})
+   ](#DATE|affdate{'Y-m'}|unique|affdate_mois_annee)[
+   (#ENV{archives}|=={[(#DATE|affdate{'Y-m'})]}|?{</b>})
+   ]</a>
+   </li>
+   ]</BOUCLE_archives_mensuelles>
+  </ul>
+</div>
+</B_archives_mensuelles>
\ No newline at end of file
diff --git a/www/plugins/calendrier_mini-2.0/modeles/rubrique_calendrier.html b/www/plugins/calendrier_mini-2.0/modeles/rubrique_calendrier.html
new file mode 100644 (file)
index 0000000..41223d0
--- /dev/null
@@ -0,0 +1 @@
+#CALENDRIER_MINI
\ No newline at end of file
diff --git a/www/plugins/calendrier_mini-2.0/modeles/select_archives_mensuelles.html b/www/plugins/calendrier_mini-2.0/modeles/select_archives_mensuelles.html
new file mode 100644 (file)
index 0000000..bad7c19
--- /dev/null
@@ -0,0 +1,26 @@
+<B_archives_mensuelles>
+<div id="form_archives" class="formulaire_spip">
+       <form action='#ENV{self}' method='get'>
+               [(#ENV{self}|form_hidden)]
+               <ul>
+                       <li class="fieldset">
+                               <h3 class="legend"><:archives|ucfirst:></h3>
+                               <fieldset>
+                                       <ul>
+                                               <li class="editer editer_archives">
+                                                       <label for="archives"><:date:></label>
+                                                       <select class="forml" name='archives'>
+                                                       <BOUCLE_archives_mensuelles(ARTICLES){branche ?}{lang}{par date}{inverse}>[
+                                                               <option value='[(#DATE|affdate{'Y-m'})]'[
+                                                               (#ENV{archives}|=={[(#DATE|affdate{'Y-m'})]}|?{selected="selected"})
+                                                               ]>(#DATE|affdate{'Y-m'}|unique{select}|affdate_mois_annee)</option>
+                                                       ]</BOUCLE_archives_mensuelles>
+                                                       </select>
+                                               </li>
+                                       </ul>
+                               </fieldset>
+                       </li>
+               </ul>
+       </form>
+</div>
+</B_archives_mensuelles>
\ No newline at end of file
diff --git a/www/plugins/calendrier_mini-2.0/paquet.xml b/www/plugins/calendrier_mini-2.0/paquet.xml
new file mode 100644 (file)
index 0000000..015a482
--- /dev/null
@@ -0,0 +1,20 @@
+<paquet
+       prefix="calendriermini"
+       categorie="date"
+       version="2.3.7"
+       etat="test"
+       compatibilite="[3.0.0;3.1.*]"
+       logo="minical-32.png"
+       documentation="http://contrib.spip.net/Calendrier-Mini-2-0"
+>      
+
+       <nom>Mini Calendrier</nom>
+       <!-- Permet l'utilisation d'une balise #CALENDRIER_MINI -->
+
+       <auteur mail="james@rezo.net">James</auteur>
+
+       <credit lien="http://tempest.deviantart.com/">Tempest pour l'icone sous licence CC BY-NC-ND.</credit>
+       <copyright>2006-2012</copyright>
+
+       <licence lien="http://www.gnu.org/licenses/gpl-3.0.html">GPL 3</licence>
+</paquet>
\ No newline at end of file
diff --git a/www/plugins/calendrier_mini-2.0/prive/squelettes/contenu/configurer_calendriermini.html b/www/plugins/calendrier_mini-2.0/prive/squelettes/contenu/configurer_calendriermini.html
new file mode 100644 (file)
index 0000000..0b3b36b
--- /dev/null
@@ -0,0 +1,4 @@
+<h1 class="grostitre"><:minical:config_titre_calendriermini:></h1>
+<div class="ajax">
+       #FORMULAIRE_CONFIGURER_CALENDRIERMINI
+</div>
\ No newline at end of file
diff --git a/www/plugins/calendrier_mini-2.0/svn.revision b/www/plugins/calendrier_mini-2.0/svn.revision
new file mode 100644 (file)
index 0000000..6f6a933
--- /dev/null
@@ -0,0 +1,10 @@
+<svn_revision>
+<text_version>
+Origine: file:///home/svn/repository/spip-zone/_plugins_/calendrier_mini/trunk
+Revision: 86025
+Dernier commit: 2014-11-13 10:54:53 +0100 
+</text_version>
+<origine>file:///home/svn/repository/spip-zone/_plugins_/calendrier_mini/trunk</origine>
+<revision>86025</revision>
+<commit>2014-11-13 10:54:53 +0100 </commit>
+</svn_revision>
\ No newline at end of file
diff --git a/www/plugins/champs_extras3/base/cextras.php b/www/plugins/champs_extras3/base/cextras.php
new file mode 100644 (file)
index 0000000..128e4ae
--- /dev/null
@@ -0,0 +1,146 @@
+<?php
+
+/**
+ * Déclaration colonnes SQL des champs extras
+ *
+ * @package SPIP\Cextras\Pipelines
+**/
+
+// sécurité
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+
+/**
+ * Déclarer les nouveaux champs et les nouvelles infos des objets éditoriaux
+ *
+ * La fonction déclare tous les champs extras (saisies de type sql).
+ *
+ * Elle déclare aussi, en fonction des options choisies pour les champs
+ * - la recherche dans le champs, avec une certaine pondération,
+ * - le versionnage de champ
+ * 
+ * @note
+ *     Ne pas utiliser dans le code de cette fonction
+ *     table_objet() qui ferait une réentrance et des calculs faux.
+ * 
+ * @pipeline declarer_tables_objets_sql
+ * @param array $tables
+ *     Description des tables
+ * @return array
+ *     Description complétée des tables
+ */
+function cextras_declarer_tables_objets_sql($tables){
+
+       include_spip('inc/cextras');
+       
+       // recuperer les champs crees par les plugins
+       // array($table => array(Liste de saisies))
+       include_spip('inc/saisies');
+       
+       // si saisies a ete supprime par ftp, on sort tranquilou sans tuer SPIP.
+       // champs extras sera ensuite desactive par admin plugins.
+       if (!function_exists('saisies_lister_avec_sql')) {
+               return $tables;
+       }
+       
+       $saisies_tables = pipeline('declarer_champs_extras', array());
+       foreach ($saisies_tables as $table => $saisies) {
+               if (isset($tables[$table])) {
+                       $saisies = saisies_lister_avec_sql($saisies);
+                       foreach ($saisies as $saisie) {
+                               $nom = $saisie['options']['nom'];
+                               if (!isset($tables[$table]['field'][$nom])) {
+                                       $tables[$table]['field'][$nom] = $saisie['options']['sql'];
+                               }
+                               // on l'ajoute dans la liste des champs editables
+                               if (isset($tables[$table]['champs_editables'])
+                                 and !in_array($nom, $tables[$table]['champs_editables'])){
+                                       $tables[$table]['champs_editables'][] = $nom;
+                               }
+
+                               // ajouter le champ dans les analyses de recherche si demande
+                               // l'option rechercher peut valoir 'on', true, ou 5 (entier) pour l'indice de ponderation
+                               // par defaut, la ponderation est de 2.
+                               if (isset($saisie['options']['rechercher']) and $saisie['options']['rechercher']) {
+                                       $ponderation = $saisie['options']['rechercher'];
+                                       if ($ponderation === 'on' OR $ponderation === true) {
+                                               // le plugin d'interface donne la valeur de ponderation dans une option separee.
+                                               if (isset($saisie['options']['rechercher_ponderation']) and $saisie['options']['rechercher_ponderation']) {
+                                                       $ponderation = intval($saisie['options']['rechercher_ponderation']);
+                                               } else {
+                                                       $ponderation = 2;
+                                               }
+                                       } else {
+                                               $ponderation = intval($ponderation);
+                                       }
+                                       $tables[$table]['rechercher_champs'][$nom] = $ponderation;
+                               }
+                               // option de versionnage ?
+                               if (isset($saisie['options']['versionner']) and $saisie['options']['versionner']) {
+                                       // on l'ajoute dans la liste des champs versionnables
+                                       if (isset($tables[$table]['champs_versionnes'])
+                                         and !in_array($nom, $tables[$table]['champs_versionnes'])) {
+                                               $tables[$table]['champs_versionnes'][] = $nom;
+                                       }
+                               }
+                       }
+               }
+       }
+
+       return $tables;
+}
+
+
+/**
+ * Déclarer les nouvelles infos sur les champs extras ajoutés
+ * en ce qui concerne les traitements automatiques sur les balises.
+ *
+ * @pipeline declarer_tables_interfaces
+ * @param array $interfaces
+ *     Déclarations d'interface pour le compilateur
+ * @return array
+ *     Déclarations d'interface pour le compilateur
+**/
+function cextras_declarer_tables_interfaces($interfaces){
+
+       include_spip('inc/cextras');
+       include_spip('inc/saisies');
+
+       // si saisies a ete supprime par ftp, on sort tranquilou sans tuer SPIP.
+       // champs extras sera ensuite desactive par admin plugins.
+       if (!function_exists('saisies_lister_avec_sql')) {
+               return $tables;
+       }
+
+       // recuperer les champs crees par les plugins
+       $saisies_tables = pipeline('declarer_champs_extras', array());
+       if (!$saisies_tables) {
+               return $interfaces;
+       }
+
+       foreach ($saisies_tables as $table=>$saisies) {
+               $saisies = saisies_lister_avec_sql($saisies);
+               $saisies = saisies_lister_avec_traitements($saisies);
+
+               foreach ($saisies as $saisie) {
+                       $traitement = $saisie['options']['traitements'];
+                       $balise = strtoupper($saisie['options']['nom']);
+                       // definir
+                       if (!isset($interfaces['table_des_traitements'][$balise])) {
+                               $interfaces['table_des_traitements'][$balise] = array();
+                       }
+                       // le traitement peut etre le nom d'un define
+                       $traitement = defined($traitement) ? constant($traitement) : $traitement;
+
+                       // SPIP 3 permet de declarer par la table sql directement.
+                       $interfaces['table_des_traitements'][$balise][$table] = $traitement;
+
+               }
+       }
+
+       // ajouter les champs au tableau spip
+       return $interfaces;
+}
+
+
+?>
diff --git a/www/plugins/champs_extras3/cextras_fonctions.php b/www/plugins/champs_extras3/cextras_fonctions.php
new file mode 100644 (file)
index 0000000..972c418
--- /dev/null
@@ -0,0 +1,311 @@
+<?php
+
+/**
+ * Déclarations de balises pour les squelettes
+ *
+ * @package SPIP\Cextras\Fonctions
+**/
+
+// sécurité
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+/**
+ * Retourne la description de la saisie du champ demandé
+ * permettant ainsi d'exploiter ses données.
+ *
+ * @example
+ *     ```
+ *     <BOUCLE_x(TABLE)>
+ *     - #CHAMP_EXTRA{nom_du_champ}
+ *     - #CHAMP_EXTRA{nom_du_champ,label}
+ *     </BOUCLE_x>
+ *     ```
+ *
+ * @balise
+ * @note
+ *     Lève une erreur de squelette si le nom de champs extras
+ *     n'est pas indiqué en premier paramètre de la balise
+ * 
+ * @param Champ $p
+ *     AST au niveau de la balise
+ * @return Champ
+ *     AST complété par le code PHP de la balise
+**/
+function balise_CHAMP_EXTRA_dist($p) {
+       // prendre nom de la cle primaire de l'objet pour calculer sa valeur
+       $id_boucle = $p->nom_boucle ? $p->nom_boucle : $p->id_boucle;
+       $objet = $p->boucles[$id_boucle]->id_table;
+
+       // recuperer les parametres : colonne sql (champ)
+       if (!$colonne = interprete_argument_balise(1, $p)) {
+               $msg = array('zbug_balise_sans_argument', array('balise' => ' CHAMP_EXTRA'));
+               erreur_squelette($msg, $p);
+       }
+
+       $demande = sinon(interprete_argument_balise(2, $p), "''");
+       $p->code = "calculer_balise_CHAMP_EXTRA('$objet', $colonne, $demande)";
+       return $p;
+}
+
+/**
+ * Retourne la description d'un champ extra indiqué
+ *
+ * Retourne le tableau de description des options de saisies
+ * ou un des attributs de ce tableau
+ *
+ * @param string $objet
+ *     Type d'objet
+ * @param string $colonne
+ *     Nom de la colonne SQL
+ * @param string $demande
+ *     Nom du paramètre demandé.
+ *     Non renseigné, tout le tableau de description est retourné
+ * @return mixed
+ *     - Tableau si toute la description est demandée
+ *     - Indéfini si un élément spécifique de la description est demandé.
+ *     - Chaine vide si le champs extra n'est pas trouvé
+ */
+function calculer_balise_CHAMP_EXTRA($objet, $colonne, $demande='') {
+       // Si la balise n'est pas dans une boucle, on cherche un objet explicite dans le premier argument
+       // de la forme "trucs/colonne" ou "spip_trucs/colonne"
+       if (!$objet and $decoupe = explode('/', $colonne) and count($decoupe) == 2){
+               $objet = $decoupe[0];
+               $colonne = $decoupe[1];
+       }
+       
+       // recuperer la liste des champs extras existants
+       include_spip('cextras_pipelines');
+       if (!$saisies = champs_extras_objet( $table = table_objet_sql($objet) )) {
+               return '';
+       }
+       
+       include_spip('inc/saisies');
+       if (!$saisie = saisies_chercher($saisies, $colonne)) {
+               return '';
+       }
+
+       if (!$demande) {
+               return $saisie['options']; // retourne la description de la saisie...
+       }
+
+       if (array_key_exists($demande, $saisie['options'])) {
+               return $saisie['options'][$demande];
+       }
+
+       return '';
+}
+
+
+/**
+ * Retourne les choix possibles d'un champ extra donné
+ *
+ * @example
+ *     ```
+ *     #LISTER_CHOIX{champ}
+ *     #LISTER_CHOIX{champ, " > "}
+ *     // ** pour retourner un tableau (cle => valeur),
+ *     // ou tableau groupe => tableau (cle => valeur) si déclaration de groupements.
+ *     #LISTER_CHOIX**{champ}
+ *     ```
+ *
+ * @balise
+ * @param Champ $p
+ *     AST au niveau de la balise
+ * @return Champ
+ *     AST complété par le code PHP de la balise
+**/
+function balise_LISTER_CHOIX_dist($p) {
+       // prendre nom de la cle primaire de l'objet pour calculer sa valeur
+       $id_boucle = $p->nom_boucle ? $p->nom_boucle : $p->id_boucle;
+
+       // s'il n'y a pas de nom de boucle, on ne peut pas fonctionner
+       if (!isset($p->boucles[$id_boucle])) {
+               $msg = array('zbug_champ_hors_boucle', array('champ' => ' LISTER_CHOIX'));
+               erreur_squelette($msg, $p);
+               $p->code = "''";
+               return $p;
+       }
+       
+       $objet = $p->boucles[$id_boucle]->id_table;
+       
+       // recuperer les parametres : colonne sql (champ)
+       if (!$colonne = interprete_argument_balise(1, $p)) {
+               $msg = array('zbug_balise_sans_argument',       array('balise' => ' LISTER_CHOIX'));
+               erreur_squelette($msg, $p);
+               $p->code = "''";
+               return $p;
+       }
+       
+       $separateur = interprete_argument_balise(2, $p);
+       if (!$separateur) $separateur = "', '";
+
+       // generer le code d'execution
+       $applatir = ($p->etoile == "**") ? 'false' : 'true';
+       $p->code = "calculer_balise_LISTER_CHOIX('$objet', $colonne, $applatir)";
+
+       // retourne un array si #LISTER_CHOIX**
+       // sinon fabrique une chaine avec le separateur designe.
+       if ($p->etoile != "**") {
+               $p->code = "(is_array(\$a = $p->code) ? join($separateur, \$a) : " . $p->code . ")";
+       }
+       
+       return $p;
+}
+
+
+/**
+ * Retourne les choix possibles d'un champ extra indiqué
+ *
+ * @note
+ *     Le plugin saisies tolère maintenant des sélections avec
+ *     un affichage par groupe (optgroup / options) avec une syntaxe
+ *     spécifique. Ici nous devons pouvoir applatir
+ *     toutes les cle => valeur.
+ * 
+ * @param string $objet
+ *     Type d'objet
+ * @param string $colonne
+ *     Nom de la colonne SQL
+ * @param bool $applatir
+ *     true pour applatir les choix possibles au premier niveau
+ *     même si on a affaire à une liste de choix triée par groupe
+ * @return string|array
+ *     - Tableau des couples (clé => valeur) des choix
+ *     - Chaîne vide si le champs extra n'est pas trouvé
+ */
+function calculer_balise_LISTER_CHOIX($objet, $colonne, $applatir = true) {
+       if ($options = calculer_balise_CHAMP_EXTRA($objet, $colonne)) {
+               if (isset($options['datas']) and $options['datas']) {
+                       include_spip('inc/saisies');
+                       $choix = saisies_chaine2tableau($options['datas']);
+                       // applatir les sous-groupes si présents
+                       if ($applatir) {
+                               $choix = saisies_aplatir_tableau($choix);
+                       }
+                       return $choix;
+               }
+       }
+       return '';
+}
+
+
+
+/**
+ * Liste les valeurs des champs de type liste (enum, radio, case)
+ * 
+ * Ces champs enregistrent en base la valeur de la clé
+ * Il faut donc transcrire clé -> valeur
+ *
+ * @example
+ *     ```
+ *     #LISTER_VALEURS{champ}
+ *     #LISTER_VALEURS{champ, " > "} 
+ *     #LISTER_VALEURS**{champ} // retourne un tableau cle/valeur
+ *     ```
+ * 
+ * @note 
+ *     Pour des raisons d'efficacité des requetes SQL
+ *     le paramètre "champ" ne peut être calculé
+ *     ``#LISTER_VALEURS{#GET{champ}}`` ne peut pas fonctionner.
+ * 
+ *     Si cette restriction est trop limitative, on verra par la suite
+ *     pour l'instant, on laisse comme ca...
+ *
+ * @balise
+ * @param Champ $p
+ *     AST au niveau de la balise
+ * @return Champ
+ *     AST complété par le code PHP de la balise
+ */
+function balise_LISTER_VALEURS_dist($p) {
+       // prendre nom de la cle primaire de l'objet pour calculer sa valeur
+       $id_boucle = $p->nom_boucle ? $p->nom_boucle : $p->id_boucle;
+
+       // s'il n'y a pas de nom de boucle, on ne peut pas fonctionner
+       if (!isset($p->boucles[$id_boucle])) {
+               $msg = array('zbug_champ_hors_boucle', array('champ' => ' LISTER_VALEURS'));
+               erreur_squelette($msg, $p);
+               $p->code = "''";
+               return $p;
+       }
+       
+       $objet = $p->boucles[$id_boucle]->id_table;
+       $_id_objet = $p->boucles[$id_boucle]->primary;
+       $id_objet = champ_sql($_id_objet, $p);
+
+       // recuperer les parametres : colonne sql (champ)
+       if (!$colonne = interprete_argument_balise(1, $p)) {
+               $msg = array('zbug_balise_sans_argument', array('balise' => ' LISTER_VALEURS'));
+               erreur_squelette($msg, $p);
+               $p->code = "''";
+               return $p;
+       }
+       
+       $separateur = interprete_argument_balise(2, $p);
+       if (!$separateur) $separateur = "', '";
+       
+       // demander la colonne dans la requete SQL
+       // $colonne doit etre un texte 'nom_du_champ'
+       if ($p->param[0][1][0]->type != 'texte') {
+               $msg = array('cextras:zbug_balise_argument_non_texte', array('nb'=>1, 'balise' => ' LISTER_VALEURS'));
+               erreur_squelette($msg, $p);
+               $p->code = "''";
+               return $p;
+       }
+       
+       $texte_colonne = $p->param[0][1][0]->texte;
+       
+       $valeur = champ_sql($texte_colonne, $p);
+
+       // generer le code d'execution
+       $p->code = "calculer_balise_LISTER_VALEURS('$objet', $colonne, $valeur)";
+
+       // retourne un array si #LISTER_VALEURS**
+       // sinon fabrique une chaine avec le separateur designe.
+       if ($p->etoile != "**") {
+               $p->code = "(is_array(\$a = $p->code) ? join($separateur, \$a) : " . $p->code . ")";
+       }
+
+       return $p;
+}
+
+
+/**
+ * Retourne liste des valeurs choisies pour un champ extra indiqué
+ *
+ * @param string $objet
+ *     Type d'objet
+ * @param string $colonne
+ *     Nom de la colonne SQL
+ * @param string $cles
+ *     Valeurs enregistrées pour ce champ dans la bdd pour l'objet en cours
+ * 
+ * @return string|array
+ *     - Tableau des couples (clé => valeur) des choix
+ *     - Chaîne vide si le champs extra n'est pas trouvé
+**/
+function calculer_balise_LISTER_VALEURS($objet, $colonne, $cles) {
+
+       // exploser les cles !
+       $cles = explode(',', $cles);
+
+       // si pas de cles, on part aussi gentiment
+       if (!$cles) return array();
+
+       // recuperer les choix possibles
+       $choix = calculer_balise_LISTER_CHOIX($objet, $colonne);
+
+       // sortir gentiment si pas de champs declares
+       // on ne peut pas traduire les cles
+       if (!$choix) return $cles;
+
+       // correspondances...
+       $vals = array_intersect_key($choix, array_flip($cles));
+
+       // et voici les valeurs !
+       return $vals ? $vals : $cles;
+}
+
+
+
+?>
diff --git a/www/plugins/champs_extras3/cextras_options.php b/www/plugins/champs_extras3/cextras_options.php
new file mode 100644 (file)
index 0000000..484d099
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * Options globales chargées à chaque hit
+ *
+ * @package SPIP\Cextras\Options
+**/
+
+// sécurité
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+// utiliser ces pipelines a part
+// afin d'etre certain d'arriver apres les autres plugins
+// sinon toutes les tables ne sont pas declarees
+// et les champs supplementaires ne peuvent pas se declarer comme il faut
+
+if (!isset($GLOBALS['spip_pipeline']['declarer_tables_objets_sql'])) {
+       $GLOBALS['spip_pipeline']['declarer_tables_objets_sql'] = '';
+}
+if (!isset($GLOBALS['spip_pipeline']['declarer_tables_interfaces'])) {
+       $GLOBALS['spip_pipeline']['declarer_tables_interfaces'] = '';
+}
+
+$GLOBALS['spip_pipeline']['declarer_tables_objets_sql'] .= '||cextras_declarer_champs_apres_les_autres';
+$GLOBALS['spip_pipeline']['declarer_tables_interfaces'] .= '||cextras_declarer_champs_interfaces_apres_les_autres';
+
+/**
+ * Ajouter les déclaration dechamps extras sur les objets éditoriaux
+ *
+ * @pipeline declarer_tables_objets_sql
+ * @see cextras_declarer_tables_objets_sql()
+ * @param array $tables
+ *     Description des objets éditoriaux
+ * @return array
+ *     Description des objets éditoriaux
+**/
+function cextras_declarer_champs_apres_les_autres($tables) {
+       include_spip('base/cextras');
+       return cextras_declarer_tables_objets_sql($tables);
+}
+
+/**
+ * Ajouter les déclaration d'interface des champs extras pour le compilateur
+ *
+ * @pipeline declarer_tables_interfaces
+ * @see cextras_declarer_tables_interfaces()
+ * @param array $interface
+ *     Description des interfaces pour le compilateur
+ * @return array
+ *     Description des interfaces pour le compilateur
+**/
+function cextras_declarer_champs_interfaces_apres_les_autres($interface) {
+       include_spip('base/cextras');
+       return cextras_declarer_tables_interfaces($interface);
+}
+?>
diff --git a/www/plugins/champs_extras3/cextras_pipelines.php b/www/plugins/champs_extras3/cextras_pipelines.php
new file mode 100644 (file)
index 0000000..6dc0147
--- /dev/null
@@ -0,0 +1,353 @@
+<?php
+
+/**
+ * Utilisations de pipelines
+ *
+ * @package SPIP\Cextras\Pipelines
+**/
+
+// sécurité
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+/**
+ * Retourne la liste des saisies de champs extras concernant un objet donné
+ *
+ * @pipeline_appel declarer_champs_extras
+ * @param string $table
+ *     Nom d'une table SQL éditoriale
+ * @return array
+ *     Liste des saisies de champs extras de l'objet
+**/
+function champs_extras_objet($table) {
+       static $saisies_tables = array();
+       if (!$saisies_tables) {
+               $saisies_tables = pipeline('declarer_champs_extras', array());
+       }
+       return isset($saisies_tables[$table]) ? $saisies_tables[$table] : array();
+}
+
+/**
+ * Filtrer par autorisation les saisies transmises 
+ *
+ * Chacune des saisies est parcourue et si le visiteur n'a pas l'autorisation
+ * de la voir, elle est enlevée de la liste.
+ * La fonction ne retourne donc que la liste des saisies que peut voir
+ * la personne.
+ * 
+ * @param string $faire
+ *     Type d'autorisation testée : 'voir', 'modifier'
+ * @param string $quoi
+ *     Type d'objet tel que 'article'
+ * @param array $saisies
+ *     Liste des saisies à filtrer
+ * @param array $args
+ *     Arguments pouvant être utiles à l'autorisation
+ * @return array
+ *     Liste des saisies filtrées
+**/
+function champs_extras_autorisation($faire, $quoi='', $saisies=array(), $args=array()) {
+       if (!$saisies) return array();
+       include_spip('inc/autoriser');
+
+       foreach ($saisies as $cle=>$saisie) {
+               $id = isset($args['id']) ? $args['id'] : $args['id_objet'];
+               if (!autoriser($faire . 'extra', $quoi, $id, '', array(
+                       'type' => $quoi,
+                       'id_objet' => $id,
+                       'contexte' => isset($args['contexte']) ? $args['contexte'] : array(),
+                       'table' => table_objet_sql($quoi),
+                       'saisie' => $saisie,
+                       'champ' => $saisie['options']['nom'],
+               ))) {
+                       // on n'est pas autorise
+                       unset($saisies[$cle]);
+               }
+               else
+               {
+                       // on est autorise
+                       // on teste les sous-elements
+                       if (isset($saisie['saisies']) and $saisie['saisies']) {
+                               $saisies['saisies'] = champs_extras_autorisation($faire, $quoi, $saisie['saisies'], $args);
+                       }
+               }
+       }
+       return $saisies;
+}
+
+/**
+ * Ajoute pour chaque saisie de type SQL un drapeau (input hidden)
+ * permettant de retrouver les saisies editées.
+ * 
+ * Particulièrement utile pour les checkbox qui ne renvoient
+ * rien si on les décoche.
+ *
+ * @param array $saisies
+ *     Liste de saisies 
+ * @return array $saisies
+ *     Saisies complétées des drapeaux d'édition
+**/
+function champs_extras_ajouter_drapeau_edition($saisies) {
+       $saisies_sql = saisies_lister_avec_sql($saisies);
+       foreach ($saisies_sql as $saisie) {
+               $nom = $saisie['options']['nom'];
+               $saisies[] = array(
+                       'saisie' => 'hidden',
+                       'options' => array(
+                               'nom' => "cextra_$nom",
+                               'defaut' => 1 
+                       )
+               );
+       }
+       return $saisies;
+}
+
+// ---------- pipelines -----------
+
+
+/**
+ * Ajouter les champs extras sur les formulaires CVT editer_xx
+ *
+ * Liste les champs extras de l'objet, et s'il y en a les ajoute
+ * sur le formulaire d'édition en ayant filtré uniquement les saisies
+ * que peut voir le visiteur et en ayant ajouté des champs hidden
+ * servant à champs extras.
+ * 
+ * @pipeline editer_contenu_objet
+ * @param array $flux Données du pipeline
+ * @return array      Données du pipeline
+**/ 
+function cextras_editer_contenu_objet($flux){
+       
+       // recuperer les saisies de l'objet en cours
+       $objet = $flux['args']['type'];
+       include_spip('inc/cextras');
+       if ($saisies = champs_extras_objet( table_objet_sql($objet) )) {
+               // filtrer simplement les saisies que la personne en cours peut voir
+               $saisies = champs_extras_autorisation('modifier', $objet, $saisies, $flux['args']);
+               // pour chaque saisie presente, de type champs extras (hors fieldset et autres)
+               // ajouter un flag d'edition
+               $saisies = champs_extras_ajouter_drapeau_edition($saisies);
+               // ajouter au formulaire
+               $ajout = recuperer_fond('inclure/generer_saisies', array_merge($flux['args']['contexte'], array('saisies'=>$saisies)));
+               $flux['data'] = preg_replace('%(<!--extra-->)%is', '<ul class="champs_extras">'.$ajout.'</ul>'."\n".'$1', $flux['data']);
+       }
+
+       return $flux;
+}
+
+
+/**
+ * Ajouter les champs extras soumis par les formulaire CVT editer_xx
+ *
+ * Pour chaque champs extras envoyé par le formulaire d'édition,
+ * ajoute les valeurs dans l'enregistrement à effectuer.
+ * 
+ * @pipeline pre_edition
+ * @param array $flux Données du pipeline
+ * @return array      Données du pipeline
+**/ 
+function cextras_pre_edition($flux){
+
+       include_spip('inc/cextras');
+       include_spip('inc/saisies_lister');
+       $table = $flux['args']['table'];
+       if ($saisies = champs_extras_objet( $table )) {
+
+               // Restreindre les champs postés en fonction des autorisations de les modifier
+               // au cas où un malin voudrait en envoyer plus que le formulaire ne demande
+               $saisies = champs_extras_autorisation('modifier', objet_type($table), $saisies, $flux['args']);
+
+               $saisies = saisies_lister_avec_sql($saisies);
+               foreach ($saisies as $saisie) {
+                       $nom = $saisie['options']['nom'];
+                       if (_request('cextra_' .  $nom)) {
+                               $extra = _request($nom);
+                               if (is_array($extra)) {
+                                       $extra = join(',' , $extra);
+                               }
+                               $flux['data'][$nom] = corriger_caracteres($extra);
+                       }
+               }
+       }
+
+       return $flux;
+}
+
+
+/**
+ * Ajouter les champs extras sur la visualisation de l'objet
+ *
+ * S'il y a des champs extras sur l'objet, la fonction les ajoute
+ * à la vue de l'objet, en enlevant les saisies que la personne n'a
+ * pas l'autorisation de voir.
+ * 
+ * @pipeline afficher_contenu_objet
+ * @param array $flux Données du pipeline
+ * @return array      Données du pipeline
+**/ 
+function cextras_afficher_contenu_objet($flux){
+       // recuperer les saisies de l'objet en cours
+       $objet = $flux['args']['type'];
+       include_spip('inc/cextras');
+       if ($saisies = champs_extras_objet( $table = table_objet_sql($objet) )) {
+               // ajouter au contexte les noms et valeurs des champs extras
+               $saisies_sql = saisies_lister_avec_sql($saisies);
+               $valeurs = sql_fetsel(array_keys($saisies_sql), $table, id_table_objet($table) . '=' . sql_quote($flux['args']['id_objet']));
+               if (!$valeurs) {
+                       $valeurs = array();
+               } else {
+                       // on applique les eventuels traitements definis
+                       // /!\ La saisies-vues/_base applique |propre par defaut si elle ne trouve pas de saisie
+                       // Dans ce cas, certains traitements peuvent être effectués 2 fois !
+                       $saisies_traitees = saisies_lister_avec_traitements($saisies_sql);
+                       unset($saisies_sql);
+
+                       // Fournir $connect et $Pile[0] au traitement si besoin (l'evil eval)
+                       $connect = '';
+                       $Pile = array(0 => (isset($flux['args']['contexte']) ? $flux['args']['contexte'] : array()));
+
+                       foreach ($saisies_traitees as $saisie) {
+                               $traitement = $saisie['options']['traitements'];
+                               $traitement = defined($traitement) ? constant($traitement) : $traitement;
+                               $nom = $saisie['options']['nom'];
+                               list($avant, $apres) = explode('%s', $traitement);
+                               eval('$val = ' . $avant . ' $valeurs[$nom] ' . $apres . ';');
+                               $valeurs[$nom] = $val;
+                       }
+               }
+
+               $contexte = isset($flux['args']['contexte']) ? $flux['args']['contexte'] : array();
+               $contexte = array_merge($contexte, $valeurs);
+
+               // restreindre la vue selon les autorisations
+               $saisies = champs_extras_autorisation('voir', $objet, $saisies, $flux['args']);
+
+               // ajouter les vues
+               $flux['data'] .= recuperer_fond('inclure/voir_saisies', array_merge($contexte, array(
+                                       'saisies' => $saisies,
+                                       'valeurs' => $valeurs,
+               )));
+       }
+
+       return $flux;
+}
+
+/**
+ * Vérification de la validité des champs extras
+ *
+ * Lorsqu'un formulaire 'editer_xx' se présente, la fonction effectue,
+ * pour chaque champs extra les vérifications prévues dans la
+ * définition de la saisie, et retourne les éventuelles erreurs rencontrées.
+ * 
+ * @pipeline formulaire_verifier
+ * @param array $flux Données du pipeline
+ * @return array      Données du pipeline
+**/ 
+function cextras_formulaire_verifier($flux){
+       $form = $flux['args']['form'];
+       
+       if (strncmp($form, 'editer_', 7) !== 0) {
+               return $flux;
+       }
+       
+       $objet = substr($form, 7);
+       if ($saisies = champs_extras_objet( $table = table_objet_sql($objet) )) {
+               include_spip('inc/autoriser');
+               include_spip('inc/saisies');
+
+               // restreindre les saisies selon les autorisations
+               $id_objet = $flux['args']['args'][0]; // ? vraiment toujours ?
+               $saisies = champs_extras_autorisation('modifier', $objet, $saisies, array_merge($flux['args'], array(
+                       'id' => $id_objet,
+                       'contexte' => array()))); // nous ne connaissons pas le contexte dans ce pipeline
+
+               // restreindre les vérifications aux saisies enregistrables
+               $saisies = saisies_lister_avec_sql($saisies);
+
+               $verifier = charger_fonction('verifier', 'inc', true);
+
+               foreach ($saisies as $saisie) {
+                       // verifier obligatoire
+                       $nom = $saisie['options']['nom'];
+                       if (isset($saisie['options']['obligatoire']) and $saisie['options']['obligatoire']
+                       and !_request($nom))
+                       {
+                               $flux['data'][$nom] = _T('info_obligatoire');
+                       
+                       // verifier (api) + normalisation
+                       } elseif ($verifier
+                          AND isset($saisie['verifier']['type'])
+                          AND $verif = $saisie['verifier']['type'])
+                       {
+                               $options = isset($saisie['verifier']['options']) ? $saisie['verifier']['options'] : array();
+                               $normaliser = null;
+                               $valeur = _request($nom);
+                               if ($erreur = $verifier($valeur, $verif, $options, $normaliser)) {
+                                       $flux['data'][$nom] = $erreur;
+                               // si une valeur de normalisation a ete transmis, la prendre.
+                               } elseif (!is_null($normaliser)) {
+                                       set_request($nom, $normaliser);
+                               } else {
+
+                                       // [FIXME] exceptions connues de vérifications (pour les dates entre autres)
+                                       // en attendant une meilleure solution !
+                                       //
+                                       // Lorsque le champ n'est pas rempli dans le formulaire
+                                       // alors qu'une normalisation est demandée,
+                                       // verifier() sort sans indiquer d'erreur (c'est normal).
+                                       // 
+                                       // Sauf que la donnée alors soumise à SQL sera une chaine vide,
+                                       // ce qui ne correspond pas toujours à ce qui est attendu.
+                                       if ((is_string($valeur) and !strlen($valeur) or (is_array($valeur) and $saisie['saisie']=='date'))
+                                         and isset($options['normaliser'])
+                                         and $norme = $options['normaliser']) {
+                                               // Charger la fonction de normalisation théoriquement dans verifier/date
+                                               // et si on en trouve une, obtenir la valeur normalisée
+                                               // qui est théoriquement la valeur par défaut, puisque $valeur est vide
+                                               include_spip("verifier/$verif");
+                                               if ($normaliser = charger_fonction("${verif}_${norme}", "normaliser", true)) {
+                                                       $erreur = null;
+                                                       $defaut = $normaliser($valeur, $options, $erreur);
+                                                       if (is_null($erreur)) {
+                                                               set_request($nom, $defaut);
+                                                       } else {
+                                                               // on affecte l'erreur, mais il est probable que
+                                                               // l'utilisateur ne comprenne pas grand chose
+                                                               $flux['data'][$nom] = $erreur;
+                                                       }
+                                               } else {
+                                                       include_spip('inc/cextras');
+                                                       extras_log("Fonction de normalisation pour ${verif}_${norme} introuvable");
+                                               }
+
+                                       }
+                               }
+                       }
+               }
+       }
+       return $flux;
+}
+
+/**
+ * Insertion dans le pipeline revisions_chercher_label (Plugin révisions)
+ * Trouver le bon label à afficher sur les champs dans les listes de révisions
+ * 
+ * Si un champ est un champ extra, son label correspond au label défini du champs extra
+ * 
+ * @pipeline revisions_chercher_label
+ * @param array $flux Données du pipeline
+ * @return array      Données du pipeline
+**/ 
+function cextras_revisions_chercher_label($flux){
+       $table = table_objet_sql($flux['args']['objet']);
+       $saisies_tables = champs_extras_objet($table);
+       foreach($saisies_tables as $champ){
+               if($champ['options']['nom'] == $flux['args']['champ']){
+                       $flux['data'] = $champ['options']['label'];
+                       break;
+               }
+       }
+       return $flux;
+}
+
+?>
diff --git a/www/plugins/champs_extras3/images/cextras-64.png b/www/plugins/champs_extras3/images/cextras-64.png
new file mode 100644 (file)
index 0000000..44ea5a6
Binary files /dev/null and b/www/plugins/champs_extras3/images/cextras-64.png differ
diff --git a/www/plugins/champs_extras3/inc/cextras.php b/www/plugins/champs_extras3/inc/cextras.php
new file mode 100644 (file)
index 0000000..4df9a64
--- /dev/null
@@ -0,0 +1,398 @@
+<?php
+
+/**
+ * Déclaration d'autorisations pour les champs extras
+ *
+ * @package SPIP\Cextras\Fonctions
+**/
+
+// sécurité
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+
+/**
+ * Log une information
+ *
+ * @param mixed $contenu
+ *     Contenu à loger
+ * @param bool $important
+ *     Est-ce une info importante à loger ?
+ */
+function extras_log($contenu, $important=false) {
+       if ($important) {
+               spip_log($contenu, 'extras.'. _LOG_INFO);
+       } else {
+               spip_log($contenu, 'extras.'. _LOG_INFO_IMPORTANTE);
+       }
+}
+
+
+/**
+ * Retourne la liste des objets valides utilisables
+ *
+ * C'est à dire les objets dont on peut afficher les champs dans les
+ * formulaires, ce qui correspond aux objets éditoriaux déclarés
+ * comme avec l'option principale.
+ *
+ * @return array
+ *    Couples (table sql => description de l'objet éditorial)
+ */
+function cextras_objets_valides(){
+
+       $objets = array();
+       $tables = lister_tables_objets_sql();
+       ksort($tables);
+
+       foreach($tables as $table => $desc) {
+               if (($desc['editable'] == 'oui') and ($desc['principale'] == 'oui')) {
+                       $objets[$table] = $desc;
+               }
+       }
+
+       return $objets;
+}
+
+
+
+
+/**
+ * Liste les saisies ayant des traitements
+ *
+ * Retourne uniquement les saisies ayant traitements à appliquer sur
+ * les champs tel que des traitements typo ou traitements raccourcis.
+ *
+ * @param array $saisies
+ *     Liste de saisies
+ * @param String $tri
+ *     Tri par défaut des résultats (s'ils ne sont pas deja triés) ('nom', 'identifiant')
+ * @return array
+ *     Liste de ces saisies triées par nom ayant des traitements définis
+**/
+function saisies_lister_avec_traitements($saisies, $tri = 'nom') {
+       return saisies_lister_avec_option('traitements', $saisies, $tri);
+}
+
+
+
+/**
+ * Créer les champs extras (colonnes en base de données)
+ * definies par le lot de saisies donné 
+ *
+ * @param string $table
+ *     Nom de la table SQL
+ * @param array $saisies
+ *     Description des saisies
+ * @return bool|void
+ *     False si pas de table ou aucune saisie de type SQL
+**/
+function champs_extras_creer($table, $saisies) {
+       if (!$table) {
+               return false;
+       }
+       if (!is_array($saisies) OR !count($saisies)) {
+               return false;
+       }
+       
+       // uniquement les saisies décrivant SQL
+       include_spip('inc/saisies');
+       $saisies = saisies_lister_avec_sql($saisies);
+       if (!$saisies) {
+               return false;
+       }
+
+       $desc = lister_tables_objets_sql($table);
+
+       // parcours des saisies et ajout des champs extras nouveaux dans
+       // la description de la table
+       foreach ($saisies as $saisie) {
+               $nom = $saisie['options']['nom'];
+               // le champ ne doit pas deja exister !
+               if (!isset($desc['field'][$nom])) {
+                       $desc['field'][$nom] = $saisie['options']['sql'];
+               }
+       }
+       
+       // executer la mise a jour
+       include_spip('base/create');
+       creer_ou_upgrader_table($table, $desc, true, true);
+}
+
+
+/**
+ * Supprimer les champs extras (colonne dans la base de données)
+ * definies par le lot de saisies donné
+ *
+ * @param string $table
+ *     Nom de la table SQL
+ * @param array $saisies
+ *     Description des saisies
+ * @return bool
+ *     False si pas de table, aucune saisie de type SQL, ou une suppression en erreur
+ *     True si toutes les suppressions sont OK
+**/
+function champs_extras_supprimer($table, $saisies) {
+       if (!$table) {
+               return false;
+       }
+       if (!is_array($saisies) OR !count($saisies)) {
+               return false;
+       }
+       
+       include_spip('inc/saisies');
+       $saisies = saisies_lister_avec_sql($saisies);
+
+       if (!$saisies) {
+               return false;
+       }       
+       
+       $desc = lister_tables_objets_sql($table);
+
+       $ok = true;
+       foreach ($saisies as $saisie) {
+               $nom = $saisie['options']['nom'];       
+               if (isset($desc['field'][$nom])) {
+                       $ok &= sql_alter("TABLE $table DROP COLUMN $nom");
+               }
+       }
+       return $ok;
+}
+
+
+/**
+ * Modifier les champs extras (colonne dans la base de données)
+ * definies par le lot de saisies donné   
+ *
+ * Permet de changer la structure SQL ou le nom de la colonne
+ * des saisies
+ * 
+ * @param string $table
+ *     Nom de la table SQL
+ * @param array $saisies_nouvelles
+ *     Description des saisies nouvelles
+ * @param array $saisies_anciennes
+ *     Description des saisies anciennes
+ * @return bool
+ *     True si les changement SQL sont correctement effectués
+**/
+function champs_extras_modifier($table, $saisies_nouvelles, $saisies_anciennes) {
+       $ok = true;
+       foreach ($saisies_nouvelles as $id => $n) {
+               $n_nom = $n['options']['nom'];
+               $n_sql = $n['options']['sql'];
+               $a_nom = $saisies_anciennes[$id]['options']['nom'];
+               $a_sql = $saisies_anciennes[$id]['options']['sql'];
+               if ($n_nom != $a_nom OR $n_sql != $n_sql) {
+                       $ok &= sql_alter("TABLE $table CHANGE COLUMN $a_nom $n_nom $n_sql");
+               }
+       }
+       return $ok;
+}
+
+
+/**
+ * Complète un tableau de mise à jour de plugin afin d'installer les champs extras.
+ *
+ * @example
+ *     ```
+ *     cextras_api_upgrade(motus_declarer_champs_extras(), $maj['create']);
+ *     ```
+ *
+ * @param array $declaration_champs_extras
+ *     Liste de champs extras à installer, c'est à dire la liste de saisies
+ *     présentes dans le pipeline declarer_champs_extras() du plugin qui demande l'installation
+ * @param array $maj_item
+ *     Un des éléments du tableau d'upgrade $maj,
+ *     il sera complété des actions d'installation des champs extras demandés
+ * 
+ * @return bool
+ *     false si les déclarations sont mal formées
+ *     true sinon
+**/
+function cextras_api_upgrade($declaration_champs_extras, &$maj_item) {
+       if (!is_array($declaration_champs_extras)) {
+               return false;
+       }
+       if (!is_array($maj_item)) {
+               $maj_item = array();
+       }
+       foreach($declaration_champs_extras as $table=>$champs) {
+               $maj_item[] = array('champs_extras_creer',$table, $champs);
+       }
+
+       return true;
+}
+
+
+/**
+ * Supprime les champs extras declarés
+ *
+ * @example
+ *     ```
+ *     cextras_api_vider_tables(motus_declarer_champs_extras());
+ *     ```
+ * 
+ * @param array $declaration_champs_extras
+ *     Liste de champs extras à désinstaller, c'est à dire la liste de saisies
+ *     présentes dans le pipeline declarer_champs_extras() du plugin qui demande la désinstallation
+ * 
+ * @return bool
+ *     false si déclaration mal formée
+ *     true sinon
+**/
+function cextras_api_vider_tables($declaration_champs_extras) {
+       if (!is_array($declaration_champs_extras)) {
+               return false;
+       }
+       foreach($declaration_champs_extras as $table=>$champs) {
+               champs_extras_supprimer($table, $champs);
+       }
+       return true;
+}
+
+
+/*
+ * 
+ * Rechercher les champs non declares mais existants
+ * dans la base de donnee en cours
+ * (code d'origine : _fil_)
+ * 
+ */
+
+/**
+ * Liste les tables et les champs que le plugin et spip savent gérer
+ * mais qui ne sont pas déclarés à SPIP
+ * 
+ * @param string $connect
+ *     Nom du connecteur de base de données
+ * @return array
+ *     Tableau (table => couples(colonne => description SQL))
+ */
+function extras_champs_utilisables($connect='') {
+       $tout = extras_champs_anormaux($connect);
+       $objets = cextras_objets_valides();
+       $utilisables = array_intersect_key($tout, $objets);
+       ksort($utilisables);
+       return $utilisables;
+}
+
+/**
+ * Liste les champs anormaux par rapport aux définitions de SPIP
+ *
+ * @note
+ *     Aucune garantie que $connect autre que la connexion principale fasse quelque chose
+ *
+ * @param string $connect
+ *     Nom du connecteur de base de données
+ * @return array
+ *     Tableau (table => couples(colonne => description SQL))
+ */
+function extras_champs_anormaux($connect='') {
+       static $tout = false;
+       if ($tout !== false) {
+               return $tout;
+       }
+       // recuperer les tables et champs de la base de donnees
+       // les vrais de vrai dans la base sql...
+       $tout = extras_base($connect);
+
+       // recuperer les champs SPIP connus
+       // si certains ne sont pas declares alors qu'ils sont presents
+       // dans la base sql, on pourra proposer de les utiliser comme champs
+       // extras (plugin interface).
+       include_spip('base/objets');
+       $tables_spip = lister_tables_objets_sql();
+
+       // chercher ce qui est different
+       $ntables = array();
+       $nchamps = array();
+       // la table doit être un objet editorial
+       $tout = array_intersect_key($tout, $tables_spip);
+       foreach ($tout as $table => $champs) {
+               // la table doit être un objet editorial principal
+               if ($tables_spip[$table]['principale'] == 'oui') {
+                       // pour chaque champ absent de la déclaration, on le note dans $nchamps.
+                       foreach($champs as $champ => $desc) {
+                               if (!isset($tables_spip[$table]['field'][$champ])) {
+                                       if (!isset($nchamps[$table])) {
+                                               $nchamps[$table] = array(); 
+                                       }
+                                       $nchamps[$table][$champ] = $desc;
+                               }
+                       }
+               }
+       }
+
+       if($nchamps) {
+               $tout = $nchamps;
+       } else {
+               $tout = array();
+       }
+
+       return $tout;
+}
+
+/**
+ * Établit la liste de tous les champs de toutes les tables de la connexion
+ * sql donnée
+ * 
+ * Ignore la table 'spip_test'
+ *
+ * @param string $connect
+ *     Nom du connecteur de base de données
+ * @return array
+ *     Tableau (table => couples(colonne => description SQL))
+ */
+function extras_base($connect='') {
+       $champs = array();
+       
+       foreach (extras_tables($connect) as $table) {
+               if ($table != 'spip_test') {
+                       $champs[$table] = extras_champs($table, $connect);
+               }
+       }
+       return $champs;
+}
+
+
+/**
+ * Liste les tables SQL disponibles de la connexion sql donnée
+ * 
+ * @param string $connect
+ *     Nom du connecteur de base de données
+ * @return array
+ *     Liste de tables SQL
+ */
+function extras_tables($connect='') {
+       $a = array();
+       $taille_prefixe = strlen( $GLOBALS['connexions'][$connect ? $connect : 0]['prefixe'] );
+
+       if ($s = sql_showbase(null, $connect)) {
+               while ($t = sql_fetch($s, $connect)) {
+                               $t = 'spip' . substr(array_pop($t), $taille_prefixe);
+                               $a[] = $t;
+               }
+       }
+       return $a;
+}
+
+
+/**
+ * Liste les champs dispos dans la table SQL de la connexion sql donnée
+ *
+ * @param string $table
+ *     Nom de la table SQL
+ * @param string $connect
+ *     Nom du connecteur de base de données
+ * @return array
+ *     Couples (colonne => description SQL)
+ */
+function extras_champs($table, $connect) {
+       $desc = sql_showtable($table, true, $connect);
+       if (is_array($desc['field'])) {
+               return $desc['field'];
+       } else {
+               return array();
+       }
+}
+
+?>
diff --git a/www/plugins/champs_extras3/inc/cextras_autoriser.php b/www/plugins/champs_extras3/inc/cextras_autoriser.php
new file mode 100644 (file)
index 0000000..dceacd1
--- /dev/null
@@ -0,0 +1,558 @@
+<?php
+
+/**
+ * Déclaration d'autorisations pour les champs extras
+ *
+ * @package SPIP\Cextras\Autorisations
+**/
+
+// sécurité
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+
+/**
+ * Fonction d'appel pour le pipeline autoriser
+ * @pipeline autoriser
+ */
+function cextras_autoriser(){}
+
+
+/**
+ * Retourne si une saisie peut s'afficher ou non 
+ *
+ * Teste les options de restrictions de la saisie si il y en a
+ * et calcule en fonction l'autorisation
+ * 
+ * @param array $saisie
+ *     Saisie que l'on traite.
+ * @param string $action
+ *     Le type d'action : voir | modifier
+ * @param string $table
+ *     La table d'application : spip_articles
+ * @param int $id
+ *     Identifiant de la table : 3
+ * @param array $qui
+ *     Description de l'auteur en cours
+ * @param Array $opt
+ *     Options de l'autorisation
+ * @return Bool
+ *     La saisie peut elle s'afficher ?
+**/
+function champs_extras_restrictions($saisie, $action, $table, $id, $qui, $opt) {
+       if (!$saisie) {
+               return true;
+       }
+       
+       if (!isset($saisie['options']['restrictions']) OR !$saisie['options']['restrictions']) {
+               return true;
+       }
+
+       if (!in_array($action, array('voir', 'modifier'))) {
+               return true;
+       }
+
+       $restrictions = $saisie['options']['restrictions'];
+
+       // tester si des options d'autorisations sont definies pour cette saisie
+       // et les appliquer.
+       // peut être 'voir' ou 'modifier'
+               // dedans peut être par type d'auteur 'webmestre', 'admin'
+       // peut être par secteur parent.
+       // peut être par branche parente.
+       // peut être par groupe parent.
+
+       // restriction par type d'auteur
+       if (isset($restrictions[$action]['auteur']) and $auteur = $restrictions[$action]['auteur']) {
+               switch ($auteur) {
+                       case 'webmestre':
+                               if (!autoriser('webmestre')) {
+                                       return false;
+                               }
+                               break;
+                       case 'admin':
+                               if ($qui['statut'] != '0minirezo' AND !$qui['restreint']) {
+                                       return false;
+                               }
+                               break;
+               }
+       }
+
+       // pour les autres autorisations, dès qu'une est valide, on part.
+       // cela permet de dire que l'on peut restreindre au secteur 1 et à la branche 3,
+       // branche en dehors du secteur 1
+       // le cumul des autorisations rendrait impossible cela
+       unset($restrictions['voir']);
+       unset($restrictions['modifier']);
+
+       // enlever tous les false (0, '')
+       $restrictions = array_filter($restrictions);
+       
+       if ($restrictions) {
+               foreach ($restrictions as $type => $ids) {
+                       $ids = explode(':', $ids);
+                       $cible = rtrim($type, 's');
+                       $restriction = charger_fonction("restreindre_extras_objet_sur_$cible", "inc", true);
+
+                       if ($restriction and $restriction($opt['type'], $opt['id_objet'], $opt, $ids, $cible)) {
+                               return true;
+                       }
+               }
+               // aucune des restrictions n'a ete validee
+               return false;
+       }
+       
+       return true;
+}
+
+/**
+ * Autorisation de voir un champ extra
+ *
+ * Cherche une autorisation spécifique pour le champ si elle existe
+ * (autoriser_{objet}_voirextra_{colonne}_dist), sinon applique
+ * l'autorisation prévue par la description de la saisie
+ * 
+ * @example
+ *     ```
+ *     autoriser('voirextra','auteur', $id_auteur,'',
+ *         array('champ'=>'prenom', 'saisie'=>$saisie, ...));
+ *     ```
+ *     Appelle ``autoriser_auteur_voirextra_prenom_dist()`` si la fonction existe...
+ * 
+ * @param  string $faire Action demandée
+ * @param  string $type  Type d'objet sur lequel appliquer l'action
+ * @param  int    $id    Identifiant de l'objet
+ * @param  array  $qui   Description de l'auteur demandant l'autorisation
+ * @param  array  $opt   Options de cette autorisation
+ * @return bool          true s'il a le droit, false sinon
+**/
+function autoriser_voirextra_dist($faire, $type, $id, $qui, $opt){
+       if (isset($opt['saisie'])) {
+               // tester des fonctions d'autorisations plus precises declarees
+               if ($opt['champ']) {
+                       $f = 'autoriser_' . $opt['type'] . '_voirextra_' . $opt['champ'];
+                       if (function_exists($f) OR function_exists($f .= '_dist')) {
+                               return $f($faire, $type, $id, $qui, $opt);
+                       }
+               }
+               return champs_extras_restrictions($opt['saisie'], substr($faire, 0, -5), $opt['table'], $id, $qui, $opt);
+       }
+       return true;
+}
+
+/**
+ * Autorisation de modifier un champ extra
+ *
+ * Cherche une autorisation spécifique pour le champ si elle existe
+ * (autoriser_{objet}_modifierextra_{colonne}_dist), sinon applique
+ * l'autorisation prévue par la description de la saisie
+ * 
+ * @example
+ *     ```
+ *     autoriser('modifierextra','auteur', $id_auteur,'',
+ *         array('champ'=>'prenom', 'saisie'=>$saisie, ...));
+ *     ```
+ *     Appelle ``autoriser_auteur_modifierextra_prenom_dist()`` si elle existe
+ *
+ * @param  string $faire Action demandée
+ * @param  string $type  Type d'objet sur lequel appliquer l'action
+ * @param  int    $id    Identifiant de l'objet
+ * @param  array  $qui   Description de l'auteur demandant l'autorisation
+ * @param  array  $opt   Options de cette autorisation
+ * @return bool          true s'il a le droit, false sinon
+**/
+function autoriser_modifierextra_dist($faire, $type, $id, $qui, $opt){
+       if (isset($opt['saisie'])) {
+               // tester des fonctions d'autorisations plus precises declarees
+               if ($opt['champ']) {
+                       $f = 'autoriser_' . $opt['type'] . '_modifierextra_' . $opt['champ'];
+                       if (function_exists($f) OR function_exists($f .= '_dist')) {
+                               return $f($faire, $type, $id, $qui, $opt);
+                       }
+               }
+               return champs_extras_restrictions($opt['saisie'], substr($faire, 0, -5), $opt['table'], $id, $qui, $opt);
+       }
+       return true;
+}
+
+
+
+/**
+ * Fonction d'aide pour créer des autorisations de champs spécifiques
+ * 
+ * Permet d'indiquer que tels champs extras se limitent à telle ou telle rubrique
+ * et cela en créant à la volée les fonctions d'autorisations adéquates.
+ * 
+ * @example
+ *     ```
+ *     restreindre_extras('article', array('nom', 'prenom'), array(8, 12));
+ *     restreindre_extras('site', 'url_doc', 18, true); // recursivement aux sous rubriques
+ *     ```
+ *
+ * @param string $objet
+ *     Objet possédant les extras
+ * @param mixed $noms
+ *     Nom des extras a restreindre
+ * @param mixed $ids
+ *     Identifiant (des rubriques par defaut) sur lesquelles s'appliquent les champs
+ * @param string $cible
+ *     Type de la fonction de test qui sera appelee, par defaut "rubrique". Peut aussi etre "secteur", "groupe" ou des fonctions definies
+ * @param bool $recursif
+ *     Application recursive sur les sous rubriques ? ATTENTION, c'est gourmand en requetes SQL :)
+ * @return bool
+ *     true si on a fait quelque chose
+ */
+function restreindre_extras($objet, $noms=array(), $ids=array(), $cible='rubrique', $recursif=false) {
+       if (!$objet or !$noms or !$ids) {
+               return false;
+       }
+
+       if (!is_array($noms)) { $noms = array($noms); }
+       if (!is_array($ids))  { $ids  = array($ids); }
+
+       #$objet = objet_type($objet);
+       $ids = var_export($ids, true);
+       $recursif = var_export($recursif, true);
+
+       foreach ($noms as $nom) {
+               $m = "autoriser_" . $objet . "_modifierextra_" . $nom . "_dist";
+               $v = "autoriser_" . $objet . "_voirextra_" . $nom . "_dist";
+
+               $code = "
+                       if (!function_exists('$m')) {
+                               function $m(\$faire, \$quoi, \$id, \$qui, \$opt) {
+                                       return _restreindre_extras_objet('$objet', \$id, \$opt, $ids, '$cible', $recursif);
+                               }
+                       }
+                       if (!function_exists('$v')) {
+                               function $v(\$faire, \$quoi, \$id, \$qui, \$opt) {
+                                       return autoriser('modifierextra', \$quoi, \$id, \$qui, \$opt);
+                               }
+                       }
+               ";
+
+               # var_dump($code);
+               eval($code);
+       }
+
+       return true;
+}
+
+
+
+/**
+ * Fonction d'autorisation interne à la fonction restreindre_extras()
+ * 
+ * Teste si un objet à le droit d'afficher des champs extras
+ * en fonction de la rubrique (ou autre defini dans la cible)
+ * dans laquelle il se trouve et des rubriques autorisées
+ *
+ * On met en cache pour éviter de plomber le serveur SQL, vu que la plupart du temps
+ * un hit demandera systématiquement le même objet/id_objet lorsqu'il affiche
+ * un formulaire.
+ *
+ * @param string $objet
+ *     Objet possédant les extras
+ * @param int $id_objet
+ *     Identifiant de l'objet possédant les extras
+ * @param array $opt
+ *     Options des autorisations
+ * @param mixed $ids
+ *     Identifiant(s) (en rapport avec la cible) sur lesquelles s'appliquent les champs
+ * @param string $cible
+ *     Type de la fonction de test qui sera appelee, par defaut "rubrique".
+ *     Peut aussi etre "secteur", "groupe" ou des fonctions definies
+ * @param bool $recursif
+ *     Application recursive sur les sous rubriques ? ATTENTION, c'est
+ *     gourmand en requetes SQL :)
+ * @return bool
+ *     Autorisé ou non
+**/
+function _restreindre_extras_objet($objet, $id_objet, $opt, $ids, $cible='rubrique', $recursif=false) {
+       static $autorise = array();
+
+       if ( !isset($autorise[$objet]) ) { $autorise[$objet] = array(); }
+
+       $cle = $cible . implode('-', $ids);
+       if (isset($autorise[$objet][$id_objet][$cle])) {
+               return $autorise[$objet][$id_objet][$cle];
+       }
+
+       $f = charger_fonction("restreindre_extras_objet_sur_$cible", "inc", true);
+       if ($f) {
+               return $autorise[$objet][$id_objet][$cle] =
+                       $f($objet, $id_objet, $opt, $ids, $recursif);
+       }
+
+       // pas trouve... on n'affiche pas... Pan !
+       return $autorise[$objet][$id_objet][$cle] = false;
+}
+
+
+/**
+ * Fonction d'autorisation interne à la fonction restreindre_extras()
+ * 
+ * Teste si un objet à le droit d'afficher des champs extras
+ * en fonction de la rubrique (ou autre defini dans la cible)
+ * dans laquelle il se trouve et des rubriques autorisées
+ * 
+ * Le dernier argument donne la colonne à chercher dans l'objet correspondant
+ *
+ * @param string $objet
+ *     Objet possédant les extras
+ * @param int $id_objet
+ *     Identifiant de l'objet possédant les extras
+ * @param array $opt
+ *     Options des autorisations
+ * @param mixed $ids
+ *     Identifiant(s) (en rapport avec la cible) sur lesquelles s'appliquent les champs
+ * @param bool $_id_cible
+ *     Nom de la colonne SQL cible (id_rubrique, id_secteur, id_groupe...)
+ * @return bool|int
+ *     - true : autorisé,
+ *     - false : non autorisé,
+ *     - 0 : incertain.
+**/
+function _restreindre_extras_objet_sur_cible($objet, $id_objet, $opt, $ids, $_id_cible) {
+
+       $id_cible = 0;
+       if (isset($opt['contexte'][$_id_cible])) {
+               $id_cible = intval($opt['contexte'][$_id_cible]);
+       }
+  
+       if (!$id_cible) {
+               // on tente de le trouver dans la table de l'objet
+               $table = table_objet_sql($objet);
+               $id_table = id_table_objet($table);
+               include_spip('base/objets');
+               $desc = lister_tables_objets_sql($table);
+  
+               if (isset($desc['field'][$_id_cible])) {
+                       $id_cible = sql_getfetsel($_id_cible, $table, "$id_table=".sql_quote($id_objet));
+               }
+    }
+
+       if (!$id_cible) {
+               // on essaie aussi dans le contexte d'appel de la page
+               $id_cible = _request($_id_cible);
+               
+               // on tente en cas de id_secteur de s'appuyer sur un eventuel id_rubrique
+               if (!$id_cible and $_id_cible == 'id_secteur') {
+                       if ($i = _request('id_rubrique')) {
+                               $id_cible = sql_getfetsel('id_secteur', 'spip_rubriques', 'id_rubrique='.sql_quote($i));
+                       }
+               }
+       }
+
+       if (!$id_cible) {
+               return array($id_cible, false);
+       }
+
+    if (in_array($id_cible, $ids)) {
+               return array($id_cible, true);
+    }
+
+    return array($id_cible, false);
+}
+
+
+
+/**
+ * Fonction d'autorisation interne à la fonction restreindre_extras()
+ * spécifique au test d'appartenance à une branche de rubrique
+ *
+ * @note ATTENTION, c'est gourmand en requetes SQL :)
+ * 
+ * @see inc_restreindre_extras_objet_sur_rubrique_dist()
+ * @param string $objet
+ *     Objet possédant les extras
+ * @param int $id_objet
+ *     Identifiant de l'objet possédant les extras
+ * @param array $opt
+ *     Options des autorisations
+ * @param mixed $ids
+ *     Identifiant(s) des branches de rubrique sur lesquelles s'appliquent les champs
+ * @param bool $recursif
+ *     Non utilisé
+ * @return bool
+ *     Autorisé ou non
+ */
+function inc_restreindre_extras_objet_sur_branche_dist($objet, $id_objet, $opt, $ids, $recursif) {
+       return inc_restreindre_extras_objet_sur_rubrique_dist($objet, $id_objet, $opt, $ids, true);
+}
+
+/**
+ * Fonction d'autorisation interne à la fonction restreindre_extras()
+ * spécifique au test d'appartenance à une rubrique
+ *
+ * @param string $objet
+ *     Objet possédant les extras
+ * @param int $id_objet
+ *     Identifiant de l'objet possédant les extras
+ * @param array $opt
+ *     Options des autorisations
+ * @param mixed $ids
+ *     Identifiant(s) des rubriques sur lesquelles s'appliquent les champs
+ * @param bool $recursif
+ *     Application récursive sur les sous rubriques ?
+ *     ATTENTION, c'est gourmand en requetes SQL :)
+ * @return bool
+ *     Autorisé ou non
+ */
+function inc_restreindre_extras_objet_sur_rubrique_dist($objet, $id_objet, $opt, $ids, $recursif) {
+
+       list($id_rubrique, $ok) = _restreindre_extras_objet_sur_cible($objet, $id_objet, $opt, $ids, 'id_rubrique');
+
+       if ($ok) {
+               return true;
+       }
+
+       if (!$recursif) {
+               return false;
+       }
+
+       // tester si un parent proche existe lorsqu'on ne connait pas la rubrique.
+       if (!$id_rubrique AND $id_rubrique = _request('id_parent')) {
+               if (in_array($id_rubrique, $ids)) {
+                       return true;
+               }
+       }
+       
+       // on teste si l'objet est dans une sous rubrique de celles mentionnee...
+       if ($id_rubrique) {
+               $id_parent = $id_rubrique;
+               while ($id_parent = sql_getfetsel("id_parent", "spip_rubriques", "id_rubrique=" . sql_quote($id_parent))) {
+                       if (in_array($id_parent, $ids)) {
+                               return true;
+                       }
+               }
+       }
+                 
+    return false;
+}
+
+
+
+/**
+ * Fonction d'autorisation interne à la fonction restreindre_extras()
+ * spécifique au test d'appartenance à un secteur
+ *
+ * @param string $objet
+ *     Objet possédant les extras
+ * @param int $id_objet
+ *     Identifiant de l'objet possédant les extras
+ * @param array $opt
+ *     Options des autorisations
+ * @param mixed $ids
+ *     Identifiant(s) des secteurs sur lesquelles s'appliquent les champs
+ * @param bool $recursif
+ *     Non utilisé
+ * @return bool
+ *     Autorisé ou non
+ */
+function inc_restreindre_extras_objet_sur_secteur_dist($objet, $id_objet, $opt, $ids, $recursif=false) {
+       list($id_secteur, $ok) = _restreindre_extras_objet_sur_cible($objet, $id_objet, $opt, $ids, 'id_secteur');
+       return $ok;
+}
+
+
+
+/**
+ * Fonction d'autorisation interne à la fonction restreindre_extras()
+ * spécifique au test d'appartenance à un groupe de mot
+ *
+ * Alias de groupemot
+ *
+ * @see inc_restreindre_extras_objet_sur_groupemot_dist()
+ * @param string $objet
+ *     Objet possédant les extras
+ * @param int $id_objet
+ *     Identifiant de l'objet possédant les extras
+ * @param array $opt
+ *     Options des autorisations
+ * @param mixed $ids
+ *     Identifiant(s) des groupes de mots sur lesquelles s'appliquent les champs
+ * @param bool $recursif
+ *     True pour appliquer aux branches d'un groupe de mot
+ *     (avec plugin spécifique groupe de mots arborescents)
+ * @return bool
+ *     Autorisé ou non
+ */
+function inc_restreindre_extras_objet_sur_groupe_dist($objet, $id_objet, $opt, $ids, $recursif) {
+       return inc_restreindre_extras_objet_sur_groupemot_dist($objet, $id_objet, $opt, $ids, $recursif);
+}
+
+/**
+ * Fonction d'autorisation interne à la fonction restreindre_extras()
+ * spécifique au test d'appartenance à un groupe de mot
+ *
+ * @param string $objet
+ *     Objet possédant les extras
+ * @param int $id_objet
+ *     Identifiant de l'objet possédant les extras
+ * @param array $opt
+ *     Options des autorisations
+ * @param mixed $ids
+ *     Identifiant(s) des groupes de mots sur lesquelles s'appliquent les champs
+ * @param bool $recursif
+ *     True pour appliquer aux branches d'un groupe de mot
+ *     (avec plugin spécifique groupe de mots arborescents)
+ * @return bool
+ *     Autorisé ou non
+ */
+function inc_restreindre_extras_objet_sur_groupemot_dist($objet, $id_objet, $opt, $ids, $recursif) {
+       list($id_groupe, $ok) = _restreindre_extras_objet_sur_cible($objet, $id_objet, $opt, $ids, 'id_groupe');
+       if ($ok) {
+               return true;
+       }
+
+       // on teste si l'objet est dans un sous groupe de celui mentionne...
+       // sauf qu'il n'existe pas encore de groupe avec id_parent :) - sauf avec plugin
+       // on desactive cette option si cette colonne est absente
+       if ($id_groupe and $recursif) {
+               $trouver_table = charger_fonction('trouver_table', 'base');
+               $desc = $trouver_table("groupes_mots");
+               if (isset($desc['field']['id_parent'])) {
+                       $id_parent = $id_groupe;
+                       while ($id_parent = sql_getfetsel("id_parent", "spip_groupes_mots", "id_parent=" . sql_quote($id_parent))) {
+                               if (in_array($id_parent, $ids)) {
+                                       return true;
+                               }
+                       }
+               }
+       }
+                 
+    return false;
+}
+
+/**
+ * Fonction d'autorisation interne à la fonction restreindre_extras()
+ * spécifique au test de la présence d'une composition
+ *
+ * @param string $objet
+ *     Objet possédant les extras
+ * @param int $id_objet
+ *     Identifiant de l'objet possédant les extras
+ * @param array $opt
+ *     Options des autorisations
+ * @param mixed $ids
+ *     Identifiant(s) des compositions sur lesquelles s'appliquent les champs
+ * @param bool $recursif
+ *     Inutile, la récursivité est prise en charge par compositions_determiner()
+ * @return bool
+ *     Autorisé ou non
+ */
+function inc_restreindre_extras_objet_sur_composition_dist($objet, $id_objet, $opt, $ids, $recursif) {
+
+       if (!function_exists('compositions_determiner')) {
+               include_spip('compositions_fonctions');
+       }
+
+       if (function_exists('compositions_determiner')) {
+               $composition = compositions_determiner($objet, $id_objet);
+               return in_array($composition, $ids);
+       }
+
+       return false;
+}
+
+?>
diff --git a/www/plugins/champs_extras3/lang/cextras.xml b/www/plugins/champs_extras3/lang/cextras.xml
new file mode 100644 (file)
index 0000000..94b3adf
--- /dev/null
@@ -0,0 +1,23 @@
+<traduction module="cextras" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/champs_extras/core/trunk/lang/" reference="fr">
+       <langue code="ar" url="http://trad.spip.net/tradlang_module/cextras?lang_cible=ar" total="4" traduits="2" relire="0" modifs="0" nouveaux="2" pourcent="50.00">
+       </langue>
+       <langue code="en" url="http://trad.spip.net/tradlang_module/cextras?lang_cible=en" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="kent1" lien="http://trad.spip.net/auteur/kent1" />
+       </langue>
+       <langue code="es" url="http://trad.spip.net/tradlang_module/cextras?lang_cible=es" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Raquel S. Bujaldón" lien="http://trad.spip.net/auteur/raquel-s-bujaldon" />
+       </langue>
+       <langue code="fr" url="http://trad.spip.net/tradlang_module/cextras?lang_cible=fr" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       </langue>
+       <langue code="it" url="http://trad.spip.net/tradlang_module/cextras?lang_cible=it" total="4" traduits="2" relire="0" modifs="0" nouveaux="2" pourcent="50.00">
+       </langue>
+       <langue code="nl" url="http://trad.spip.net/tradlang_module/cextras?lang_cible=nl" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
+       </langue>
+       <langue code="ru" url="http://trad.spip.net/tradlang_module/cextras?lang_cible=ru" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Serge Markitanenko" lien="http://trad.spip.net/auteur/serge-markitanenko" />
+       </langue>
+       <langue code="sk" url="http://trad.spip.net/tradlang_module/cextras?lang_cible=sk" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="jaro" lien="http://trad.spip.net/auteur/jaro" />
+       </langue>
+</traduction>
diff --git a/www/plugins/champs_extras3/lang/cextras_ar.php b/www/plugins/champs_extras3/lang/cextras_ar.php
new file mode 100644 (file)
index 0000000..4700cfa
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/cextras?lang_cible=ar
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'cextra_par_defaut' => 'قيمة تلقائية',
+
+       // T
+       'type' => '@type@'
+);
+
+?>
diff --git a/www/plugins/champs_extras3/lang/cextras_en.php b/www/plugins/champs_extras3/lang/cextras_en.php
new file mode 100644 (file)
index 0000000..657e20f
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/cextras?lang_cible=en
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'cextra_par_defaut' => 'Default value',
+
+       // P
+       'pas_auteur' => 'no author',
+
+       // T
+       'type' => '@type@',
+
+       // Z
+       'zbug_balise_argument_non_texte' => 'The @nb@ argument in the tag @balise@ should be text typed'
+);
+
+?>
diff --git a/www/plugins/champs_extras3/lang/cextras_es.php b/www/plugins/champs_extras3/lang/cextras_es.php
new file mode 100644 (file)
index 0000000..e5db0b5
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/cextras?lang_cible=es
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'cextra_par_defaut' => 'Valor por defecto',
+
+       // P
+       'pas_auteur' => 'sin autor',
+
+       // T
+       'type' => '@type@',
+
+       // Z
+       'zbug_balise_argument_non_texte' => 'El argumento @nb@ de la etiqueta @balise@ debe ser de tipo texto'
+);
+
+?>
diff --git a/www/plugins/champs_extras3/lang/cextras_fr.php b/www/plugins/champs_extras3/lang/cextras_fr.php
new file mode 100644 (file)
index 0000000..c639462
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// Fichier source, a modifier dans svn://zone.spip.org/spip-zone/_plugins_/champs_extras/core/trunk/lang/
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'cextra_par_defaut' => 'Valeur par défaut',
+
+       // P
+       'pas_auteur' => 'pas d’auteur',
+
+       // T
+       'type' => '@type@',
+
+       // Z
+       'zbug_balise_argument_non_texte' => 'L’argument @nb@ dans la balise @balise@ doit être de type texte'
+);
+
+?>
diff --git a/www/plugins/champs_extras3/lang/cextras_it.php b/www/plugins/champs_extras3/lang/cextras_it.php
new file mode 100644 (file)
index 0000000..1225280
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/cextras?lang_cible=it
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'cextra_par_defaut' => 'Valore predefinito',
+
+       // T
+       'type' => '@type@'
+);
+
+?>
diff --git a/www/plugins/champs_extras3/lang/cextras_nl.php b/www/plugins/champs_extras3/lang/cextras_nl.php
new file mode 100644 (file)
index 0000000..c7e7fae
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/cextras?lang_cible=nl
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'cextra_par_defaut' => 'Standaardwaarde',
+
+       // P
+       'pas_auteur' => 'geen auteur',
+
+       // T
+       'type' => '@type@',
+
+       // Z
+       'zbug_balise_argument_non_texte' => 'Argument @nb@ van het baken @balise@ moet van het type tekst zijn'
+);
+
+?>
diff --git a/www/plugins/champs_extras3/lang/cextras_ru.php b/www/plugins/champs_extras3/lang/cextras_ru.php
new file mode 100644 (file)
index 0000000..2d0c6e3
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/cextras?lang_cible=ru
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'cextra_par_defaut' => 'Значение по умолчанию',
+
+       // P
+       'pas_auteur' => 'автор не указан',
+
+       // T
+       'type' => '@type@',
+
+       // Z
+       'zbug_balise_argument_non_texte' => 'Параметры @nb@ для тега @balise@ должны быть в текстовом формате'
+);
+
+?>
diff --git a/www/plugins/champs_extras3/lang/cextras_sk.php b/www/plugins/champs_extras3/lang/cextras_sk.php
new file mode 100644 (file)
index 0000000..46a4214
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/cextras?lang_cible=sk
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'cextra_par_defaut' => 'Predvolená hodnota',
+
+       // P
+       'pas_auteur' => 'bez autora',
+
+       // T
+       'type' => '@type@',
+
+       // Z
+       'zbug_balise_argument_non_texte' => 'Parameter @nb@ v tagu @balise@ musí byť typu "text"'
+);
+
+?>
diff --git a/www/plugins/champs_extras3/lang/paquet-cextras.xml b/www/plugins/champs_extras3/lang/paquet-cextras.xml
new file mode 100644 (file)
index 0000000..be92151
--- /dev/null
@@ -0,0 +1,27 @@
+<traduction module="paquet-cextras" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/champs_extras/core/trunk/lang/" reference="fr">
+       <langue code="de" url="http://trad.spip.net/tradlang_module/paquet-cextras?lang_cible=de" total="4" traduits="3" relire="0" modifs="0" nouveaux="1" pourcent="75.00">
+               <traducteur nom="kent1" lien="http://trad.spip.net/auteur/kent1" />
+       </langue>
+       <langue code="en" url="http://trad.spip.net/tradlang_module/paquet-cextras?lang_cible=en" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="kent1" lien="http://trad.spip.net/auteur/kent1" />
+       </langue>
+       <langue code="es" url="http://trad.spip.net/tradlang_module/paquet-cextras?lang_cible=es" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="kent1" lien="http://trad.spip.net/auteur/kent1" />
+               <traducteur nom="Raquel S. Bujaldón" lien="http://trad.spip.net/auteur/raquel-s-bujaldon" />
+       </langue>
+       <langue code="fr" url="http://trad.spip.net/tradlang_module/paquet-cextras?lang_cible=fr" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       </langue>
+       <langue code="it" url="http://trad.spip.net/tradlang_module/paquet-cextras?lang_cible=it" total="4" traduits="3" relire="0" modifs="0" nouveaux="1" pourcent="75.00">
+               <traducteur nom="kent1" lien="http://trad.spip.net/auteur/kent1" />
+       </langue>
+       <langue code="nl" url="http://trad.spip.net/tradlang_module/paquet-cextras?lang_cible=nl" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
+       </langue>
+       <langue code="ru" url="http://trad.spip.net/tradlang_module/paquet-cextras?lang_cible=ru" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Serge Markitanenko" lien="http://trad.spip.net/auteur/serge-markitanenko" />
+       </langue>
+       <langue code="sk" url="http://trad.spip.net/tradlang_module/paquet-cextras?lang_cible=sk" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="jaro" lien="http://trad.spip.net/auteur/jaro" />
+               <traducteur nom="kent1" lien="http://trad.spip.net/auteur/kent1" />
+       </langue>
+</traduction>
diff --git a/www/plugins/champs_extras3/lang/paquet-cextras_de.php b/www/plugins/champs_extras3/lang/paquet-cextras_de.php
new file mode 100644 (file)
index 0000000..fb1b678
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-cextras?lang_cible=de
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'cextras_nom' => 'Zusatzfelder',
+       'cextras_slogan' => 'Zusätzliche Felder für die Standardobjekte von SPIP anlegen',
+       'cextras_titre' => 'Zusatzfelder'
+);
+
+?>
diff --git a/www/plugins/champs_extras3/lang/paquet-cextras_en.php b/www/plugins/champs_extras3/lang/paquet-cextras_en.php
new file mode 100644 (file)
index 0000000..ac80bc0
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-cextras?lang_cible=en
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'cextras_description' => 'Provides a simple API to create new fields on the editorial objects.
+It is the base for other plugins including "Extras Fields Interface" which provides
+a graphical interface to manage these new fields.',
+       'cextras_nom' => 'Extra fields',
+       'cextras_slogan' => 'Create new edit fields for SPIP objects',
+       'cextras_titre' => 'Extra fields'
+);
+
+?>
diff --git a/www/plugins/champs_extras3/lang/paquet-cextras_es.php b/www/plugins/champs_extras3/lang/paquet-cextras_es.php
new file mode 100644 (file)
index 0000000..27c77bf
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-cextras?lang_cible=es
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'cextras_description' => 'Ofrece una API simple permitiendo crear nuevos campos en los objetos editoriales. 
+Es pues la base para otros plugins, especialmente para «Campos Extras Interfaz», que da una interfaz gráfica de gestión de estos nuevos campos.',
+       'cextras_nom' => 'Campos Extras',
+       'cextras_slogan' => 'API de gestión de nuevos campos en los objetos editoriales',
+       'cextras_titre' => 'Campos Extras'
+);
+
+?>
diff --git a/www/plugins/champs_extras3/lang/paquet-cextras_fr.php b/www/plugins/champs_extras3/lang/paquet-cextras_fr.php
new file mode 100644 (file)
index 0000000..cbc169d
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// Fichier source, a modifier dans svn://zone.spip.org/spip-zone/_plugins_/champs_extras/core/trunk/lang/
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'cextras_description' => 'Offre une API simple permettant de créer de nouveaux champs dans les objets éditoriaux.
+                                       Il est donc le socle pour d’autres plugins notamment pour « Champs Extras Interface » qui donne
+                                       une interface graphique de gestion de ces nouveaux champs.',
+       'cextras_nom' => 'Champs Extras',
+       'cextras_slogan' => 'API de gestion de nouveaux champs dans les objets éditoriaux.',
+       'cextras_titre' => 'Champs Extras'
+);
+
+?>
diff --git a/www/plugins/champs_extras3/lang/paquet-cextras_it.php b/www/plugins/champs_extras3/lang/paquet-cextras_it.php
new file mode 100644 (file)
index 0000000..730a7f9
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-cextras?lang_cible=it
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'cextras_nom' => 'Campi Extra',
+       'cextras_slogan' => 'Crea nuovi campi per gli oggetti editoriali di SPIP',
+       'cextras_titre' => 'Campi Extra'
+);
+
+?>
diff --git a/www/plugins/champs_extras3/lang/paquet-cextras_nl.php b/www/plugins/champs_extras3/lang/paquet-cextras_nl.php
new file mode 100644 (file)
index 0000000..1af5a54
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-cextras?lang_cible=nl
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'cextras_description' => 'Biedt een API voor het beheer van nieuw velden in redactionele objecten.
+                                       Het is het fundament voor andere plugins zoals « Champs Extras Interface » wat een
+                                       grafische interface biedt voor het beheer van deze nieuwe velden.',
+       'cextras_nom' => 'Extra Velden',
+       'cextras_slogan' => 'API voor het beheer van nieuw velden in redactionele objecten.',
+       'cextras_titre' => 'Extra Velden'
+);
+
+?>
diff --git a/www/plugins/champs_extras3/lang/paquet-cextras_ru.php b/www/plugins/champs_extras3/lang/paquet-cextras_ru.php
new file mode 100644 (file)
index 0000000..003bd4a
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-cextras?lang_cible=ru
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'cextras_description' => 'Добавляет API для создания новых полей к существующим объектам.
+
+Это базовый плагин для многих других, в том числе для "Интерфейс для Champs Extras". Он обеспечивает интерфейс для добавления новых полей в административной панели сайта.',
+       'cextras_nom' => 'Создание новых полей (Champs Extras)',
+       'cextras_slogan' => 'API для создания новых полей у существующих объектов (статей, разделов и т. д.).',
+       'cextras_titre' => 'Создание новых полей (Champs Extras)'
+);
+
+?>
diff --git a/www/plugins/champs_extras3/lang/paquet-cextras_sk.php b/www/plugins/champs_extras3/lang/paquet-cextras_sk.php
new file mode 100644 (file)
index 0000000..1118043
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-cextras?lang_cible=sk
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // C
+       'cextras_description' => 'Ponúka jednoduchú aplikáciu, ktorá umožňuje vytvárať nové polia v redakčných objektoch.
+                                       Je základom pre iné zásuvné moduly, vrátane  "Rozhrania doplnkových polí", ktoré poskytuje
+                                       grafické rozhranie na riadenie týchto nových polí.',
+       'cextras_nom' => 'Doplnkové polia',
+       'cextras_slogan' => 'Aplikácia na riadenie nových polí v redakčných objektoch',
+       'cextras_titre' => 'Doplnkové polia'
+);
+
+?>
diff --git a/www/plugins/champs_extras3/paquet.xml b/www/plugins/champs_extras3/paquet.xml
new file mode 100644 (file)
index 0000000..cbecd4b
--- /dev/null
@@ -0,0 +1,29 @@
+<paquet\r
+       prefix="cextras"\r
+       categorie="outil"\r
+       version="3.4.1"\r
+       etat="stable"\r
+       compatibilite="[3.0.0;3.1.*]"\r
+       logo="images/cextras-64.png"\r
+       documentation="http://contrib.spip.net/?article4068"\r
+>\r
+\r
+       <nom>Champs Extras</nom>\r
+\r
+       <auteur>Matthieu Marcillaud</auteur>\r
+       <auteur>Fil</auteur>\r
+       <licence>GNU/GPL</licence>\r
+\r
+       <pipeline nom="declarer_champs_extras" action="" />\r
+\r
+       <pipeline nom="autoriser" inclure="inc/cextras_autoriser.php" />\r
+       <pipeline nom="editer_contenu_objet" inclure="cextras_pipelines.php" />\r
+       <pipeline nom="afficher_contenu_objet" inclure="cextras_pipelines.php" />\r
+       <pipeline nom="pre_edition" inclure="cextras_pipelines.php" />\r
+       <pipeline nom="formulaire_verifier" inclure="cextras_pipelines.php" />\r
+       <pipeline nom="revisions_chercher_label" inclure="cextras_pipelines.php" />\r
+\r
+       <necessite nom="saisies" compatibilite="[2.0.3;]" />\r
+       <utilise nom="verifier" compatibilite="[0.1.12;]" />\r
+\r
+</paquet>\r
diff --git a/www/plugins/champs_extras3/svn.revision b/www/plugins/champs_extras3/svn.revision
new file mode 100644 (file)
index 0000000..ae846fb
--- /dev/null
@@ -0,0 +1,10 @@
+<svn_revision>
+<text_version>
+Origine: file:///home/svn/repository/spip-zone/_plugins_/champs_extras/core/trunk
+Revision: 86876
+Dernier commit: 2014-12-28 19:00:08 +0100 
+</text_version>
+<origine>file:///home/svn/repository/spip-zone/_plugins_/champs_extras/core/trunk</origine>
+<revision>86876</revision>
+<commit>2014-12-28 19:00:08 +0100 </commit>
+</svn_revision>
\ No newline at end of file
diff --git a/www/plugins/clavettes b/www/plugins/clavettes
new file mode 160000 (submodule)
index 0000000..a498c70
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit a498c704046d4cc39dab1284dc27eb0f84fef891
diff --git a/www/plugins/couleur_rubrique/formulaires/configurer_pb_couleur_rubrique.html b/www/plugins/couleur_rubrique/formulaires/configurer_pb_couleur_rubrique.html
new file mode 100644 (file)
index 0000000..24ac2c2
--- /dev/null
@@ -0,0 +1,33 @@
+
+
+<div class="formulaire_spip formulaire_config formulaire_#FORM">
+       <div class="hd titrem"><h3><:pb_couleur_rubrique:afficher_le_formulaire_de_couleur:></h3><div class="nettoyeur"></div><!--/hd--></div>
+       
+       [<p class="reponse_formulaire reponse_formulaire_ok">(#ENV*{message_ok})</p>]
+       [<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV*{message_erreur})</p>]
+       
+       <form method="post" action="#ENV{action}">
+               <div>
+                       #ACTION_FORMULAIRE{#ENV{action}}
+                       <ul>
+                               <li class="editer pleine_largeur configurer_pb_couleur_rubrique">
+                                       <div class="choix">
+                                               <input type="checkbox" id="afficher" name='afficher' value='non'[(#CONFIG{pb_couleur_rubrique/afficher}|=={non}|oui) checked="checked"] />
+                                               <label for="afficher"><:pb_couleur_rubrique:ne_changez_plus_de_couleur:></label>
+                                               <p class="explication"><:pb_couleur_rubrique:geler_le_formulaire_de_couleur_explication:></p>
+                                       </div>
+                               </li>
+                               <li class="editer pleine_largeur configurer_pb_couleur_rubrique">
+                                       <div class="choix">
+                                               <input type="checkbox" id="secteurs" name='secteurs' value='oui'[(#CONFIG{pb_couleur_rubrique/secteurs}|=={oui}|oui) checked="checked"] />
+                                               <label for="secteurs"><:pb_couleur_rubrique:que_dans_les_secteurs:></label>
+                                               <p class="explication"><:pb_couleur_rubrique:que_dans_secteurs_explication:></p>
+                                       </div>
+                               </li>
+                       </ul>
+                       <div class="boutons">
+                               <input type="submit" class="submit" value="<:bouton_enregistrer:>" />
+                       </div>
+               </div>
+       </form>
+</div>
\ No newline at end of file
diff --git a/www/plugins/couleur_rubrique/formulaires/couleur_rubrique.html b/www/plugins/couleur_rubrique/formulaires/couleur_rubrique.html
new file mode 100644 (file)
index 0000000..e9d10b2
--- /dev/null
@@ -0,0 +1,34 @@
+<div class="formulaire_spip formulaire_config formulaire_#FORM">
+       <h3 class="titrem">[(#CHEMIN{img_pack/pb_couleur_rubrique-24.png}|balise_img{'',cadre-icone})][(#ENV{_site}|?{Couleur du site,Couleur de la rubrique})]</h3>
+       [<p class="reponse_formulaire reponse_formulaire_ok none">(#ENV*{message_ok})</p>]
+       [<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV*{message_erreur})</p>]
+       [(#ENV{editable}|oui)
+       <form method="post" action="#ENV{action}">
+               <div>
+                       #ACTION_FORMULAIRE{#ENV{action}}
+                       <ul>
+                               <li class="editer_couleur_rubrique_palette[(#ENV**{erreurs}|table_valeur{pb_couleur_rubrique}|oui)erreur]">
+                                       <label for="pb_couleur_rubrique"><:pb_couleur_rubrique:choisir_une_nouvelle_couleur:></label>[
+                                       <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{pb_couleur_rubrique})</span>
+                                       ]<div class="couleur" style="width:70px;margin:0 0 2px 2px;text-align:center;text-transform:uppercase;background:[#(#ENV{pb_couleur_rubrique,''}|ltrim{#})];color:[#(#ENV{pb_couleur_rubrique,''}|couleur_inverser|couleur_extreme{0.45})];border:1px solid;float: right"
+                                       >#ENV{pb_couleur_rubrique,''}</div><input class='palette' type='color' id='pb_couleur_rubrique' name='pb_couleur_rubrique' value="#ENV{pb_couleur_rubrique,''}" style="width:70px;text-transform:uppercase;" />
+                               </li>
+                       </ul>
+                       <div class="boutons">
+                               <input type="submit" class="over" value="<:bouton_enregistrer|attribut_html:>" />
+                               <small><input type="submit" class="cancel left" name="supprimer" value="<:pb_couleur_rubrique:bouton_supprimer|attribut_html:>" /></small>
+                               <input type="submit" class="submit" value="<:bouton_enregistrer|attribut_html:>" />
+                       </div>
+               </div>
+       </form>
+       ]
+       [(#ENV{editable}|non)
+               [(#ENV{pb_couleur_rubrique,''}|non)<p><:pb_couleur_rubrique:aucune_couleur:></p>]
+               [(#ENV{pb_couleur_rubrique,''}|oui)
+               <p class="clearfix">
+                       <:pb_couleur_rubrique:la_couleur_actuelle:><br />
+                       <span class="couleur codehex" style="text-transform:uppercase;display:block;padding:2px;text-align:center;background:[#(#ENV{pb_couleur_rubrique,''}|ltrim{#})];color:[#(#ENV{pb_couleur_rubrique,''}|couleur_inverser|couleur_extreme{0.45})];border:1px solid;">[#(#ENV{pb_couleur_rubrique,''}|ltrim{#})]</span>
+               </p>
+               ]
+       ]
+</div>
\ No newline at end of file
diff --git a/www/plugins/couleur_rubrique/formulaires/couleur_rubrique.php b/www/plugins/couleur_rubrique/formulaires/couleur_rubrique.php
new file mode 100644 (file)
index 0000000..c98773d
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+function formulaires_couleur_rubrique_charger_dist($id_rubrique){
+       $editable = true;
+       if ($GLOBALS['visiteur_session']['statut']!=='0minirezo')
+               $editable = false;
+       else {
+               include_spip("inc/config");
+               if (lire_config("pb_couleur_rubrique/afficher")=="non")
+                       $editable = false;
+       }
+
+       // chargement des valeurs du formulaire
+       $valeurs = array(
+               'pb_couleur_rubrique' => "#".couleur_rubrique($id_rubrique),
+               'supprimer' => '',
+               '_site' => $id_rubrique?'':' ',
+               "editable" => $editable,
+       );
+       // autorisation : #ENV{editable} est evite car on veut toujours voir le formulaire meme apres validation
+       return $valeurs;
+}
+
+function formulaires_couleur_rubrique_verifier_dist($id_rubrique){
+       // rien de particulier a verifier
+       $erreurs = array();
+       if (!_request('pb_couleur_rubrique'))
+               $erreurs['pb_couleur_rubrique'] = _T('info_obligatoire');
+       return $erreurs;
+}
+
+function formulaires_couleur_rubrique_traiter_dist($id_rubrique){
+       if (_request('supprimer')){
+               effacer_meta("pb_couleur_rubrique$id_rubrique");
+       }
+       else {
+               // preparation des variables
+               $cr = _request('pb_couleur_rubrique');
+               $couleur = ltrim(trim($cr),"#");
+               // enregistrer/supprimer les valeurs
+               ecrire_meta("pb_couleur_rubrique$id_rubrique", $couleur);
+       }
+       set_request('pb_couleur_rubrique'); // repasser toujours par la lecture en base
+
+       return array("message_ok" => _T('pb_couleur_rubrique:info_message_ok'),"editable"=>true);
+}
+
+?>
\ No newline at end of file
diff --git a/www/plugins/couleur_rubrique/img_pack/pb_couleur_rubrique-24.png b/www/plugins/couleur_rubrique/img_pack/pb_couleur_rubrique-24.png
new file mode 100644 (file)
index 0000000..0e675bf
Binary files /dev/null and b/www/plugins/couleur_rubrique/img_pack/pb_couleur_rubrique-24.png differ
diff --git a/www/plugins/couleur_rubrique/img_pack/pb_couleur_rubrique.png b/www/plugins/couleur_rubrique/img_pack/pb_couleur_rubrique.png
new file mode 100644 (file)
index 0000000..a4f1f0b
Binary files /dev/null and b/www/plugins/couleur_rubrique/img_pack/pb_couleur_rubrique.png differ
diff --git a/www/plugins/couleur_rubrique/inclure/couleur_rubrique.html b/www/plugins/couleur_rubrique/inclure/couleur_rubrique.html
new file mode 100644 (file)
index 0000000..3d9d6c0
--- /dev/null
@@ -0,0 +1,4 @@
+<div class="ajax">
+[(#FORMULAIRE_COULEUR_RUBRIQUE{#ENV{id_rubrique}})]
+</div>
+
diff --git a/www/plugins/couleur_rubrique/javascript/pb_couleur_rubrique.js b/www/plugins/couleur_rubrique/javascript/pb_couleur_rubrique.js
new file mode 100644 (file)
index 0000000..9efa901
--- /dev/null
@@ -0,0 +1,7 @@
+$(document).ready(function(){
+       $('a.chargez_couleur').click(function(){
+               var codehexa = $('strong.codehex').html();
+               $('input.palette').attr('value',codehexa).focus();
+               return false;
+       });
+});
diff --git a/www/plugins/couleur_rubrique/lang/paquet-pb_couleur_rubrique_fr.php b/www/plugins/couleur_rubrique/lang/paquet-pb_couleur_rubrique_fr.php
new file mode 100644 (file)
index 0000000..128646f
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+// Ceci est un fichier langue de SPIP -- This is a SPIP language file
+///  Fichier produit par PlugOnet
+// Module: paquet-pb_couleur_rubrique
+// Langue: fr
+// Items: 2
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+// P
+       'pb_couleur_rubrique_description' => 'Ce plugin permet de choisir une couleur pour chaque rubrique du site. Une fois activ&#233;, il ne demande aucune configuration suppl&#233;mentaire. Il ajoute simplement un pav&#233; dans les pages des rubriques permettant de choisir une couleur. L\'option n\'est accessible qu\'aux administrateurs.
+       
+               Pour afficher la couleur d\'une rubrique dans un squelette, il suffit d\'utiliser le code : <code>[#(#ID_RUBRIQUE|couleur_rubrique)]</code>.
+
+               Pour afficher la couleur d\'un secteur dans un squelette, il suffit d\'utiliser le code : <code>[#(#ID_RUBRIQUE|couleur_secteur)]</code>.
+
+               Il faut installer en plus le plugin Palette pour s&#233;lectionner visuellement la couleur sur une roue chromatique, sinon il faut utiliser le code hexad&#233;cimal correspondant &#224; la couleur, du type : #C5E41C
+               
+               Une page de configuration permet d\'interdire le changement de couleur, ou de ne permettre les couleurs que sur les secteurs.',
+               
+       'pb_couleur_rubrique_slogan' => 'Une couleur pour chaque rubrique',
+);
+?>
\ No newline at end of file
diff --git a/www/plugins/couleur_rubrique/lang/pb_couleur_rubrique_fr.php b/www/plugins/couleur_rubrique/lang/pb_couleur_rubrique_fr.php
new file mode 100644 (file)
index 0000000..c63bb9d
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+
+// Ceci est un fichier langue de SPIP -- This is a SPIP language file
+// plugin Couleur de rubrique v2
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+       
+       //A
+       'afficher_le_formulaire_de_couleur' => 'Afficher le formulaire de couleur sur chaque page de rubrique ?',
+       'aucune_couleur' => 'Aucune couleur n\'a été choisie.',
+
+       // B
+       'bouton_supprimer' => 'Supprimer',
+       
+       // C
+       'choisir_une_nouvelle_couleur' => 'Choisir une nouvelle couleur',
+       'configurer_couleur_de_rubrique' => 'Configurer le choix de couleur de rubrique',
+       
+       // G
+       'geler_le_formulaire_de_couleur_explication' => 'En choisissant de ne plus changez de couleur, le formulaire de choix de couleur ne s\'affichera pas. Il faudra revenir d&#233;sactiver ce param&#232;tre pour r&#233;activer le formulaire sur les pages de rubriques.',
+       
+       // I
+       'info_obligatoire' => 'Choisissez une option avant de valider.',
+       'info_message_ok' => 'Couleur modifiée',
+       
+       // L
+       'la_couleur_actuelle' => 'La couleur actuelle',
+       
+       // N
+       'ne_changez_plus_de_couleur' => 'Ne changez plus de couleur',
+       
+       // Q
+       'que_dans_les_secteurs' => 'Ne choisir une couleur que dans les secteurs.',
+       'que_dans_secteurs_explication' => 'Alors le formulaire de choix de couleur n\'apparaîtra que dans les rubriques à la racine du site. La couleur générale du site (?exec=rubriques) n\'est jamais désactivée.',
+       
+       // S
+       'supprimer_la_couleur' => 'Supprimer la couleur'
+);
+?>
\ No newline at end of file
diff --git a/www/plugins/couleur_rubrique/paquet.xml b/www/plugins/couleur_rubrique/paquet.xml
new file mode 100644 (file)
index 0000000..a5179a7
--- /dev/null
@@ -0,0 +1,24 @@
+<paquet
+prefix="pb_couleur_rubrique"
+categorie="maintenance"
+version="2.3.6"
+etat="stable"
+compatibilite="[3.0.0;3.1.*]"
+logo="img_pack/pb_couleur_rubrique.png"
+documentation="http://contrib.spip.net/Couleur-de-Rubrique"
+       >       
+
+       <nom>Couleur de rubrique</nom>
+       <!-- Une couleur pour chaque rubrique -->
+       
+       <auteur mail="arno@rezo.net">ARNO*</auteur>
+       <auteur lien="http://www.erational.org">sur la base du travail d'erational</auteur>
+       
+       <licence lien="http://www.gnu.org/licenses/gpl-3.0.html">GPL 3</licence>
+       
+       <utilise nom="palette" compatibilite="[3.0.0;[" />
+       
+       <pipeline nom="affiche_droite" inclure="pb_couleur_rubrique_pipelines.php" />
+       <pipeline nom="header_prive" inclure="pb_couleur_rubrique_pipelines.php" />
+       
+</paquet>
\ No newline at end of file
diff --git a/www/plugins/couleur_rubrique/pb_couleur_rubrique_options.php b/www/plugins/couleur_rubrique/pb_couleur_rubrique_options.php
new file mode 100644 (file)
index 0000000..a41730f
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+//
+// functions
+//
+function pb_couleur_rubrique($id_rubrique) {
+       $pb_couleur_rubrique = lire_meta("pb_couleur_rubrique$id_rubrique");
+       return $pb_couleur_rubrique;
+}
+
+function couleur_rubrique($id_rubrique) {
+       return pb_couleur_rubrique($id_rubrique);
+}
+
+function couleur_site() {
+       $pb_couleur_site = lire_meta("pb_couleur_rubrique0");
+       return $pb_couleur_site;
+}
+
+function couleur_secteur($id_rubrique){
+       $id_secteur = sql_getfetsel('id_secteur', 'spip_rubriques', 'id_rubrique=' . intval($id_rubrique));
+       $couleur_secteur = lire_meta("pb_couleur_rubrique$id_secteur");
+       return $couleur_secteur;
+}
+
+?>
\ No newline at end of file
diff --git a/www/plugins/couleur_rubrique/pb_couleur_rubrique_pipelines.php b/www/plugins/couleur_rubrique/pb_couleur_rubrique_pipelines.php
new file mode 100644 (file)
index 0000000..0c5c408
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+/**
+ * Inserer le script js dans l'espace prive
+ * @param $flux
+ * @return string
+ */
+function pb_couleur_rubrique_header_prive($flux){
+       $flux .= '<script type="text/javascript" src="' . _DIR_PLUGIN_PB_COULEUR_RUBRIQUE . 'javascript/pb_couleur_rubrique.js"></script>';
+       return $flux;
+}
+
+
+function pb_couleur_rubrique_affiche_droite($flux){
+
+       $exec = $flux["args"]["exec"];
+       if ($exec=="rubrique"){
+               $id_rubrique = $flux["args"]["id_rubrique"];
+               // si la config est sur "oui, que les secteurs"
+               if (lire_config('pb_couleur_rubrique/secteurs')=='oui'){
+                       // calcul du secteur
+                       $id_secteur = sql_getfetsel('id_secteur', 'spip_rubriques', 'id_rubrique=' . intval($id_rubrique));
+                       // on affiche que dans le secteur
+                       if ($id_secteur==$id_rubrique){
+                               $contexte = array('id_rubrique' => $id_rubrique);
+                               $flux["data"] .= recuperer_fond("inclure/couleur_rubrique", $contexte);
+                       }
+               } else {
+                       $contexte = array('id_rubrique' => $id_rubrique);
+                       $flux["data"] .= recuperer_fond("inclure/couleur_rubrique", $contexte);
+               }
+       }
+       // quoi qu'il en soit, la couleur du site sera toujours
+       if ($exec=="rubriques"){
+               $contexte = array('id_rubrique' => '0');
+               $flux["data"] .= recuperer_fond("inclure/couleur_rubrique", $contexte);
+       }
+       return $flux;
+}
+
+
+?>
\ No newline at end of file
diff --git a/www/plugins/couleur_rubrique/prive/squelettes/contenu/configurer_pb_couleur_rubrique.html b/www/plugins/couleur_rubrique/prive/squelettes/contenu/configurer_pb_couleur_rubrique.html
new file mode 100644 (file)
index 0000000..3de901d
--- /dev/null
@@ -0,0 +1,6 @@
+[(#AUTORISER{configurer,pb_couleur_rubrique}|oui)
+       <h1><:pb_couleur_rubrique:configurer_couleur_de_rubrique:></h1>
+       <div class="ajax">
+               #FORMULAIRE_CONFIGURER_PB_COULEUR_RUBRIQUE
+       </div>
+]
\ No newline at end of file
diff --git a/www/plugins/couleur_rubrique/svn.revision b/www/plugins/couleur_rubrique/svn.revision
new file mode 100644 (file)
index 0000000..de83c29
--- /dev/null
@@ -0,0 +1,10 @@
+<svn_revision>
+<text_version>
+Origine: file:///home/svn/repository/spip-zone/_plugins_/couleur_rubrique/trunk
+Revision: 86141
+Dernier commit: 2014-11-18 00:15:32 +0100 
+</text_version>
+<origine>file:///home/svn/repository/spip-zone/_plugins_/couleur_rubrique/trunk</origine>
+<revision>86141</revision>
+<commit>2014-11-18 00:15:32 +0100 </commit>
+</svn_revision>
\ No newline at end of file
diff --git a/www/plugins/gis/action/editer_gis.php b/www/plugins/gis/action/editer_gis.php
new file mode 100644 (file)
index 0000000..b1495c4
--- /dev/null
@@ -0,0 +1,229 @@
+<?php
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+include_spip('inc/autoriser');
+
+function action_editer_gis_dist($arg=null) {
+       if (is_null($arg)){
+               $securiser_action = charger_fonction('securiser_action', 'inc');
+               $arg = $securiser_action();
+       }
+       
+       // si id_gis n'est pas un nombre, c'est une creation
+       if (!$id_gis = intval($arg)) {
+               if (!autoriser('creer','gis') or !$id_gis = gis_inserer())
+                       return array(false,_L('echec'));
+       }
+       $err = gis_modifier($id_gis);
+       return array($id_gis,$err);
+}
+
+/**
+ * Fonction d'insertion d'un gis vide
+ * 
+ * @return int/false $id_gis : l'identifiant numérique du point ou false en cas de non création
+ */
+function gis_inserer() {
+       $champs = array();
+       
+       // Envoyer aux plugins
+       $champs = pipeline('pre_insertion', array(
+               'args' => array(
+                       'table' => 'spip_gis',
+               ),
+               'data' => $champs
+       ));
+       
+       $id_gis = sql_insertq("spip_gis", $champs);
+       
+       pipeline('post_insertion',
+               array(
+                       'args' => array(
+                               'table' => 'spip_gis',
+                               'id_objet' => $id_gis
+                       ),
+                       'data' => $champs
+               )
+       );
+       return $id_gis;
+}
+
+/**
+ *  Enregistrer certaines modifications d'un gis
+ * 
+ * @param int $id_gis : l'identifiant numérique du point
+ * @param array $c : un array des valeurs à mettre en base (par défaut false, on récupère les valeurs passées en dans le POST)
+ */
+/**
+ * Appelle toutes les fonctions de modification d'un point gis
+ * $err est de la forme chaine de langue ou vide si pas d'erreur
+ * http://doc.spip.org/@articles_set
+ *
+ * @param  $id_gis
+ * @param null $set
+ * @return string
+ */
+function gis_modifier($id_gis, $set=null) {
+       include_spip('inc/modifier');
+       include_spip('inc/filtres');
+       $c = collecter_requests(
+               // white list
+               objet_info('gis','champs_editables'),
+               // black list
+               array('id_objet','objet'),
+               // donnees eventuellement fournies
+               $set
+       );
+
+       if(isset($c['lon'])){
+               if($c['lon'] > 180){
+                       while($c['lon'] > 180){
+                               $c['lon'] = $c['lon'] - 360;
+                       }
+               }else if($c['lon'] <= -180){
+                       while($c['lon'] <= -180){
+                               $c['lon'] = $c['lon'] + 360;
+                       }
+               }
+       }
+       if(isset($c['lat'])){
+               if($c['lat'] > 90){
+                       while($c['lat'] > 90){
+                               $c['lat'] = $c['lat'] - 180;
+                       }
+               }else if($c['lat'] <= -90){
+                       while($c['lat'] <= -90){
+                               $c['lat'] = $c['lon'] + 180;
+                       }
+               }
+       }
+       if ($err = objet_modifier_champs('gis', $id_gis,
+               array(
+                       //'nonvide' => array('nom' => _T('info_sans_titre')),
+                       'invalideur' => "id='gis/$id_gis'",
+               ),
+               $c))
+               return $err;
+
+       // lier a un parent ?
+       $c = collecter_requests(array('id_objet', 'objet'),array(),$set);
+       if (isset($c['id_objet']) AND intval($c['id_objet']) AND isset($c['objet']) AND $c['objet']) {
+               lier_gis($id_gis, $c['objet'], $c['id_objet']);
+       }
+
+       return $err;
+}
+
+
+/**
+ * Associer un point géolocalisé a des objets listes sous forme
+ * array($objet=>$id_objets,...)
+ * $id_objets peut lui meme etre un scalaire ou un tableau pour une liste d'objets du meme type
+ *
+ * on peut passer optionnellement une qualification du (des) lien(s) qui sera
+ * alors appliquee dans la foulee.
+ * En cas de lot de liens, c'est la meme qualification qui est appliquee a tous
+ *
+ * @param int $id_gis
+ * @param array $objets
+ * @param array $qualif
+ * @return string
+ */
+function gis_associer($id_gis,$objets, $qualif = null){
+       include_spip('action/editer_liens');
+       $res = objet_associer(array('gis'=>$id_gis), $objets, $qualif);
+       include_spip('inc/invalideur');
+       suivre_invalideur("id='gis/$id_gis'");
+       return $res;
+}
+
+/**
+ * Dissocier un point géolocalisé des objets listes sous forme
+ * array($objet=>$id_objets,...)
+ * $id_objets peut lui meme etre un scalaire ou un tableau pour une liste d'objets du meme type
+ *
+ * un * pour $id_auteur,$objet,$id_objet permet de traiter par lot
+ *
+ * @param int $id_gis
+ * @param array $objets
+ * @return string
+ */
+function gis_dissocier($id_gis,$objets){
+       include_spip('action/editer_liens');
+       $res = objet_dissocier(array('gis'=>$id_gis), $objets);
+       include_spip('inc/invalideur');
+       suivre_invalideur("id='gis/$id_gis'");
+       return $res;
+}
+
+
+
+/**
+ * Supprimer définitivement un point géolocalisé
+ * 
+ * @param int $id_gis identifiant numérique du point
+ * @return int|false 0 si réussite, false dans le cas ou le point n'existe pas
+ */
+function gis_supprimer($id_gis){
+       $valide = sql_getfetsel('id_gis','spip_gis','id_gis='.intval($id_gis));
+       if($valide && autoriser('supprimer','gis',$valide)){
+               sql_delete("spip_gis_liens", "id_gis=".intval($id_gis));
+               sql_delete("spip_gis", "id_gis=".intval($id_gis));
+               $id_gis = 0;
+               include_spip('inc/invalideur');
+               suivre_invalideur("id='id_gis/$id_gis'");
+               return $id_gis;
+       }
+       return false;
+}
+
+
+/**
+ * Délier un point géolocalisé d'un objet SPIP
+ *
+ * @param int $id_gis identifiant numérique du point
+ * @param string $objet Le type de l'objet à lier
+ * @param int $id_objet L'identifiant numérique de l'objet lié
+ *
+ * @return bool : true si la suppression de la liaison s'est bien passée, false à l'inverse
+ */
+function delier_gis($id_gis, $objet, $id_objet){
+       //$objet = objet_type($objet);
+       if ($id_objet AND $id_gis
+       AND preg_match('/^[a-z0-9_]+$/i', $objet) # securite
+       AND autoriser('delier','gis',$id_gis,$GLOBALS['visiteur_session'],array('objet' => $objet,'id_objet'=>$id_objet))
+       ) {
+               gis_dissocier($id_gis,array($objet=>$id_objet));
+               return true;
+       }
+       return false;
+}
+
+/**
+ * Lier un point géolocalisé à un objet SPIP
+ *
+ * @param int $id_gis identifiant numérique du point
+ * @param string $objet Le type de l'objet à lier
+ * @param int $id_objet L'identifiant numérique de l'objet lié
+ *
+ * @return bool : true si la liaison s'est bien passée, false à l'inverse
+ */
+function lier_gis($id_gis, $objet, $id_objet){
+       //$objet = objet_type($objet);
+       if ($id_objet AND $id_gis
+       AND preg_match('/^[a-z0-9_]+$/i', $objet) # securite
+       AND !sql_getfetsel("id_gis", "spip_gis_liens", "id_gis=$id_gis AND id_objet=$id_objet AND objet=".sql_quote($objet))
+       AND autoriser('lier','gis',$id_gis,$GLOBALS['visiteur_session'],array('objet' => $objet,'id_objet'=>$id_objet))
+       ) {
+               gis_associer($id_gis,array($objet=>$id_objet));
+               return true;
+       }
+       return false;
+}
+
+function insert_gis() {return gis_inserer();}
+function revisions_gis($id_gis, $c=false) {return gis_modifier($id_gis,$c);}
+function supprimer_gis($id_gis){return gis_supprimer($id_gis);}
+
+?>
diff --git a/www/plugins/gis/action/editer_lien_gis.php b/www/plugins/gis/action/editer_lien_gis.php
new file mode 100644 (file)
index 0000000..f5633d3
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+
+function action_editer_lien_gis_dist(){
+       $securiser_action = charger_fonction('securiser_action', 'inc');
+       $arg = $securiser_action();
+       list($action,$id_gis, $objet, $id_objet) = explode('/',$arg);
+       
+       include_spip('inc/autoriser');
+       if (intval($id_gis) AND autoriser('lier','gis',$id_gis,$GLOBALS['visiteur_session'],array('objet' => $objet,'id_objet'=>$id_objet))){
+               include_spip('action/editer_gis');
+               if ($action == 'lier')
+                       lier_gis($id_gis, $objet, $id_objet);
+               elseif ($action == 'delier')
+                       delier_gis($id_gis, $objet, $id_objet);
+       }
+}
+
+?>
\ No newline at end of file
diff --git a/www/plugins/gis/action/gis_geocoder_rechercher.php b/www/plugins/gis/action/gis_geocoder_rechercher.php
new file mode 100644 (file)
index 0000000..ec300dc
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+include_spip("inc/distant");
+
+/*
+ * Proxy vers le service Nomatim d'OpenStreetMap.
+ *
+ * Cette fonction permet de transmettre une requete auprès du service
+ * de recherche d'adresse d'OpenStreetMap (Nomatim). Les arguments
+ * spécifiques à SPIP sont supprimés (exec=, action= et mode=), le
+ * reste est transmis tel quel à Nomatim.
+ */
+function action_gis_geocoder_rechercher_dist() {
+       include_spip("inc/modifier");
+
+       $mode = _request("mode");
+       if(!$mode || !in_array($mode, array("search", "reverse")))
+               return;
+
+       /* On filtre les arguments à renvoyer à Nomatim (liste blanche) */    
+       $arguments = collecter_requests(array("json_callback", "format", "q", "limit", "addressdetails", "accept-language", "lat", "lon"),array());
+
+       if(!empty($arguments)) {
+               header('Content-Type: application/json; charset=UTF-8');
+               echo recuperer_page("http://nominatim.openstreetmap.org/{$mode}?" . http_build_query($arguments));
+       }
+}
diff --git a/www/plugins/gis/action/kml_infos.php b/www/plugins/gis/action/kml_infos.php
new file mode 100644 (file)
index 0000000..eda2049
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+include_spip('inc/charsets');  # pour le nom de fichier
+include_spip('inc/actions');
+
+function action_kml_infos_dist(){
+       global $redirect;
+
+       $securiser_action = charger_fonction('securiser_action', 'inc');
+       $arg = $securiser_action();
+
+       if (!preg_match(",^(-?)(\d+)\W(\w+)\W?(\d*)\W?(\d*)$,", $arg, $r)){
+               spip_log("action_kml_infos_dist incompris: " . $arg);
+               $redirect = urldecode(_request('redirect'));
+               return;
+       }
+       else{
+               action_kml_infos_post($r);
+       }
+}
+
+function action_kml_infos_post($r){
+       list(, $sign, $id_objet, $objet, $id_document, $suite) = $r;
+
+       if(intval($id_document)){
+               $recuperer_info = charger_fonction('kml_infos','inc');
+               $infos = $recuperer_info($id_document);
+               if($infos){
+                       include_spip('inc/documents');
+                       $fichier = sql_getfetsel('fichier','spip_documents','id_document='.intval($id_document));
+                       if(is_numeric($latitude = $infos['latitude']) && is_numeric($longitude = $infos['longitude'])){
+                               $c = array(
+                                       'titre' => $infos['titre'] ? $infos['titre'] : basename($fichier),
+                                       'lat'=> $latitude,
+                                       'lon' => $longitude,
+                                       'zoom' => $config['zoom'] ? $config['zoom'] :'4'
+                               );
+               
+                               include_spip('action/editer_gis');
+       
+                               if($id_gis = sql_getfetsel("G.id_gis","spip_gis AS G LEFT  JOIN spip_gis_liens AS T ON T.id_gis=G.id_gis ","T.id_objet=" . intval($id_document) . " AND T.objet='document'")){
+                                       // Des coordonnées sont déjà définies pour ce document => on les update
+                                       revisions_gis($id_gis,$c);
+                                       spip_log("GIS EXIFS : Update des coordonnées depuis EXIFS pour le document $id_document => id_gis = $id_gis","gis");
+                               }
+                               else{
+                                       // Aucune coordonnée n'est définie pour ce document  => on les crées
+                                       $id_gis = insert_gis();
+                                       revisions_gis($id_gis,$c);
+                                       lier_gis($id_gis, 'document', $id_document);
+                                       spip_log("GIS EXIFS : Création des coordonnées depuis EXIFS pour le document $id_document => id_gis = $id_gis","gis");
+                               }
+                       }
+                       unset($infos['longitude']);
+                       unset($infos['latitude']);
+                       if(count($infos) > 0){
+                               include_spip('action/editer_document');
+                               document_modifier($id_document, $infos);
+                       }
+               }
+       }
+       $redirect = urldecode(_request('redirect'));
+       return $redirect;
+}
+
+?>
\ No newline at end of file
diff --git a/www/plugins/gis/action/supprimer_gis.php b/www/plugins/gis/action/supprimer_gis.php
new file mode 100644 (file)
index 0000000..6c838f2
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+
+function action_supprimer_gis_dist(){
+       
+       $securiser_action = charger_fonction('securiser_action', 'inc');
+       $arg = $securiser_action();
+       
+       list($id_gis) = preg_split(',[^0-9],',$arg);
+       include_spip('inc/autoriser');
+       if (intval($id_gis) AND autoriser('supprimer','gis',$id_gis)){
+               include_spip("action/editer_gis");
+               supprimer_gis($id_gis);
+       }
+}
+
+?>
\ No newline at end of file
diff --git a/www/plugins/gis/base/gis.php b/www/plugins/gis/base/gis.php
new file mode 100644 (file)
index 0000000..345c14f
--- /dev/null
@@ -0,0 +1,135 @@
+<?php
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+function gis_declarer_tables_interfaces($interface){
+       $interface['table_des_tables']['gis'] = 'gis';
+       $interface['table_des_tables']['gis_liens'] = 'gis_liens';
+
+       // Traitements typo et raccourcis
+       $interface['table_des_traitements']['TITRE_GIS'][] = 'typo(extraire_multi(%s))';
+       $interface['table_des_traitements']['DESCRIPTIF_GIS'][] = _TRAITEMENT_RACCOURCIS;
+       $interface['table_des_traitements']['VILLE_GIS'][] = 'typo(extraire_multi(%s))';
+       $interface['table_des_traitements']['PAYS_GIS'][] = 'typo(extraire_multi(%s))';
+       $interface['table_des_traitements']['REGION_GIS'][] = 'typo(extraire_multi(%s))';
+       $interface['table_des_traitements']['DEPARTEMENT_GIS'][] = 'typo(extraire_multi(%s))';
+       $interface['table_des_traitements']['VILLE'][] = 'typo(extraire_multi(%s))';
+       $interface['table_des_traitements']['PAYS'][] = 'typo(extraire_multi(%s))';
+       $interface['table_des_traitements']['REGION'][] = 'typo(extraire_multi(%s))';
+       $interface['table_des_traitements']['DEPARTEMENT'][] = 'typo(extraire_multi(%s))';
+
+       return $interface;
+}
+
+function gis_declarer_tables_objets_sql($tables){
+       /* Declaration de la table de points gis */
+       $tables['spip_gis'] = array(
+               /* Declarations principales */
+               'table_objet' => 'gis',
+               'table_objet_surnoms' => array('gis'),
+               'type' => 'gis',
+               'type_surnoms' => array('gi'),
+
+               /* La table */
+               'field' => array(
+                       "id_gis" => "bigint(21) NOT NULL",
+                       "titre" => "text NOT NULL DEFAULT ''",
+                       "descriptif" => "text NOT NULL DEFAULT ''",
+                       "lat" => "double NULL NULL",
+                       "lon" => "double NULL NULL",
+                       "zoom" => "tinyint(4) NULL NULL",
+                       "adresse" => "text NOT NULL DEFAULT ''",
+                       "pays" => "text NOT NULL DEFAULT ''",
+                       "code_pays" => "varchar(255) NOT NULL DEFAULT ''",
+                       "region" => "text NOT NULL DEFAULT ''",
+                       "departement" => "text NOT NULL DEFAULT ''",
+                       "ville" => "text NOT NULL DEFAULT ''",
+                       "code_postal" => "varchar(255) NOT NULL DEFAULT ''"
+               ),
+               'key' => array(
+                       "PRIMARY KEY" => "id_gis",
+                       'KEY lat' => 'lat',
+                       'KEY lon' => 'lon',
+                       'KEY pays' => 'pays(500)',
+                       'KEY code_pays' => 'code_pays',
+                       'KEY region' => 'region(500)',
+                       'KEY departement' => 'departement(500)',
+                       'KEY ville' => 'ville(500)',
+                       'KEY code_postal' => 'code_postal',
+               ),
+               'join' => array(
+                               "id_gis"=>"id_gis"
+               ),
+               'principale' => 'oui',
+               'modeles' => array('carte_gis', 'carte_gis_preview'),
+
+               /* Le titre, la date et la gestion du statut */
+               'titre' => "titre, '' AS lang",
+
+               /* L'édition, l'affichage et la recherche */
+               'page' => false,
+               'url_voir' => 'gis',
+               'url_edit' => 'gis_edit',
+               'editable' => 'oui',
+               'champs_editables' => array('lat', 'lon', 'zoom', 'titre', 'descriptif', 'adresse', 'code_postal', 'ville', 'region', 'departement', 'pays', 'code_pays'),
+               'champs_versionnes' => array('lat', 'lon', 'zoom', 'titre', 'descriptif', 'adresse', 'code_postal', 'ville', 'region', 'departement', 'pays', 'code_pays'),
+               'icone_objet' => 'gis',
+               'rechercher_champs' => array(
+                       'titre' => 8,
+                       'descriptif' => 5,
+                       'pays' => 3,
+                       'region' => 3,
+                       'departement' => 3,
+                       'ville' => 3,
+                       'code_postal' => 3,
+               ),
+
+               /* Les textes standard */
+               'texte_ajouter' => 'gis:texte_ajouter_gis',
+               'texte_retour' => 'icone_retour',
+               'texte_modifier' => 'gis:texte_modifier_gis',
+               'texte_creer' => 'gis:texte_creer_gis',
+               'texte_creer_associer' => 'gis:texte_creer_associer_gis',
+               'texte_objet' => 'gis:gis_singulier',
+               'texte_objets' => 'gis:gis_pluriel',
+               'info_aucun_objet' => 'gis:info_aucun_gis',
+               'info_1_objet' => 'gis:info_1_gis',
+               'info_nb_objets' => 'gis:info_nb_gis',
+               'texte_logo_objet' => 'gis:libelle_logo_gis',
+       );
+
+       $tables[]['tables_jointures'][]= 'gis_liens';
+       $tables[]['champs_versionnes'][] = 'jointure_gis';
+
+       // recherche jointe sur les points gis pour tous les objets
+       $tables[]['rechercher_jointures']['gis'] = array(
+                       'titre' => 3,
+                       'descriptif' => 2,
+                       'pays' => 4,
+                       'region' => 1,
+                       'departement' => 1,
+                       'ville' => 1,
+                       'code_postal' => 1
+               );
+
+       return $tables;
+}
+
+function gis_declarer_tables_auxiliaires($tables_auxiliaires){
+       $spip_gis_liens = array(
+               "id_gis" => "bigint(21) NOT NULL",
+               "objet" => "VARCHAR (25) DEFAULT '' NOT NULL",
+               "id_objet" => "bigint(21) NOT NULL");
+
+       $spip_gis_liens_key = array(
+               "PRIMARY KEY" => "id_gis,id_objet,objet",
+               "KEY id_objet" => "id_gis");
+
+       $tables_auxiliaires['spip_gis_liens'] = array(
+               'field' => &$spip_gis_liens,
+               'key' => &$spip_gis_liens_key);
+
+       return $tables_auxiliaires;
+}
+
+?>
diff --git a/www/plugins/gis/crud/gis.php b/www/plugins/gis/crud/gis.php
new file mode 100644 (file)
index 0000000..ab9e955
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+include_spip('action/editer_gis');
+
+/**
+ * Interface C(r)UD pour GIS
+ */
+
+/**
+ * Create :
+ * Crée un point géolocalisé
+ * 
+ * @param $dummy
+ * @param array $set : Le contenu des champs à mettre en base
+ * @return array : un array avec (bool) success, (string) message et (array) result indiquant l'id créé 
+ */
+function crud_gis_create_dist($dummy,$set=null){
+       if (autoriser('voir','gis') && $id = gis_inserer())
+               $err = gis_modifier($id,$set);
+       else
+               $err = _T('crud:erreur_creation',array('objet'=>'gis'));
+       return array('success'=>($err && strlen($err)>0)?false:true,'message'=>$err,'result'=>array('id'=>$id));
+}
+
+/**
+ * Update :
+ * Met à jour un point géolocalisé
+ * 
+ * @param $dummy
+ * @param array $set : Le contenu des champs à mettre en base
+ * @return array : un array avec (bool) success, (string) message et (array) result indiquant l'id créé 
+ */
+function crud_gis_update_dist($id,$set=null){
+       $id_gis = sql_getfetsel('id_gis','spip_gis','id_gis='.intval($id));
+       if(!$id_gis){
+               $err = _T('gis:erreur_gis_inconnu',array('id'=>$id));
+       }else if(autoriser('modifier','gis',$id)){
+               $err = gis_modifier($id,$set);
+       }else{
+               $err = _L('update error');
+       }
+       return array('success'=>($err && strlen($err)>0)?false:true,'message'=>$err,'result'=>array('id'=>$id));
+}
+
+/**
+ * Delete :
+ * Supprime un point géolocalisé
+ * 
+ * @param $dummy
+ * @param int $id : L'identifiant numérique du point à supprimer
+ * @return array : un array avec (bool) success, (string) message et (array) result indiquant l'id supprimé 
+ */
+function crud_gis_delete_dist($id){
+       if(autoriser('supprimer','gis',$id)){
+               $err = gis_supprimer($id);
+       }
+       return array('success'=>is_numeric($err)?true:false,'message'=>$err,'result'=>array('id'=>$id));
+}
+
+?>
\ No newline at end of file
diff --git a/www/plugins/gis/embed/kml.html b/www/plugins/gis/embed/kml.html
new file mode 100644 (file)
index 0000000..3fe9d5a
--- /dev/null
@@ -0,0 +1,38 @@
+<BOUCLE_document(DOCUMENTS){id_document}>
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="#LANG" lang="#LANG" dir="#LANG_DIR">
+<head>
+    <title>[(#TITRE|sinon{#FICHIER|basename})] - #NOM_SITE_SPIP</title>
+    <base target="_parent" /> 
+    <meta name="generator" content="SPIP[ (#SPIP_VERSION)]" />
+    <meta http-equiv="Content-Type" content="text/html; charset=#CHARSET" />
+    [<link rel="stylesheet" href="(#CHEMIN{css/embed_code.css})">]
+               
+       [(#VAL{''}|gis_insert_head_css)]
+       [<script src="(#CHEMIN{prive/javascript/jquery.js})" type="text/javascript"></script>]
+    [<script src="(#CHEMIN{prive/javascript/jquery.placeholder-label.js})" type="text/javascript"></script>]
+    [<script src="(#CHEMIN{prive/javascript/ajaxCallback.js})" type="text/javascript"></script>]
+       [(#VAL{''}|gis_insert_head)]
+</head>
+<body id="document_#ENV{id_document}" class="document embed_document embed_document_#MEDIA" dir="#LANG_DIR" style="width:#ENV{largeur}px;height:#ENV{hauteur}px">
+       <div class="infos_document gis">
+               [<div class="logo">(#LOGO_DOCUMENT|image_reduire{100,100})</div>]
+               <div class="titre"><h1>[(#TITRE|sinon{#FICHIER|basename})]</h1></div>
+               <BOUCLE_gis(GIS){id_document}>
+                       <div class="spip_documents" style="clear:both">
+                       #MODELE{carte_gis,largeur=100%,hauteur=#ENV{hauteur,400px},kml=#ID_DOCUMENT,lat=#LAT,lon=#LON,zoom=#ZOOM,point=non,id_carte_gis=kml#ID_DOCUMENT}
+                       </div>
+               </BOUCLE_gis>
+               </B_gis>
+                       <div class="spip_documents" style="clear:both">
+                       #MODELE{carte_gis,largeur=100%,hauteur=#ENV{hauteur,400px},kml=#ID_DOCUMENT,point=non,id_carte_gis=kml#ID_DOCUMENT}
+                       </div>
+               <//B_gis>
+       </div>
+</body>
+</html>
+</BOUCLE_document>
+</B_document>
+#INCLURE{fond=embed/document,env}
+<//B_document>
+#FILTRE{trim}
\ No newline at end of file
diff --git a/www/plugins/gis/embed/kml_fonctions.php b/www/plugins/gis/embed/kml_fonctions.php
new file mode 100644 (file)
index 0000000..7ad44a3
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+// Sécurité
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+/**
+ * On inclue le fichier de pipelines pour avoir les fonctions:
+ * gis_insert_head_css
+ * gis_insert_head
+ */
+include_spip('gis_pipelines');
+
+?>
diff --git a/www/plugins/gis/formulaires/configurer_gis.html b/www/plugins/gis/formulaires/configurer_gis.html
new file mode 100755 (executable)
index 0000000..5ebe968
--- /dev/null
@@ -0,0 +1,196 @@
+<div class="formulaire_spip formulaire_configurer formulaire_configurer_gis">
+
+[<p class="reponse_formulaire reponse_formulaire_ok">(#ENV*{message_ok})</p>]
+[<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV*{message_erreur})</p>]
+
+<div id="map_config" class="carte_gis" style="width: 100%; height: 370px"></div>
+
+<form method="post" action="#ENV{action}"><div>
+       #ACTION_FORMULAIRE{#ENV{action}}
+       <ul>
+               <li class="rechercher_adresse editer_map_config">
+                       <label for="champ_map_config_geocoder"><:gis:label_rechercher_address:></label>
+                       <input type="text" class="text" name="champ_map_config_geocoder" id="champ_map_config_geocoder" value="" />
+                       <a id="map_config_rechercher_geocodage"><:info_rechercher:></a>
+               </li>
+               [(#SAISIE{input,lat,
+                       label=<:gis:lat:>,
+                       defaut=0,
+                       size=40})]
+               [(#SAISIE{input,lon,
+                       label=<:gis:lon:>,
+                       defaut=0,
+                       size=40})]
+               [(#SAISIE{input,zoom,
+                       label=<:gis:zoom:>,
+                       defaut=0,
+                       size=2,
+                       maxlength=2})]
+               
+               #SET{layers,#ARRAY}
+               <BOUCLE_layer(DATA){source table, #EVAL{$GLOBALS['gis_layers']}}>
+               #SET{layers,#GET{layers}|array_merge{#ARRAY{#CLE,#VALEUR|table_valeur{nom}}}}
+               </BOUCLE_layer>
+               
+               [(#SET{layer_defaut,openstreetmap_mapnik})]
+               [(#VAL{_GIS_LAYER_DEFAUT}|defined|oui)
+                       [(#SET{layer_defaut,[(#EVAL{_GIS_LAYER_DEFAUT})]})]
+               ]
+               [(#VAL{_GIS_LAYER_DEFAUT_FORCE}|defined|oui)
+                       [(#SET{layer_readonly,readonly})]
+                       [(#SET{layer_disable,disabled})]
+                       [(#SET{layer_forcee,[(#EVAL{_GIS_LAYER_DEFAUT_FORCE})]})]
+                       [(#SET{layer_explications,<:gis:explication_layer_forcee:>})]
+               ]
+               [(#SAISIE{selection,layer_defaut,
+                       label=<:gis:cfg_lbl_layer_defaut:>,
+                       cacher_option_intro=oui,
+                       defaut=#GET{layer_defaut},
+                       valeur_forcee=#GET{layer_forcee},
+                       readonly=#GET{layer_readonly},
+                       disable=#GET{layer_disable},
+                       explication=#GET{layer_explications},
+                       datas=#GET{layers}})]
+                       
+               [(#SAISIE{selection_multiple,layers,
+                       label=<:gis:cfg_lbl_layers:>,
+                       cacher_option_intro=oui,
+                       defaut=#GET{layer_defaut},
+                       datas=#GET{layers}})]
+               
+               [(#SAISIE{input,api_key_bing,
+                       label=<:gis:cfg_lbl_api_key_bing:>,
+                       explication=<:gis:cfg_inf_bing{url=https://www.bingmapsportal.com/}:>,
+                       size=40})]
+       
+               [(#SAISIE{oui_non,geocoder,
+                       defaut='',
+                       label=<:gis:cfg_lbl_geocoder:>,
+                       explication=<:gis:cfg_inf_geocoder:>})]
+               
+               [(#SAISIE{oui_non,adresse,
+                       defaut='',
+                       label=<:gis:cfg_lbl_adresse:>,
+                       explication=<:gis:cfg_inf_adresse:>})]
+               
+               [(#SAISIE{oui_non,geolocaliser_user_html5,
+                       defaut='',
+                       label=<:gis:cfg_lbl_geolocaliser_user_html5:>,
+                       explication=<:gis:cfg_inf_geolocaliser_user_html5:>})]
+
+               [(#SAISIE{choisir_objets,gis_objets,
+                       label=<:gis:cfg_lbl_activer_objets:>,
+                       exclus=spip_gis})]
+
+       </ul>
+
+
+       <p class="boutons">
+               <input type="submit" name="_cfg_ok" class="submit" value="<:bouton_enregistrer:>" />
+       </p>
+</div></form>
+
+<script type="text/javascript">
+/*<![CDATA[*/
+
+(function($){
+       var map, map_container = 'map_config', geocoder, marker;
+       
+       var maj_inputs = function(map,pos,action) {
+               if(action == 'click'){
+                       var zoom = map.getZoom();
+                       $("#champ_lat").val(pos.lat);
+                       $("#champ_lon").val(pos.lng);
+                       $("#champ_zoom").val(zoom);
+                       annuler_geocoder = 1;
+               }else if(annuler_geocoder != 1){
+                       if(pos.point == 'undefined'){
+                               $('#champ_#ENV{champ_lat,lat}').val(pos.lat);
+                               $('#champ_#ENV{champ_lon,lon}').val(pos.lng);
+                               map.panTo(pos);
+                               marker.setLatLng(pos);
+                       }else{
+                               $('#champ_#ENV{champ_lat,lat}').val(pos.point.lat);
+                               $('#champ_#ENV{champ_lon,lon}').val(pos.point.lng);
+                               map.panTo(pos.point);
+                               marker.setLatLng(pos.point);
+                       }
+               }
+       }
+       
+       function geocode(query) {
+               if(!query.error)
+                       maj_inputs(map,query,'geocoding');
+               else
+                       alert('<:gis:erreur_geocoder:> '+query.search);
+       }
+
+       var init_config = function() {
+               map = new L.Map(map_container);
+               
+               map.attributionControl.setPrefix('');
+               
+               //default layer
+               #SET{layer_defaut,#REM|gis_layer_defaut} #SET{layers,#EVAL{$GLOBALS['gis_layers']}}
+               var [(#GET{layer_defaut})] = [new (#GET{layers}|table_valeur{#GET{layer_defaut}/layer})];
+               map.addLayer([(#GET{layer_defaut})]);
+               
+               <B_layers>
+               var layers_control = new L.Control.Layers();
+               layers_control.addBaseLayer([(#GET{layer_defaut})],["(#GET{layers}|table_valeur{#GET{layer_defaut}/nom})"]);
+               <BOUCLE_layers(DATA){source table, #GET{layers}}{si #ENV{control_type,#ENV{controle_type}}|!={non}|et{#ENV{no_control,#ENV{aucun_controle}}|!={oui}}|et{#CONFIG{gis/layers,#ARRAY}|count|>{1}|oui}|oui}>[
+               (#CLE|!={#GET{layer_defaut}}|oui|et{#CLE|in_array{#CONFIG{gis/layers,#ARRAY}}|oui}|oui)
+               layers_control.addBaseLayer([new (#VALEUR|table_valeur{layer})],"[(#VALEUR|table_valeur{nom})]");]
+               </BOUCLE_layers>
+               map.addControl(layers_control);
+               // classe noajax sur le layer_control pour éviter l'ajout de hidden par SPIP
+               $(layers_control._form).addClass('noajax');
+               </B_layers>
+
+               map.setView(new L.LatLng(#ENV{lat,0},#ENV{lon,0}),#ENV{zoom,0});
+
+               marker = new L.Marker(new L.LatLng(#ENV{lat,0},#ENV{lon,0}));
+               map.addLayer(marker);
+               
+               geocoder = new L.Geocoder(geocode,{acceptLanguage:'#ENV{lang}'});
+               
+               // mettre a jour les coordonnees quand on clique la carte
+               map.on('click', function(e) {
+                       annuler_geocoder = 0;
+                       marker.setLatLng(e.latlng);
+                       map.panTo(e.latlng);
+                       maj_inputs(map,e.latlng,'click');
+               });
+               
+               // geocoder si clic...
+               $('a#map_config_rechercher_geocodage').css("cursor","pointer").click(function(){
+                       var address = $("#champ_map_config_geocoder").val();
+                       annuler_geocoder = 0;
+                       geocoder.geocode(address);
+               });
+               
+               // ne pas soumettre le formulaire si on presse Entree depuis le champ de recherche
+               $('#champ_map_config_geocoder').keypress(function(e){
+                       if (e.which == 13) {
+                               $('a#map_config_rechercher_geocodage').trigger("click");
+                               return false;
+                       }
+               });
+               
+               // mettre à jour le zoom quand on le modifie
+               map.on('zoomend', function(e) {
+                       $("#champ_zoom").val(e.target._zoom);
+               });
+       }
+
+       $(function(){
+               jQuery.getScript('[(#PRODUIRE{fond=javascript/gis.js}|compacte)]',function(){
+                       init_config();
+               });
+               //onAjaxLoad(init_config);
+       });
+
+})(jQuery);
+/*]]>*/
+</script>
+</div>
diff --git a/www/plugins/gis/formulaires/configurer_gis.php b/www/plugins/gis/formulaires/configurer_gis.php
new file mode 100644 (file)
index 0000000..40bdc3b
--- /dev/null
@@ -0,0 +1,33 @@
+<?php 
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+/**
+ * Fonction de verification du formulaire de configuration
+ * - On vérifie que la clé Bing est présente si cette couche est sélectionnée
+ */
+function formulaires_configurer_gis_verifier_dist(){
+       $erreurs = array();
+       
+       if ((_request('layer_defaut') == 'bing_aerial') OR in_array('bing_aerial', _request('layers'))){
+               $obligatoire = 'api_key_bing';
+               if (!_request($obligatoire)){
+                       $erreurs[$obligatoire] = _T('info_obligatoire');
+               }
+       }
+       
+       // S'il n'y a pas d'erreur on va chercher l'ancienne couche par défaut pour voir si elle a changé
+       if (empty($erreurs)){
+               include_spip('inc/config');
+               $layer_defaut = lire_config('gis/layer_defaut');
+               // Si on change la couche par défaut ou si une couche google est présente dans la conf, le formulaire ne doit pas etre traiter en ajax
+               if ((_request('layer_defaut') != $layer_defaut)
+                       OR (count(array_intersect(array('google_roadmap', 'google_satellite', 'google_terrain'), _request('layers'))) > 0)
+                       OR (in_array('bing_aerial', _request('layers'))))
+                       refuser_traiter_formulaire_ajax();
+       }
+       
+       return $erreurs;
+}
+
+?>
diff --git a/www/plugins/gis/formulaires/editer_gis.html b/www/plugins/gis/formulaires/editer_gis.html
new file mode 100755 (executable)
index 0000000..2cf65c9
--- /dev/null
@@ -0,0 +1,66 @@
+#CACHE{0}
+<div class="formulaire_spip formulaire_editer formulaire_editer_gis">
+       <!-- br class='spacer' / -->
+       [<p class="reponse_formulaire reponse_formulaire_ok">(#ENV**{message_ok})</p>]
+       [<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV*{message_erreur})</p>]
+       <BOUCLE_editable(CONDITION){si #ENV{editable}}>
+       <form method='post' action='#ENV{action}' enctype='multipart/form-data' name='formulaire_editer_gis' id='formulaire_editer_gis'><div>
+               [(#REM) declarer les hidden qui declencheront le service du formulaire 
+               parametre : url d'action ]
+               #ACTION_FORMULAIRE{#ENV{action}}
+               <ul>
+                       [(#SAISIE{hidden,objet})]
+                       [(#SAISIE{hidden,id_objet})]
+                       [(#SAISIE{carte,editer_gis_#ENV{id_gis},env})]
+                       <li class="fieldset">
+                       <fieldset><ul>
+                               [(#SAISIE{input,lat,
+                                       label=<:gis:lat:>,
+                                       defaut=#ENV{lat,#CONFIG{gis/lat,0}},
+                                       obligatoire=oui})]
+                               [(#SAISIE{input,lon,
+                                       label=<:gis:lon:>,
+                                       defaut=#ENV{lon,#CONFIG{gis/lon,0}},
+                                       obligatoire=oui})]
+                               [(#SAISIE{input,zoom,
+                                       label=<:gis:zoom:>,
+                                       defaut=#ENV{zoom,#CONFIG{gis/zoom,0}},
+                                       size=2,
+                                       maxlength=2,
+                                       obligatoire=oui})]
+                               [(#SAISIE{input,titre,
+                                       defaut=#INFO_TITRE{#OBJET,#ID_OBJET},
+                                       label=<:info_titre:>,
+                                       obligatoire=oui})]
+                               [(#SAISIE{textarea,descriptif,
+                                       label=<:info_descriptif:>,
+                                       rows=5})]
+                       </ul></fieldset>
+                       </li>
+                       <li class="fieldset adresse"[(#CONFIG{gis/adresse}|=={on}|non) style="display: none;"]>
+                       <fieldset><ul>
+                               [(#SAISIE{input,adresse,
+                                       label=<:gis:label_adress:>})]
+                               [(#SAISIE{input,code_postal,
+                                       label=<:gis:label_code_postal:>})]
+                               [(#SAISIE{input,ville,
+                                       label=<:gis:label_ville:>})]
+                               [(#SAISIE{input,departement,
+                                       label=<:gis:label_departement:>})]
+                               [(#SAISIE{input,region,
+                                       label=<:gis:label_region:>})]
+                               [(#SAISIE{input,pays,
+                                       label=<:gis:label_pays:>})]
+                               [(#SAISIE{input,code_pays,
+                                       label=<:gis:label_code_pays:>})]
+                       </ul></fieldset>
+                       </li>
+               </ul>
+               [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ]
+               <!--extra-->
+               <p class='boutons'[ style="direction: (#LANG_DIR|=={ltr}|?{rtl,ltr})"]>
+                       <input class='submit' type='submit' name='enregistrer' value='<:bouton_enregistrer:>' />
+               </p>
+       </div></form>
+       </BOUCLE_editable>
+</div>
diff --git a/www/plugins/gis/formulaires/editer_gis.php b/www/plugins/gis/formulaires/editer_gis.php
new file mode 100644 (file)
index 0000000..22619dd
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+/**
+ * Formulaire de création et d'édition d'un point géolocalisé
+ */
+
+include_spip('inc/actions');
+include_spip('inc/editer');
+
+/**
+ * Chargement des valeurs par défaut du formulaire
+ * 
+ * @param int|string $id_gis Identifiant numérique du point ou 'new' pour un nouveau
+ * @param string $objet Le type d'objet SPIP auquel il est attaché
+ * @param int $id_objet L'id_objet de l'objet auquel il est attaché
+ * @param string $retour L'url de retour
+ * @param string $ajaxload initialiser la carte à chaque onAjaxLoad()
+ * @param array $options TODO à documenter, voir avec l'auteur de http://zone.spip.org/trac/spip-zone/changeset/53906
+ */
+function formulaires_editer_gis_charger_dist($id_gis='new', $objet='', $id_objet='', $retour='', $ajaxload='oui', $options=''){
+       $valeurs = formulaires_editer_objet_charger('gis', $id_gis, '', '', $retour, '');
+       $valeurs['objet'] = $objet;
+       $valeurs['id_objet'] = $id_objet;
+       $valeurs['ajaxload'] = $ajaxload;
+    /* Traitement des options */
+       /* peut etre a envoyer dans une fonction generique de verification des options */
+       if (is_array($options)) {
+       if (!$valeurs['lat'] and is_numeric($options['lat']))
+               $valeurs['lat']=$options['lat'];
+           if (!$valeurs['lon'] and is_numeric($options['lon']))
+               $valeurs['lon']=$options['lon'];
+       if (!$valeurs['zoom'] and is_numeric($options['zoom']) && intval($options['zoom'])==$options['zoom'])
+               $valeurs['zoom']=$options['zoom'];
+               /* Bounding Box */
+           if (is_numeric($options['sw_lat']))
+               $valeurs['sw_lat']=$options['sw_lat'];
+           if (is_numeric($options['sw_lon']))
+               $valeurs['sw_lon']=$options['sw_lon'];
+           if (is_numeric($options['ne_lat']))
+               $valeurs['ne_lat']=$options['ne_lat'];
+           if (is_numeric($options['ne_lon']))
+               $valeurs['ne_lon']=$options['ne_lon'];
+       }
+       return $valeurs;
+}
+
+/**
+ * Vérification des valeurs du formulaire
+ * 
+ * 4 champs sont obligatoires :
+ * -* Son titre
+ * -* Sa latitude
+ * -* Sa longitude
+ * -* Son niveau de zoom
+ * 
+ * @param int|string $id_gis Identifiant numérique du point ou 'new' pour un nouveau
+ * @param string $objet Le type d'objet SPIP auquel il est attaché
+ * @param int $id_objet L'id_objet de l'objet auquel il est attaché
+ * @param string $retour L'url de retour
+ * @param string $ajaxload initialiser la carte à chaque onAjaxLoad()
+ * @param array $options ???
+ */
+function formulaires_editer_gis_verifier_dist($id_gis='new', $objet='', $id_objet='', $retour='', $ajaxload='oui', $options=''){
+       $erreurs = formulaires_editer_objet_verifier('gis', $id_gis,array('titre','lat','lon','zoom'));
+       return $erreurs;
+}
+
+/**
+ * Traitement des valeurs du formulaire
+ * 
+ * @param int|string $id_gis Identifiant numérique du point ou 'new' pour un nouveau
+ * @param string $objet Le type d'objet SPIP auquel il est attaché
+ * @param int $id_objet L'id_objet de l'objet auquel il est attaché
+ * @param string $retour L'url de retour
+ * @param string $ajaxload initialiser la carte à chaque onAjaxLoad()
+ * @param array $options ???
+ */
+function formulaires_editer_gis_traiter_dist($id_gis='new', $objet='', $id_objet='', $retour='', $ajaxload='oui', $options=''){
+       if (_request('supprimer')){
+               include_spip('action/editer_gis');
+               supprimer_gis($id_gis);
+               $id_table_objet = id_table_objet($objet);
+               if ($retour)
+                       $res['redirect'] = parametre_url($retour,$id_table_objet,$id_objet);
+               return $res;
+       } else {
+               return formulaires_editer_objet_traiter('gis', $id_gis, '', '', $retour, '');
+       }
+}
+
+?>
diff --git a/www/plugins/gis/formulaires/gis_inserer_modeles_traiter.php b/www/plugins/gis/formulaires/gis_inserer_modeles_traiter.php
new file mode 100644 (file)
index 0000000..0126211
--- /dev/null
@@ -0,0 +1,34 @@
+<?php\r
+if (!defined("_ECRIRE_INC_VERSION")) return;\r
+\r
+function formulaires_gis_inserer_modeles_traiter_dist($champs) {\r
+\r
+       // champs a ne pas prendre en compte\r
+       $ignorer = array('adresse','code_postal','ville','pays');\r
+\r
+       $code = '<'._request('modele');\r
+       if (_request('id_modele') && _request('id_modele')!='')\r
+               $code .= _request('id_modele');\r
+       if (_request('variante') && _request('variante')!='')\r
+               $code .= '|'._request('variante');\r
+       if (_request('classe') && _request('classe')!='')\r
+               $code .= '|'._request('classe');\r
+       if (_request('align') && _request('align')!='')\r
+               $code .= '|'._request('align');\r
+       foreach ($champs as $champ) {\r
+               if( !in_array($champ, $ignorer) && $champ != 'modele' && $champ != 'variante' && $champ != 'classe' && $champ != 'id_modele' && $champ != 'align' && _request($champ) && _request($champ)!='') {\r
+                       if($champ == _request($champ))\r
+                               $code .= "|$champ";\r
+                       // On transforme les tableaux en une liste\r
+                       elseif (is_array(_request($champ)))\r
+                               $code .= "|$champ=".implode(',',_request($champ));\r
+                       else\r
+                               $code .= "|$champ="._request($champ);\r
+               }\r
+       }\r
+       $code .= '>';\r
+\r
+       return $code;\r
+}\r
+\r
+?>\r
diff --git a/www/plugins/gis/formulaires/rechercher_gis.html b/www/plugins/gis/formulaires/rechercher_gis.html
new file mode 100755 (executable)
index 0000000..9d6e8b4
--- /dev/null
@@ -0,0 +1,36 @@
+#CACHE{0}
+<BOUCLE_exclure(GIS){objet=#ENV{objet}}{id_objet=#ENV{id_objet}}{doublons objet}> </BOUCLE_exclure>
+<BOUCLE_si_recherche(CONDITION){si #ENV{recherche_gis}}>
+<B_recherche>
+       <ul class="liste_items">
+       <BOUCLE_recherche(GIS){recherche #ENV{recherche_gis}}{doublons objet}>#SET{id_gis,#ID_GIS}
+       #SET{ou, #LISTE{#VILLE, #PAYS}|array_filter|join{", "}}
+       [<li class="item">(#TITRE)[ ((#GET{ou}))]
+               <div class="actions">
+                       [(#BOUTON_ACTION{<:gis:bouton_lier:>,[(#URL_ACTION_AUTEUR{editer_lien_gis,lier/#GET{id_gis}/#ENV{objet}/#ENV{id_objet},#SELF})],ajax})]
+               </div>
+       </li>]
+       </BOUCLE_recherche>
+       </ul>
+</B_recherche>
+<p class="reponse_formulaire reponse_formulaire_erreur"><:gis:erreur_recherche_pas_resultats:></p>
+<//B_recherche>
+</BOUCLE_si_recherche>
+<div class="formulaire_spip formulaire_rechercher formulaire_rechercher_gis">
+       [<p class="reponse_formulaire reponse_formulaire_ok">(#ENV*{message_ok})</p>]
+       [<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV*{message_erreur})</p>]
+       <BOUCLE_si_editable(CONDITION){si #ENV{editable}}>
+       <form method='post' action='#ENV{action}' name='formulaire_rechercher_gis' id='formulaire_rechercher_gis'><div>
+               #ACTION_FORMULAIRE{#ENV{action}}
+               <ul>
+                       [(#SAISIE{input,recherche_gis,
+                               label=<:gis:label_rechercher_point:>})]
+               </ul>
+               [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ]
+               <!--extra-->
+               <p class='boutons'>
+                       <input class='submit' type='submit' name='enregistrer' value='<:info_rechercher:>' />
+               </p>
+       </div></form>
+       </BOUCLE_si_editable>
+</div>
diff --git a/www/plugins/gis/formulaires/rechercher_gis.php b/www/plugins/gis/formulaires/rechercher_gis.php
new file mode 100755 (executable)
index 0000000..5e1597a
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+/**
+ * Formulaire de création et d'édition d'un point géolocalisé
+ */
+
+include_spip('inc/actions');
+include_spip('inc/editer');
+
+/**
+ * Chargement des valeurs par défaut du formulaire
+ * 
+ * @param string $objet Le type d'objet SPIP auquel il est attaché
+ * @param int $id_objet L'id_objet de l'objet auquel il est attaché
+ * @param string $retour L'url de retour
+ * @param string $recherche
+ */
+function formulaires_rechercher_gis_charger_dist($objet='', $id_objet='', $retour='', $recherche=''){
+       $valeurs['recherche_gis'] = _request('recherche_gis');
+       $valeurs['objet'] = $objet;
+       $valeurs['id_objet'] = $id_objet;
+       return $valeurs;
+}
+
+/**
+ * Vérification des valeurs du formulaire
+ * 
+ * @param string $objet Le type d'objet SPIP auquel il est attaché
+ * @param int $id_objet L'id_objet de l'objet auquel il est attaché
+ * @param string $retour L'url de retour
+ * @param string $recherche
+ */
+function formulaires_rechercher_gis_verifier_dist($objet='', $id_objet='', $retour='', $recherche=''){
+       return $erreurs;
+}
+
+/**
+ * Traitement des valeurs du formulaire
+ * 
+ * @param string $objet Le type d'objet SPIP auquel il est attaché
+ * @param int $id_objet L'id_objet de l'objet auquel il est attaché
+ * @param string $retour L'url de retour
+ * @param string $recherche
+ */
+function formulaires_rechercher_gis_traiter_dist($objet='', $id_objet='', $retour='', $recherche=''){
+       return;
+}
+
+?>
\ No newline at end of file
diff --git a/www/plugins/gis/gis_administrations.php b/www/plugins/gis/gis_administrations.php
new file mode 100644 (file)
index 0000000..c158c2f
--- /dev/null
@@ -0,0 +1,116 @@
+<?php
+
+// Sécurité
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+/**
+ * Installation/maj des tables gis
+ *
+ * @param string $nom_meta_base_version
+ * @param string $version_cible
+ */
+function gis_upgrade($nom_meta_base_version, $version_cible){
+       $maj = array();
+       
+       // Première installation
+       $maj['create'] = array(
+               array('maj_tables', array('spip_gis')),
+               array('maj_tables', array('spip_gis_liens')),
+       );
+       
+       // Mise à jour depuis GIS 1
+       $maj['2.0'] = array(
+               // On ajoute la nouvelle table
+               array('maj_tables', array('spip_gis_liens')),
+               // On renomme le champ #LONX en #LON
+               array('sql_alter', 'TABLE spip_gis CHANGE lonx lon float(21) NULL NULL'),
+               // On déplace les liaisons articles, rubriques et mots
+               array('gis_upgrade_2_0'),
+               // Virer les champs id_article et id_rubrique
+               array('sql_alter', 'TABLE spip_gis DROP id_article'),
+               array('sql_alter', 'TABLE spip_gis DROP id_rubrique'),
+               // Virer les index id_article et id_rubrique
+               array('sql_alter', 'TABLE spip_gis DROP INDEX id_article'),
+               array('sql_alter', 'TABLE spip_gis DROP INDEX id_rubrique'),
+               // Virer la table pour les mots
+               array('sql_drop_table', 'spip_gis_mots'),
+       );
+       
+       // Des nouveaux champs
+       $maj['2.0.1'] = array(
+               array('maj_tables', array('spip_gis')),
+       );
+       
+       // Augmenter la précision des champs de coordonnées
+       $maj['2.0.2'] = array(
+               array('sql_alter', 'TABLE spip_gis CHANGE lat lat DOUBLE NULL NULL'),
+               array('sql_alter', 'TABLE spip_gis CHANGE lon lon DOUBLE NULL NULL'),
+       );
+       
+       // Ajouter des INDEX sur les champs potentiellement utilisables dans des comparaisons/group by/etc.
+       $maj['2.0.4'] = array(
+               array('sql_alter', 'TABLE spip_gis ADD INDEX (lat)'),
+               array('sql_alter', 'TABLE spip_gis ADD INDEX (lon)'),
+               array('sql_alter', 'TABLE spip_gis ADD INDEX (pays(500))'),
+               array('sql_alter', 'TABLE spip_gis ADD INDEX (code_pays)'),
+               array('sql_alter', 'TABLE spip_gis ADD INDEX (region(500))'),
+               array('sql_alter', 'TABLE spip_gis ADD INDEX (ville(500))'),
+               array('sql_alter', 'TABLE spip_gis ADD INDEX (code_postal)'),
+       );
+       
+       // Ajout du département dans les champs de coordonnées
+       $maj['2.0.5'] = array(
+               array('maj_tables',array('spip_gis')),
+       );
+       
+       // Transformer les titres des points de varchar(255) à text
+       $maj['2.0.6'] = array(
+               array('sql_alter', 'TABLE spip_gis CHANGE titre titre text NOT NULL'),
+       );
+       
+       include_spip('base/upgrade');
+       maj_plugin($nom_meta_base_version, $version_cible, $maj);
+}
+
+function gis_upgrade_2_0(){
+       include_spip('action/editer_gis');
+       
+       // On déplace les liaisons articles et rubriques
+       $res = sql_select('*','spip_gis');
+       while ($row = sql_fetch($res)) {
+               if($row['id_article'] != 0)
+                       lier_gis($row['id_gis'], 'article', $row['id_article']);
+               if($row['id_rubrique'] != 0)
+                       lier_gis($row['id_gis'], 'article', $row['id_rubrique']);
+       }
+       
+       // On déplace les liaisons mots
+       $res = sql_select('*','spip_gis_mots');
+       while ($row = sql_fetch($res)) {
+               $titre_mot = sql_getfetsel('titre','spip_mots','id_mot='.$row['id_mot']);
+               $c = array(
+                       'titre' => $titre_mot,
+                       'lat'=> $row['lat'],
+                       'lon' => $row['lonx'],
+                       'zoom' => $row['zoom']
+               );
+               $id_gis = insert_gis();
+               revisions_gis($id_gis,$c);
+               lier_gis($id_gis, 'mot', $row['id_mot']);
+       }
+}
+
+/**
+ * Desinstallation/suppression des tables gis
+ *
+ * @param string $nom_meta_base_version
+ */
+function gis_vider_tables($nom_meta_base_version) {
+       sql_drop_table("spip_gis");
+       sql_drop_table("spip_gis_liens");
+       effacer_meta($nom_meta_base_version);
+       // Effacer la config
+       effacer_meta('gis');
+}
+
+?>
diff --git a/www/plugins/gis/gis_autoriser.php b/www/plugins/gis/gis_autoriser.php
new file mode 100644 (file)
index 0000000..13d4c48
--- /dev/null
@@ -0,0 +1,113 @@
+<?php 
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+function gis_autoriser(){};
+
+/**
+ * Autorisation a modifier le logo d'un point
+ * Si on est autorisé à modifier le point en question
+ * 
+ * @param string $faire L'action
+ * @param string $type Le type d'objet
+ * @param int $id L'identifiant numérique de l'objet
+ * @param array $qui Les informations de session de l'auteur
+ * @param array $opt Des options
+ * @return boolean true/false
+ */
+function autoriser_gis_iconifier_dist($faire,$quoi){
+       return autoriser('modifier','gis',$id,$qui,$opts);
+}
+
+/**
+ * Autorisation a modifier un point
+ * Avoir un statut dans les 3 fournis par SPIP
+ * (On n'a pas d'auteur pour un point ...)
+ * 
+ * @param string $faire L'action
+ * @param string $type Le type d'objet
+ * @param int $id L'identifiant numérique de l'objet
+ * @param array $qui Les informations de session de l'auteur
+ * @param array $opt Des options
+ * @return boolean true/false
+ */
+function autoriser_gis_modifier_dist($faire,$quoi,$id,$qui,$opts){
+       return (in_array($qui['statut'],array('0minirezo','1comite','6forum')));
+}
+
+/**
+ * Autorisation a creer un point
+ * Avoir un statut dans les 3 fournis par SPIP
+ * (On n'a pas d'auteur pour un point ...)
+ * 
+ * @param string $faire L'action
+ * @param string $type Le type d'objet
+ * @param int $id L'identifiant numérique de l'objet
+ * @param array $qui Les informations de session de l'auteur
+ * @param array $opt Des options
+ * @return boolean true/false
+ */
+function autoriser_gis_creer_dist($faire,$quoi,$id,$qui,$opts){
+       return (in_array($qui['statut'],array('0minirezo','1comite','6forum')));
+}
+
+/**
+ * Autorisation a lier un point d'un objet
+ * Un auteur peut lier un point à un autre objet que s'il peut modifier l'objet à lier en question
+ * 
+ * @param string $faire L'action
+ * @param string $type Le type d'objet
+ * @param int $id L'identifiant numérique de l'objet
+ * @param array $qui Les informations de session de l'auteur
+ * @param array $opt Des options
+ * @return boolean true/false
+ */
+function autoriser_gis_lier_dist($faire,$quoi,$id,$qui,$opts){
+       if(is_array($opts) && isset($opts['objet']) && isset($opts['id_objet'])){
+               return autoriser('modifier',$opts['objet'],$opts['id_objet'],$qui);
+       }
+       return false;
+}
+
+/**
+ * Autorisation a délier un point d'un objet
+ * Un auteur peut délier un point d'un autre objet que s'il peut modifier l'objet en question
+ * Si l'objet lié n'existe plus, on vérifie que l'auteur a le droit de modifier le point
+ * 
+ * @param string $faire L'action
+ * @param string $type Le type d'objet
+ * @param int $id L'identifiant numérique de l'objet
+ * @param array $qui Les informations de session de l'auteur
+ * @param array $opt Des options
+ * @return boolean true/false
+ */
+function autoriser_gis_delier_dist($faire,$quoi,$id,$qui,$opts){
+       $table = table_objet_sql($opts['objet']);
+       $_id_objet = id_table_objet($table);
+       if (!sql_getfetsel($_id_objet,$table,"$_id_objet=".intval($opts['id_objet'])))
+               return autoriser('modifier','gis',$id,$qui,$opts);
+       else
+               return autoriser('lier','gis',$id,$qui,$opts);
+}
+
+/**
+ * Autorisation a supprimer un point
+ * Un auteur peut supprimer un point s'il peut délier tous les objets et modifier le point
+ * 
+ * @param string $faire L'action
+ * @param string $type Le type d'objet
+ * @param int $id L'identifiant numérique de l'objet
+ * @param array $qui Les informations de session de l'auteur
+ * @param array $opt Des options
+ * @return boolean true/false
+ */
+function autoriser_gis_supprimer_dist($faire,$quoi,$id,$qui,$opts){
+       $liaisons = sql_select('*','spip_gis_liens','id_gis='.intval($id));
+       while($liaison = sql_fetch($liaisons)){
+               if(!autoriser('delier','gis',$liaison['id_gis'],$qui,$liaison)){
+                       return false;
+               }
+       }
+       return autoriser('modifier','gis',$id,$qui,$opts);
+}
+?>
\ No newline at end of file
diff --git a/www/plugins/gis/gis_download.html b/www/plugins/gis/gis_download.html
new file mode 100644 (file)
index 0000000..914a7bd
--- /dev/null
@@ -0,0 +1 @@
+<BOUCLE_gis(GIS){id_gis}>[(#ENV**{format}|=={kml}|?{#HTTP_HEADER{Content-Type: application/vnd.google-earth.kml+xml;charset=#CHARSET},#HTTP_HEADER{Content-Type: application/gpx+xml;charset=#CHARSET}})][(#HTTP_HEADER{Content-Disposition: attachment; filename=#TITRE|concat{.#ENV**{format}}})][(#INCLURE{fond=inclure/download_#ENV{format},id_gis})]</BOUCLE_gis>
\ No newline at end of file
diff --git a/www/plugins/gis/gis_fonctions.php b/www/plugins/gis/gis_fonctions.php
new file mode 100755 (executable)
index 0000000..1c22da8
--- /dev/null
@@ -0,0 +1,427 @@
+<?php
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+include_spip('inc/config');
+include_spip('inc/json');
+
+/** 
+ * Filtre dec_to_dms, http://www.statemaster.com/encyclopedia/Geographic-coordinate-conversion
+ * 
+ * @param decimal $coord
+ * @return string
+ */
+function dec_to_dms($coord) {
+       return sprintf(
+               "%0.0f° %2.3f",
+               floor(abs($coord)),
+               60*(abs($coord)-floor(abs($coord)))
+       );
+}
+
+/** 
+ * Filtre dms_to_dec, http://www.statemaster.com/encyclopedia/Geographic-coordinate-conversion
+ * 
+ * @param string $ref N, E, S, W
+ * @param int $deg
+ * @param int $min
+ * @param int $sec
+ * @return decimal
+ */
+function dms_to_dec($ref,$deg,$min,$sec) {
+
+       $arrLatLong = array();
+       $arrLatLong["N"] = 1;
+       $arrLatLong["E"] = 1;
+       $arrLatLong["S"] = -1;
+       $arrLatLong["W"] = -1;
+
+       return ($deg+((($min*60)+($sec))/3600)) * $arrLatLong[$ref];
+}
+
+/** 
+ * Filtre distance pour renvoyer la distance entre deux points
+ * http://snipplr.com/view/2531/calculate-the-distance-between-two-coordinates-latitude-longitude/
+ * sinon voir ici : http://zone.spip.org/trac/spip-zone/browser/_plugins_/forms/geoforms/inc/gPoint.php
+ * 
+ * @param int|array $from
+ *     id_gis du point de référence ou tableau de coordonnées
+ * @param int|array $to
+ *     id_gis du point distant ou tableau de coordonnées
+ * @param bool $miles
+ *     Renvoyer le résultat en miles (kilomètres par défaut)
+ * @return float
+ *     Retourne la distance en kilomètre ou en miles
+ */
+function distance($from, $to, $miles=false) {
+       // On ne travaille que si on a toutes les infos
+       if (
+               // Le départ est soit un tableau soit un entier
+               (
+                       (is_array($from) and isset($from['lat']) and isset($from['lon']))
+                       or
+                       ($from = intval($from) and $from > 0 and $from = sql_fetsel('lat,lon','spip_gis',"id_gis=$from"))
+               )
+               and
+               // Le distant est soit un tableau soit un entier
+               (
+                       (is_array($to) and isset($to['lat']) and isset($to['lon']))
+                       or
+                       ($to = intval($to) and $to > 0 and $to = sql_fetsel('lat,lon','spip_gis',"id_gis=$to"))
+               )
+       ){
+               $pi80 = M_PI / 180;
+               $from['lat'] *= $pi80;
+               $from['lon'] *= $pi80;
+               $to['lat'] *= $pi80;
+               $to['lon'] *= $pi80;
+
+               $r = 6372.797; // mean radius of Earth in km
+               $dlat = $to['lat'] - $from['lat'];
+               $dlng = $to['lon'] - $from['lon'];
+               $a = sin($dlat / 2) * sin($dlat / 2) + cos($from['lat']) * cos($to['lat']) * sin($dlng / 2) * sin($dlng / 2);
+               $c = 2 * atan2(sqrt($a), sqrt(1 - $a));
+               $km = $r * $c;
+
+               return ($miles ? ($km * 0.621371192) : $km);
+       }
+       
+       return false;
+}
+
+/**
+ * Compilation du critère {distancefrom}
+ * 
+ * Critère {distancefrom} qui permet de ne sélectionner que les objets se trouvant à une distance comparée avec un point de repère.
+ * On doit lui passer 3 paramètres obligatoires :
+ * - le point de repère qui est un tableau avec les clés "lat" et "lon" ou un id_gis
+ * - l'opérateur de comparaison
+ * - la distance à comparer, en kilomètres
+ * Cela donne par exemple :
+ *   {distancefrom #ARRAY{lat,#LAT,lon,#LON},<,30}
+ *   {distancefrom #ARRAY{lat,#ENV{lat},lon,#ENV{lon}},<=,#ENV{distance}}
+ *
+ * @param unknown $idb
+ * @param unknown &$boucles
+ * @param unknown $crit
+ */
+function critere_distancefrom_dist($idb, &$boucles, $crit) {
+       $boucle = &$boucles[$idb];
+       $id_table = $boucle->id_table; // articles
+       $primary = $boucle->primary; // id_article
+       $objet = objet_type($id_table); // article
+       
+       if (
+               // Soit depuis une boucle (GIS) soit un autre objet mais avec {gis}
+               ($id_table == 'gis' or isset($boucle->join['gis']))
+               // Il faut aussi qu'il y ait 3 critères obligatoires
+               and count($crit->param) == 3
+       ){
+               $point_reference = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent);
+               $operateur = calculer_liste($crit->param[1], array(), $boucles, $boucles[$idb]->id_parent);
+               $distance = calculer_liste($crit->param[2], array(), $boucles, $boucles[$idb]->id_parent);
+
+               // Si le point de référence est un entier, on essaye de récupérer les coordonnées du point GIS
+               // Et si on a toujours pas de tableau correct, on met false
+               $boucle->hierarchie .= '$point_reference = '.$point_reference.';';
+               $boucle->hierarchie .= 'if (is_numeric($point_reference)){ $point_reference = sql_fetsel("lat,lon", "spip_gis", "id_gis = ".intval($point_reference)); }';
+               $boucle->hierarchie .= 'if (!is_array($point_reference) or !isset($point_reference["lat"]) or !isset($point_reference["lon"])){ $point_reference = false; }';
+               // L'opérateur doit exister dans une liste précise
+               $boucle->hierarchie .= '$operateur_distance = trim('.$operateur.');';
+               $boucle->hierarchie .= 'if (!in_array($operateur_distance, array("=","<",">","<=",">="))){ $operateur_distance = false; }';
+               $boucle->hierarchie .= '$distance = '.$distance.';';
+               
+               $boucle->select[] = '".(!$point_reference ? "\'\' as distance" : "(6371 * acos( cos( radians(".$point_reference["lat"].") ) * cos( radians( gis.lat ) ) * cos( radians( gis.lon ) - radians(".$point_reference["lon"].") ) + sin( radians(".$point_reference["lat"].") ) * sin( radians( gis.lat ) ) ) ) AS distance")."';
+               $boucle->having[] = '((!$point_reference or !$operateur_distance or !$distance) ? "1=1" : "distance $operateur_distance ".sql_quote($distance))';
+       }
+}
+
+/**
+ * Critere {gis distance<XX} pour filtrer une liste de points par rapport à la distance du point de l'env
+ *
+ * @param unknown_type $idb
+ * @param unknown_type $boucles
+ * @param unknown_type $crit
+ */
+function critere_gis_dist($idb, &$boucles, $crit) {
+       $boucle = &$boucles[$idb];
+       $id_table = $boucle->id_table; // articles
+       $primary = $boucle->primary; // id_article
+       $objet = objet_type($id_table); // article
+       
+       if ($id_table == 'gis') {
+               // exclure l'élément en cours des résultats
+               $id_gis = calculer_argument_precedent($idb,$primary, $boucles);
+               $boucle->where[]= array("'!='", "'$boucle->id_table." . "$primary'", $id_gis);
+               
+               // récupérer les paramètres du critère
+               $op='';
+               $params = $crit->param;
+               $type = array_shift($params);
+               $type = $type[0]->texte;
+               if(preg_match(',^(\w+)([<>=]+)([0-9]+)$,',$type,$r)){
+                       $type=$r[1];
+                       $op=$r[2];
+                       $op_val=$r[3];
+               }
+               if ($op)
+                       $boucle->having[]= array("'".$op."'", "'".$type."'",$op_val);
+               
+               // récupérer lat/lon du point de la boucle englobante
+               $lat = calculer_argument_precedent($idb,'lat', $boucles);
+               $lon = calculer_argument_precedent($idb,'lon', $boucles);
+               
+               // http://www.awelty.fr/developpement-web/php/
+               // http://www.movable-type.co.uk/scripts/latlong-db.html
+               // http://code.google.com/intl/fr/apis/maps/articles/geospatial.html#geospatial
+               $select = "(6371 * acos( cos( radians(\".$lat.\") ) * cos( radians( gis.lat ) ) * cos( radians( gis.lon ) - radians(\".$lon.\") ) + sin( radians(\".$lat.\") ) * sin( radians( gis.lat ) ) ) ) AS distance";
+               $order = "'distance'";
+               
+               $boucle->select[]= $select;
+               $boucle->order[]= $order;
+       } else {
+               // ajouter tous les champs du point au select 
+               // et les suffixer pour lever toute ambiguite avec des champs homonymes
+               $boucle->select[]= 'gis.titre AS titre_gis';
+               $boucle->select[]= 'gis.descriptif AS descriptif_gis';
+               $boucle->select[]= 'gis.adresse AS adresse_gis';
+               $boucle->select[]= 'gis.pays AS pays_gis';
+               $boucle->select[]= 'gis.code_pays AS code_pays_gis';
+               $boucle->select[]= 'gis.region AS region_gis';
+               $boucle->select[]= 'gis.departement AS departement_gis';
+               $boucle->select[]= 'gis.ville AS ville_gis';
+               $boucle->select[]= 'gis.code_postal AS code_postal_gis';
+               // jointure sur spip_gis_liens/spip_gis
+               // cf plugin notation
+               // $boucle->join["surnom (as) table de liaison"] = array("surnom de la table a lier", "cle primaire de la table de liaison", "identifiant a lier", "type d'objet de l'identifiant");
+               $boucle->from['gis_liens'] = 'spip_gis_liens';
+               $boucle->join['gis_liens']= array("'$id_table'","'id_objet'","'$primary'","'gis_liens.objet='.sql_quote('$objet')");
+               $boucle->from['gis'] = 'spip_gis';
+               $boucle->join['gis']= array("'gis_liens'","'id_gis'");
+               // bien renvoyer tous les points qui son attachés à l'objet
+               // mais attention, si on trouve en amont un groupement portant sur un champ *de GIS*,
+               // alors cela signifie que la personne veut faire une opération de groupement sur les points donc là on n'ajoute pas id_gis
+               $tous_les_points = true;
+               foreach ($boucle->group as $champ){
+                       if (in_array($champ, array('ville', 'code_postal', 'pays', 'code_pays', 'region','departement'))) {
+                               $tous_les_points = false;
+                       }
+               }
+               if ($tous_les_points) {
+                       $boucle->group[] = 'gis_liens.id_gis';
+               }
+               // ajouter gis aux jointures et spécifier les jointures explicites pour pouvoir utiliser les balises de la table de jointure
+               // permet de passer dans trouver_champ_exterieur() depuis index_tables_en_pile()
+               // cf http://article.gmane.org/gmane.comp.web.spip.zone/6628
+               $boucle->jointures[] = 'gis';
+               if (empty($boucle->jointures_explicites)){
+                       $boucle->jointures_explicites = 'gis_liens gis';
+               }
+               else{
+                       $boucle->jointures_explicites .= ' gis_liens gis';
+               }
+       }
+}
+
+/**
+ * Balise #DISTANCE issue du critère {gis distance<XX}
+ * merci marcimant : http://formation.magraine.net/spip.php?article61
+ *
+ * @param unknown_type $p
+ */
+function balise_distance_dist($p) {
+       return rindex_pile($p, 'distance', 'gis');
+}
+
+/**
+ * Balise #TITRE_GIS : retourne le titre du point
+ * Necessite le critere {gis} sur la boucle
+ *
+ * @param unknown_type $p
+ */
+function balise_titre_gis_dist($p) {
+       return rindex_pile($p, 'titre_gis', 'gis');
+}
+
+/**
+ * Balise #DESCRIPTIF_GIS : retourne le descriptif du point
+ * Necessite le critere {gis} sur la boucle
+ *
+ * @param unknown_type $p
+ */
+function balise_descriptif_gis_dist($p) {
+       return rindex_pile($p, 'descriptif_gis', 'gis');
+}
+
+/**
+ * Balise #ADRESSE_GIS : retourne l'adresse du point
+ * Necessite le critere {gis} sur la boucle
+ *
+ * @param unknown_type $p
+ */
+function balise_adresse_gis_dist($p) {
+       return rindex_pile($p, 'adresse_gis', 'gis');
+}
+
+/**
+ * Balise #PAYS_GIS : retourne le pays du point
+ * Necessite le critere {gis} sur la boucle
+ *
+ * @param unknown_type $p
+ */
+function balise_pays_gis_dist($p) {
+       return rindex_pile($p, 'pays_gis', 'gis');
+}
+
+/**
+ * Balise #CODE_PAYS_GIS : retourne le code pays du point
+ * Necessite le critere {gis} sur la boucle
+ *
+ * @param unknown_type $p
+ */
+function balise_code_pays_gis_dist($p) {
+       return rindex_pile($p, 'code_pays_gis', 'gis');
+}
+
+/**
+ * Balise #VILLE_GIS : retourne la ville du point
+ * Necessite le critere {gis} sur la boucle
+ *
+ * @param unknown_type $p
+ */
+function balise_ville_gis_dist($p) {
+       return rindex_pile($p, 'ville_gis', 'gis');
+}
+
+/**
+ * Balise #REGION_GIS : retourne la région du point
+ * Necessite le critere {gis} sur la boucle
+ *
+ * @param unknown_type $p
+ */
+function balise_region_gis_dist($p) {
+       return rindex_pile($p, 'region_gis', 'gis');
+}
+
+/**
+ * Balise #DEPARTEMENT_GIS : censé retourner le département du point
+ * Necessite le critere {gis} sur la boucle
+ *
+ * @param unknown_type $p
+ */
+function balise_departement_gis_dist($p) {
+       return rindex_pile($p, 'departement_gis', 'gis');
+}
+
+/**
+ * Balise #CODE_POSTAL_GIS : retourne le code postal du point
+ * Necessite le critere {gis} sur la boucle
+ *
+ * @param unknown_type $p
+ */
+function balise_code_postal_gis_dist($p) {
+       return rindex_pile($p, 'code_postal_gis', 'gis');
+}
+
+/**
+ * Définition du fond de carte à utiliser par défaut en prenant compte les defines
+ */
+function gis_layer_defaut(){
+       $defaut = 'openstreetmap_mapnik';
+       if(defined('_GIS_LAYER_DEFAUT_FORCE')){
+               return _GIS_LAYER_DEFAUT_FORCE;
+       }else{
+               if(defined('_GIS_LAYER_DEFAUT')){
+                       $defaut = _GIS_LAYER_DEFAUT;
+               }
+               $config = lire_config('gis/layer_defaut');
+               return $config ? $config : $defaut;
+       }
+}
+
+/**
+ * Recuperer les cles primaires du env pour l'appel a l'url json des points
+ * @param $env
+ * @return array
+ */
+function gis_modele_url_json_env($env){
+       $contexte = array();
+       if (is_string($env))
+               $env = unserialize($env);
+       if ($env){
+               // d'abord toutes les cles primaires connues
+               $tables_sql = lister_tables_objets_sql();
+               foreach (array_keys($tables_sql) as $table){
+                       $primary = id_table_objet($table);
+                       if (isset($env[$primary])) {
+                               $contexte[$primary] = is_array($env[$primary]) ? $env[$primary] : trim($env[$primary]);
+                       }
+               }
+               // puis cas particuliers et ceux ajoutés par le pipeline
+               $keys = pipeline('gis_modele_parametres_autorises', array("objet", "id_objet","id_secteur","id_parent","media","recherche","mots","pays","code_pays","region","departement","ville","code_postal","adresse"));
+               foreach ($keys as $key){
+                       if (isset($env[$key])) {
+                               $contexte[$key] = is_array($env[$key]) ? $env[$key] : trim($env[$key]);
+                       }
+               }
+       }
+       return $contexte;
+}
+
+/**
+ * Transformer le tableau de kml en tableau d'urls :
+ *   si numerique c'est un id de document
+ *   si chaine c'est une url qu'on rapatrie en local
+ * @param array $kml
+ * @return array
+ */
+function gis_kml_to_urls($kml){
+       if ($kml AND count($kml)){
+               include_spip("inc/filtres_mini");
+               include_spip("inc/distant");
+               foreach($kml as $k=>$v){
+                       if (is_numeric($v)){
+                               $kml[$k] = url_absolue(generer_url_entite($v,"document"));
+                       }
+                       else
+                               $kml[$k] = _DIR_RACINE.copie_locale($kml[$k],"modif");
+               }
+       }
+       return $kml;
+}
+
+/**
+ * Retourne les propriétés JSON de l'icône d'un point
+ * 
+ * @param string $img
+ *     Balise HTML `<img ... />` ou chemin de l'image (qui peut être une URL distante).
+ * @return string
+ *     Les propriétés de l'icône
+**/
+function gis_icon_properties($img=''){
+       $props = $icon = '';
+       
+       if ($img) {
+               if (largeur($img) >= 44)
+                       $icon = extraire_attribut(filtrer('image_graver',filtrer('image_recadre',filtrer('image_passe_partout',$img,32,32),32,32,'center','transparent')),'src');
+               else
+                       $icon = extraire_attribut($img,'src') ? extraire_attribut($img,'src') : $img;
+       }
+       else
+               $icon = find_in_path('images/marker_defaut.png');
+       
+       if ($icon) {
+               $props .= ",\n\"icon\": ". json_encode(url_absolue($icon)).",";
+               list($h,$w) = taille_image($icon);
+               $props .= "\n\"icon_size\": ". json_encode(array($w,$h)).",";
+               $props .= "\n\"icon_anchor\": ". json_encode(array($w/2,$h)).",";
+               $props .= "\n\"popup_anchor\": ". json_encode(array(1,-$h/1.2));
+       }
+       
+       if ($shadow = find_in_path('images/marker_defaut_shadow.png'))
+               $props .= ",\n\"shadow\": ". json_encode(url_absolue($shadow));
+       
+       return $props;
+}
+
+?>
diff --git a/www/plugins/gis/gis_json.html b/www/plugins/gis/gis_json.html
new file mode 100644 (file)
index 0000000..ab7370c
--- /dev/null
@@ -0,0 +1,6 @@
+#HTTP_HEADER{Content-Type: application/json; charset=#CHARSET}
+{"type": "FeatureCollection",
+       "features": [
+               [(#INCLURE{fond=json/gis[_(#ENV{objets})],env})]
+       ]
+}
diff --git a/www/plugins/gis/gis_kml.html b/www/plugins/gis/gis_kml.html
new file mode 100755 (executable)
index 0000000..28e2934
--- /dev/null
@@ -0,0 +1,13 @@
+#HTTP_HEADER{Content-Type: application/vnd.google-earth.kml+xml;charset=#CHARSET}[(#ENV**{dl}
+|?{#HTTP_HEADER{Content-Disposition: attachment; filename=#CONFIG{gis/nom_fichier_kml}|sinon{gis}|concat{.kml}},''})]<?xml 
+version="1.0" encoding="#CHARSET" ?>
+<kml xmlns="http://www.opengis.net/kml/2.2"
+       xmlns:atom="http://www.w3.org/2005/Atom">
+<Document>
+[<name>(#NOM_SITE_SPIP|texte_backend)</name>]
+[<description>(#DESCRIPTIF_SITE_SPIP|supprimer_tags|texte_backend)</description>]
+<BOUCLE_gis(GIS gis_liens){id_gis ?}{id_rubrique ?}{id_article ?}{id_breve ?}{id_document ?}{id_mot ?}{id_auteur ?}{id_syndic ?}{recherche ?}{0, #ENV{limit,500}}>
+       [(#INCLURE{fond=inclure/kml-item,id_gis,objet,id_objet})]
+</BOUCLE_gis>
+</Document>
+</kml>
\ No newline at end of file
diff --git a/www/plugins/gis/gis_options.php b/www/plugins/gis/gis_options.php
new file mode 100755 (executable)
index 0000000..a02cc19
--- /dev/null
@@ -0,0 +1,163 @@
+<?php
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+// pour compat cf http://zone.spip.org/trac/spip-zone/changeset/79911/
+define('_DIR_LIB_GIS',find_in_path('lib/leaflet/'));
+
+$GLOBALS['logo_libelles']['id_gis'] = _T('gis:libelle_logo_gis');
+
+$config = @unserialize($GLOBALS['meta']['gis']);
+
+$GLOBALS['gis_layers'] = array (
+       'openstreetmap_mapnik' => array(
+               'nom' => 'OpenStreetMap',
+               'layer' => 'L.tileLayer.provider("OpenStreetMap")'
+       ),
+       'openstreetmap_blackandwhite' => array(
+               'nom' => 'OpenStreetMap Black and White',
+               'layer' => 'L.tileLayer.provider("OpenStreetMap.BlackAndWhite")'
+       ),
+       'openstreetmap_de' => array(
+               'nom' => 'OpenStreetMap DE',
+               'layer' => 'L.tileLayer.provider("OpenStreetMap.DE")'
+       ),
+       'openstreetmap_hot' => array(
+               'nom' => 'OpenStreetMap H.O.T.',
+               'layer' => 'L.tileLayer.provider("OpenStreetMap.HOT")'
+       ),
+       'google_roadmap' => array(
+               'nom' => 'Google Roadmap',
+               'layer' => 'L.Google("ROADMAP")'
+       ),
+       'google_satellite' => array(
+               'nom' => 'Google Satelitte',
+               'layer' => 'L.Google("SATELLITE")'
+       ),
+       'google_terrain' => array(
+               'nom' => 'Google Terrain',
+               'layer' => 'L.Google("TERRAIN")'
+       ),
+       'bing_aerial' => array(
+               'nom' => 'Bing Aerial',
+               'layer' => 'L.BingLayer("'.$config['api_key_bing'].'")'
+       ),
+       'thunderforest_opencyclemap' => array(
+               'nom' => 'Thunderforest OpenCycleMap',
+               'layer' => 'L.tileLayer.provider("Thunderforest.OpenCycleMap")'
+       ),
+       'thunderforest_transport' => array(
+               'nom' => 'Thunderforest Transport',
+               'layer' => 'L.tileLayer.provider("Thunderforest.Transport")'
+       ),
+       'thunderforest_landscape' => array(
+               'nom' => 'Thunderforest Landscape',
+               'layer' => 'L.tileLayer.provider("Thunderforest.Landscape")'
+       ),
+       'thunderforest_outdoors' => array(
+               'nom' => 'Thunderforest Outdoors',
+               'layer' => 'L.tileLayer.provider("Thunderforest.Outdoors")'
+       ),
+       'openmapsurfer' => array(
+               'nom' => 'OpenMapSurfer',
+               'layer' => 'L.tileLayer.provider("OpenMapSurfer")'
+       ),
+       'openmapsurfer_grayscale' => array(
+               'nom' => 'OpenMapSurfer Grayscale',
+               'layer' => 'L.tileLayer.provider("OpenMapSurfer.Grayscale")'
+       ),
+       'hydda' => array(
+               'nom' => 'Hydda',
+               'layer' => 'L.tileLayer.provider("Hydda")'
+       ),
+       'hydda_base' => array(
+               'nom' => 'Hydda Base',
+               'layer' => 'L.tileLayer.provider("Hydda.Base")'
+       ),
+       'mapquestopen_osm' => array(
+               'nom' => 'Mapquest Open',
+               'layer' => 'L.tileLayer.provider("MapQuestOpen.OSM")'
+       ),
+       'mapquestopen_aerial' => array(
+               'nom' => 'Mapquest Open Aerial',
+               'layer' => 'L.tileLayer.provider("MapQuestOpen.Aerial")'
+       ),
+       'stamen_toner' => array(
+               'nom' => 'Stamen Toner',
+               'layer' => 'L.tileLayer.provider("Stamen.Toner")'
+       ),
+       'stamen_tonerlite' => array(
+               'nom' => 'Stamen Toner Lite',
+               'layer' => 'L.tileLayer.provider("Stamen.TonerLite")'
+       ),
+       'stamen_terrain' => array(
+               'nom' => 'Stamen Terrain',
+               'layer' => 'L.tileLayer.provider("Stamen.Terrain")'
+       ),
+       'stamen_watercolor' => array(
+               'nom' => 'Stamen Watercolor',
+               'layer' => 'L.tileLayer.provider("Stamen.Watercolor")'
+       ),
+       'esri_worldstreetmap' => array(
+               'nom' => 'Esri WorldStreetMap',
+               'layer' => 'L.tileLayer.provider("Esri.WorldStreetMap")'
+       ),
+       'esri_delorme' => array(
+               'nom' => 'Esri DeLorme',
+               'layer' => 'L.tileLayer.provider("Esri.DeLorme")'
+       ),
+       'esri_worldtopomap' => array(
+               'nom' => 'Esri WorldTopoMap',
+               'layer' => 'L.tileLayer.provider("Esri.WorldTopoMap")'
+       ),
+       'esri_worldimagery' => array(
+               'nom' => 'Esri WorldImagery',
+               'layer' => 'L.tileLayer.provider("Esri.WorldImagery")'
+       ),
+       'esri_worldterrain' => array(
+               'nom' => 'Esri WorldTerrain',
+               'layer' => 'L.tileLayer.provider("Esri.WorldTerrain")'
+       ),
+       'esri_worldshadedrelief' => array(
+               'nom' => 'Esri WorldShadedRelief',
+               'layer' => 'L.tileLayer.provider("Esri.WorldShadedRelief")'
+       ),
+       'esri_worldphysical' => array(
+               'nom' => 'Esri WorldPhysical',
+               'layer' => 'L.tileLayer.provider("Esri.WorldPhysical")'
+       ),
+       'esri_oceanbasemap' => array(
+               'nom' => 'Esri OceanBasemap',
+               'layer' => 'L.tileLayer.provider("Esri.OceanBasemap")'
+       ),
+       'esri_natgeoworldmap' => array(
+               'nom' => 'Esri NatGeoWorldMap',
+               'layer' => 'L.tileLayer.provider("Esri.NatGeoWorldMap")'
+       ),
+       'esri_worldgraycanvas' => array(
+               'nom' => 'Esri WorldGrayCanvas',
+               'layer' => 'L.tileLayer.provider("Esri.WorldGrayCanvas")'
+       ),
+       'acetate' => array(
+               'nom' => 'Acetate',
+               'layer' => 'L.tileLayer.provider("Acetate.all")'
+       ),
+       'cartodb_positron' => array(
+               'nom' => 'CartoDB Positron',
+               'layer' => 'L.tileLayer.provider("CartoDB.Positron")'
+       ),
+       'cartodb_positron_base' => array(
+               'nom' => 'CartoDB Positron Base',
+               'layer' => 'L.tileLayer.provider("CartoDB.PositronNoLabels")'
+       ),
+       'cartodb_darkmatter' => array(
+               'nom' => 'CartoDB DarkMatter',
+               'layer' => 'L.tileLayer.provider("CartoDB.DarkMatter")'
+       ),
+       'cartodb_darkmatter_base' => array(
+               'nom' => 'CartoDB DarkMatter Base',
+               'layer' => 'L.tileLayer.provider("CartoDB.DarkMatterNoLabels")'
+       )
+);
+
+?>
\ No newline at end of file
diff --git a/www/plugins/gis/gis_pipelines.php b/www/plugins/gis/gis_pipelines.php
new file mode 100755 (executable)
index 0000000..14fdd0b
--- /dev/null
@@ -0,0 +1,369 @@
+<?php
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+/**
+ * Insertion des css du plugin dans les pages publiques
+ *
+ * @param $flux
+ * @return mixed
+ */
+function gis_insert_head_css($flux){
+       $flux .="\n".'<link rel="stylesheet" href="'. find_in_path('lib/leaflet/dist/leaflet.css') .'" />';
+       $flux .="\n".'<link rel="stylesheet" href="'. sinon(find_in_path('css/leaflet-plugins.css'),find_in_path('lib/leaflet/plugins/leaflet-plugins.css')) .'" />';
+       $flux .="\n".'<link rel="stylesheet" href="'. sinon(find_in_path('css/leaflet.markercluster.css'),find_in_path('lib/leaflet/plugins/leaflet.markercluster.css')) .'" />';
+       return $flux;
+}
+
+/**
+ * Insertion des scripts du plugin dans les pages publiques
+ *
+ * @param $flux
+ * @return mixed
+ */
+function gis_insert_head($flux){
+       
+       // initialisation des valeurs de config
+       $config = @unserialize($GLOBALS['meta']['gis']);
+       if (!isset($config['layers']) || !is_array($config['layers']))
+               $config['layers'] = array('openstreetmap_mapnik');
+       
+       include_spip('gis_fonctions');
+       if (!in_array(gis_layer_defaut(),$config['layers']))
+               $config['layers'][] = gis_layer_defaut();
+       
+       // insertion des scripts pour google si nécessaire
+       if (count(array_intersect(array('google_roadmap', 'google_satellite', 'google_terrain'), $config['layers'])) > 0) {
+               $flux .="\n".'<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false&amp;language='.$GLOBALS['spip_lang'].'"></script>';
+       }
+       
+       return $flux;
+}
+
+/**
+ * Insertion des scripts et css du plugin dans les pages de l'espace privé
+ *
+ * @param $flux
+ * @return mixed
+ */
+function gis_header_prive($flux){
+       $flux .= gis_insert_head_css('');
+       $flux .= gis_insert_head('');
+       return $flux;
+}
+
+/**
+ * Insertion du bloc GIS dans les pages des objets de l'espace privé
+ *
+ * @param $flux
+ * @return mixed
+ */
+function gis_afficher_contenu_objet($flux){
+       if ($objet = $flux['args']['type']
+               and include_spip('inc/config')
+               and in_array(table_objet_sql($objet), lire_config('gis/gis_objets', array()))
+               and ($id = intval($flux['args']['id_objet']))
+               
+       ){
+               $texte = recuperer_fond(
+                       'prive/contenu/gis_objet',
+                       array(
+                               'table_source'=>'gis',
+                               'objet'=>$objet,
+                               'id_objet'=>$id
+                       )
+               );
+               $flux['data'] .= $texte;
+       }
+       
+       return $flux;
+}
+
+/**
+ * Si la geolocalisation des documents est activée dans la config,
+ * création/suppression d'un point à partir des métadonnées du document ajouté (jpg, kml et kmz)
+ *
+ * @param $flux
+ * @return mixed
+ */
+function gis_post_edition($flux){
+       if (is_array($flux) && isset($flux['args']['operation']) && ($flux['args']['operation'] == 'ajouter_document') 
+               AND ($document = sql_fetsel("*","spip_documents","id_document=".intval($flux['args']['id_objet'])))
+               AND (in_array(table_objet_sql('document'), lire_config('gis/gis_objets', array())))
+       ) {
+               if(in_array($document['extension'],array('jpg','kml','kmz'))){
+                       $config = @unserialize($GLOBALS['meta']['gis']);
+                       if(!is_array($config))
+                               $config = array();
+                       include_spip('inc/documents');
+                       $fichier = get_spip_doc($document['fichier']);
+                       $id_document = $document['id_document'];
+               }
+               if ($document['extension'] == 'jpg') {
+                       // on recupere les coords definies dans les exif du document s'il y en a
+                       if (function_exists('exif_read_data') AND $exifs =  @exif_read_data($fichier,'GPS')) {
+                               if(!function_exists('dms_to_dec'))
+                                       include_spip('gis_fonctions');
+                               spip_log("GIS EXIFS : Récuperation des coordonnees du fichier $fichier","gis");
+                               
+                               $LatDeg = explode("/",$exifs["GPSLatitude"][0]);
+                               if(is_numeric($LatDeg[1]) > 0)
+                                       $intLatDeg = $LatDeg[0]/$LatDeg[1];
+
+                               $LatMin = explode("/",$exifs["GPSLatitude"][1]);
+                               if(is_numeric($LatMin[1]) > 0)
+                                       $intLatMin = $LatMin[0]/$LatMin[1];
+
+                               $LatSec = explode("/",$exifs["GPSLatitude"][2]);
+                               if(is_numeric($LatSec[1]) > 0)
+                                       $intLatSec = $LatSec[0]/$LatSec[1];
+
+                               $LongDeg = explode("/",$exifs["GPSLongitude"][0]);
+                               if(is_numeric($LongDeg[1]) > 0)
+                                       $intLongDeg = $LongDeg[0]/$LongDeg[1];
+
+                               $LongMin = explode("/",$exifs["GPSLongitude"][1]);
+                               if(is_numeric($LongMin[1]) > 0)
+                                       $intLongMin = $LongMin[0]/$LongMin[1];
+
+                               $LongSec = explode("/",$exifs["GPSLongitude"][2]);
+                               if(is_numeric($LongSec[1]) > 0)
+                                       $intLongSec = $LongSec[0]/$LongSec[1];
+
+                               // round to 5 = approximately 1 meter accuracy
+                               if(is_numeric($intLatDeg) && is_numeric($intLatMin) && is_numeric($intLatSec))
+                                       $latitude = round(dms_to_dec($exifs["GPSLatitudeRef"],
+                                               $intLatDeg,$intLatMin,$intLatSec),5);
+
+                               if(is_numeric($intLongDeg) && is_numeric($intLongMin) && is_numeric($intLongSec))
+                                       $longitude =  round(dms_to_dec($exifs["GPSLongitudeRef"],
+                                               $intLongDeg,$intLongMin,$intLongSec), 5);
+                               if($config['geocoder'] == 'on'){
+                                       include_spip('inc/xml');
+                                       $url_geocoder = 'http://maps.googleapis.com/maps/api/geocode/xml?latlng='.urlencode($latitude).','.urlencode($longitude).'&sensor=true';
+                                       $geocoder = spip_xml_load($url_geocoder);
+                                       spip_xml_match_nodes(',result,',$geocoder,$matches_adress);
+                                       if(is_array($matches_adress['result'])){
+                                               foreach($matches_adress['result'] as $component){
+                                                       if(in_array('country',$component['type'])){
+                                                               $pays = $component['address_component'][0]['long_name'][0];
+                                                               $code_pays = $component['address_component'][0]['short_name'][0];
+                                                       }
+                                                       if(in_array('administrative_area_level_1',$component['type'])){
+                                                               $region = $component['address_component'][0]['long_name'][0];
+                                                       }
+                                                       if(in_array('administrative_area_level_2',$component['type'])){
+                                                               $departement = $component['address_component'][0]['long_name'][0];
+                                                       }
+                                                       if(in_array('locality',$component['type'])){
+                                                               $ville = $component['address_component'][0]['long_name'][0];
+                                                       }
+                                                       if(in_array('postal_code',$component['type'])){
+                                                               $code_postal = $component['address_component'][0]['long_name'][0];
+                                                       }
+                                                       if(in_array('route',$component['type'])){
+                                                               $adresse = $component['address_component'][0]['long_name'][0];
+                                                       }
+                                               }
+                                       }
+                               }
+                       }else if(file_exists($fichier)){
+                               include_spip("inc/iptc");
+
+                               $er = new class_IPTC($fichier);
+                               $iptc = $er->fct_lireIPTC();
+                               $codesiptc = $er->h_codesIptc;
+                               $string_recherche = '';
+                               
+                               if($iptc['city']){
+                                       $string_recherche .= $iptc['city'].', ';
+                               }
+                               if($iptc['provinceState']){
+                                       $string_recherche .= $iptc['provinceState'].', ';
+                               }
+                               if($iptc['country']){
+                                       $string_recherche .= $iptc['country'];
+                               }
+                               if(strlen($string_recherche)){
+                                       include_spip('inc/xml');
+                                       $url_geocoder = 'http://maps.googleapis.com/maps/api/geocode/xml?address='.urlencode($string_recherche).'&sensor=true';
+                                       $geocoder = spip_xml_load($url_geocoder);
+                                       if(is_array($geocoder)){
+                                               spip_xml_match_nodes(',location,',$geocoder,$matches);
+                                               $latitude = $matches['location']['0']['lat']['0'];
+                                               $longitude = $matches['location']['0']['lng']['0'];
+                                               if($config['adresse'] == 'on'){
+                                                       spip_xml_match_nodes(',address_component,',$geocoder,$matches_adress);
+                                                       if(is_array($matches_adress['address_component'])){
+                                                               foreach($matches_adress['address_component'] as $component){
+                                                                       if(in_array('country',$component['type'])){
+                                                                               $pays = $component['long_name'][0];
+                                                                               $code_pays = $component['short_name'][0];
+                                                                       }
+                                                                       if(in_array('administrative_area_level_1',$component['type'])){
+                                                                               $region = $component['long_name'][0];
+                                                                       }
+                                                                       if(in_array('administrative_area_level_2',$component['type'])){
+                                                                               $departement = $component['long_name'][0];
+                                                                       }
+                                                                       if(in_array('locality',$component['type'])){
+                                                                               $ville = $component['long_name'][0];
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+                       if(is_numeric($latitude) && is_numeric($longitude)){
+                               $c = array(
+                                       'titre' => basename($fichier),
+                                       'lat'=> $latitude,
+                                       'lon' => $longitude,
+                                       'zoom' => $config['zoom'] ? $config['zoom'] :'4',
+                                       'adresse' => $adresse,
+                                       'code_postal' => $code_postal,
+                                       'ville' => $ville,
+                                       'region' => $region,
+                                       'departement' => $departement,
+                                       'pays' => $pays,
+                                       'code_pays' => $code_pays
+                               );
+                               
+                               if (defined('_DIR_PLUGIN_GISGEOM')) {
+                                       $geojson = '{"type":"Point","coordinates":['.$longitude.','.$latitude.']}';
+                                       set_request('geojson',$geojson);
+                               }
+                               
+                               include_spip('action/editer_gis');
+       
+                               if($id_gis = sql_getfetsel("G.id_gis","spip_gis AS G LEFT  JOIN spip_gis_liens AS T ON T.id_gis=G.id_gis ","T.id_objet=" . intval($id_document) . " AND T.objet='document'")){
+                                       // Des coordonnées sont déjà définies pour ce document => on les update
+                                       revisions_gis($id_gis,$c);
+                                       spip_log("GIS EXIFS : Update des coordonnées depuis EXIFS pour le document $id_document => id_gis = $id_gis","gis");
+                               }
+                               else{
+                                       // Aucune coordonnée n'est définie pour ce document  => on les crées
+                                       $id_gis = insert_gis();
+                                       revisions_gis($id_gis,$c);
+                                       lier_gis($id_gis, 'document', $id_document);
+                                       spip_log("GIS EXIFS : Création des coordonnées depuis EXIFS pour le document $id_document => id_gis = $id_gis","gis");
+                               }
+                       }
+               }elseif(in_array($document['extension'],array('kml','kmz','gpx'))){
+                       $recuperer_info = charger_fonction('kml_infos','inc');
+                       $infos = $recuperer_info($document['id_document']);
+                       if($infos){
+                               if(is_numeric($latitude = $infos['latitude']) && is_numeric($longitude = $infos['longitude'])){
+                                       $c = array(
+                                               'titre' => $infos['titre'] ? $infos['titre'] : basename($fichier),
+                                               'descriptif' => $infos['descriptif'],
+                                               'lat'=> $latitude,
+                                               'lon' => $longitude,
+                                               'zoom' => $config['zoom'] ? $config['zoom'] :'4'
+                                       );
+                       
+                                       include_spip('action/editer_gis');
+               
+                                       if($id_gis = sql_getfetsel("G.id_gis","spip_gis AS G LEFT  JOIN spip_gis_liens AS T ON T.id_gis=G.id_gis ","T.id_objet=" . intval($id_document) . " AND T.objet='document'")){
+                                               // Des coordonnées sont déjà définies pour ce document => on les update
+                                               revisions_gis($id_gis,$c);
+                                               spip_log("GIS EXIFS : Update des coordonnées depuis EXIFS pour le document $id_document => id_gis = $id_gis","gis");
+                                       }
+                                       else{
+                                               // Aucune coordonnée n'est définie pour ce document  => on les crées
+                                               $id_gis = insert_gis();
+                                               revisions_gis($id_gis,$c);
+                                               lier_gis($id_gis, 'document', $id_document);
+                                               spip_log("GIS EXIFS : Création des coordonnées depuis EXIFS pour le document $id_document => id_gis = $id_gis","gis");
+                                       }
+                               }
+                               unset($infos['longitude']);
+                               unset($infos['latitude']);
+                               if(count($infos) > 0){
+                                       include_spip('action/editer_document');
+                                       document_modifier($id_document, $infos);
+                               }
+                       }
+               }
+       }
+       if (is_array($flux) && isset($flux['args']['operation']) && ($flux['args']['operation'] == 'supprimer_document') 
+               AND ($id_document = intval($flux['args']['id_objet'])
+               AND ($id_gis = sql_getfetsel("G.id_gis","spip_gis AS G LEFT  JOIN spip_gis_liens AS T ON T.id_gis=G.id_gis ","T.id_objet=" . intval($id_document) . " AND T.objet='document'")))
+       ) {
+               include_spip('action/editer_gis');
+               supprimer_gis($id_gis);
+               spip_log("GIS EXIFS : Suppression des coordonnées pour le document $id_document => id_gis = $id_gis","gis");
+       }
+       
+       return $flux;
+}
+
+
+/**
+ * Optimiser la base de données en supprimant les liens orphelins
+ *
+ * @param array $flux
+ * @return array
+ */
+function gis_optimiser_base_disparus($flux){
+
+       include_spip('action/editer_liens');
+       // optimiser les liens morts :
+       // entre gis vers des objets effaces
+       // depuis des gis effaces
+       $flux['data'] += objet_optimiser_liens(array('gis'=>'*'),'*');
+
+       return $flux;
+}
+
+function gis_saisies_autonomes($flux){
+       $flux[] = 'carte';
+       return $flux;
+}
+
+/**
+ * Insertion dans le pipeline xmlrpc_methodes (xmlrpc)
+ * Ajout de méthodes xml-rpc spécifiques à GIS
+ * 
+ * @param array $flux : un array des methodes déjà présentes, fonctionnant sous la forme :
+ * -* clé = nom de la méthode;
+ * -* valeur = le nom de la fonction à appeler;
+ * @return array $flux : l'array complété avec nos nouvelles méthodes 
+ */
+function gis_xmlrpc_methodes($flux){
+       $flux['spip.liste_gis'] = 'spip_liste_gis';
+       $flux['spip.lire_gis'] = 'spip_lire_gis';
+       return $flux;
+}
+
+/**
+ * Insertion dans le pipeline xmlrpc_server_class (xmlrpc)
+ * Ajout de fonctions spécifiques utilisés par le serveur xml-rpc 
+ */
+function gis_xmlrpc_server_class($flux){
+       include_spip('inc/gis_xmlrpc');
+       return $flux;
+}
+
+/**
+ * Insertion dans le traitement du formulaire de configuration
+ * 
+ * Purger le répertoire js si on a une carte google dans les layers pour recalculer le js statique
+ * Peut être à améliorer
+ * 
+ * @param array $flux
+ *             Le contexte du pipeline
+ * @return array $flux
+ */
+function gis_formulaire_traiter($flux){
+       if($flux['args']['form'] == 'configurer_gis'){
+               if (count(array_intersect(array('google_roadmap', 'google_satellite', 'google_terrain'), _request('layers'))) > 0){
+                       include_spip('inc/invalideur');
+                       purger_repertoire(_DIR_VAR.'cache-js');
+                       suivre_invalideur(1);
+               }
+       }
+       return $flux;
+}
+?>
diff --git a/www/plugins/gis/icones_barre/gis.png b/www/plugins/gis/icones_barre/gis.png
new file mode 100644 (file)
index 0000000..b0e5a4c
Binary files /dev/null and b/www/plugins/gis/icones_barre/gis.png differ
diff --git a/www/plugins/gis/images/gis-16.png b/www/plugins/gis/images/gis-16.png
new file mode 100644 (file)
index 0000000..b0e5a4c
Binary files /dev/null and b/www/plugins/gis/images/gis-16.png differ
diff --git a/www/plugins/gis/images/gis-24.png b/www/plugins/gis/images/gis-24.png
new file mode 100755 (executable)
index 0000000..6cb9f54
Binary files /dev/null and b/www/plugins/gis/images/gis-24.png differ
diff --git a/www/plugins/gis/images/gis.png b/www/plugins/gis/images/gis.png
new file mode 100755 (executable)
index 0000000..8f867fc
Binary files /dev/null and b/www/plugins/gis/images/gis.png differ
diff --git a/www/plugins/gis/inc/gis_xmlrpc.php b/www/plugins/gis/inc/gis_xmlrpc.php
new file mode 100644 (file)
index 0000000..e2867fc
--- /dev/null
@@ -0,0 +1,132 @@
+<?php
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+/**
+ * Récupère la liste des points géolocalisés
+ * 
+ * Arguments possibles :
+ * -* login string 
+ * -* pass string
+ * -* objet string : le type d'objets liés
+ * -* id_objet int : l'identifiant numérique de l'objet lié
+ * -* where array : conditions à ajouter dans la clause where du select
+ * -* tri array : les éléments de tri
+ * -** Si 'distance' dans le tri
+ * -*** lat float : la latitude à partir de laquelle chercher
+ * -*** lon float : la longitude à partir de laquelle chercher
+ * -* limite int : le nombre d'éléments maximum à retourner
+ */
+function spip_liste_gis($args) {
+       global $spip_xmlrpc_serveur;
+       
+       if(!$spip_xmlrpc_serveur)
+               return false;
+       
+       $objet = 'gis';
+       
+       $what[] = 'gis.id_gis';
+       $from = 'spip_gis as gis LEFT JOIN spip_gis_liens as lien ON gis.id_gis=lien.id_gis';
+       $where = is_array($args['where']) ? $args['where'] : array();
+       $order = is_array($args['tri']) ? $args['tri'] : array();
+       if((intval($args['id_objet']) > 0) && $args['objet']){
+               $where[] = 'lien.id_objet='.intval($args['id_objet']).' AND lien.objet='.sql_quote($args['objet']);
+       }
+       
+       if(in_array('distance',$order) OR in_array('!distance',$order)){
+               $distance = true;
+               $lat = $args['lat'];
+               $lon = $args['lon'];
+               if(!is_numeric($lon) OR !is_numeric($lat)){
+                       $erreur = _T('gis:erreur_xmlrpc_lat_lon');
+                       return new IXR_Error(-32601, attribut_html($erreur));
+               }else{
+                       $what[] = "(6371 * acos( cos( radians(\"$lat\") ) * cos( radians( gis.lat ) ) * cos( radians( gis.lon ) - radians(\"$lon\") ) + sin( radians(\"$lat\") ) * sin( radians( gis.lat ) ) ) ) AS distance";
+               }
+       }
+       
+       /**
+        * Une recherche
+        */     
+       if(is_string($args['recherche']) AND strlen($args['recherche']) > 3){
+               $prepare_recherche = charger_fonction('prepare_recherche', 'inc');
+               list($rech_select, $rech_where) = $prepare_recherche($args['recherche'], $objet.'s', $where);
+               $what[] = $rech_select;
+               $from .= ' INNER JOIN spip_resultats AS resultats ON ( resultats.id = gis.id_gis ) ';
+               $where[] = 'resultats.'.$rech_where;
+       }
+       
+       $points_struct = array();
+
+       if($points = sql_select($what,$from,$where,'',$order,$args['limite'])){
+               while($point = sql_fetch($points)){
+                       $struct=array();
+                       $args['id_gis'] = $point['id_gis'];
+                       /**
+                        * On utilise la fonction geodiv_lire_media pour éviter de dupliquer trop de code
+                        */
+                       $struct = spip_lire_gis($args);
+                       if($distance)
+                               $struct['distance'] = $point['distance'];
+                       $points_struct[] = $struct;
+               }
+       }
+       return $points_struct;
+}
+
+/**
+ * Récupère le contenu d'un point géolocalisé
+ * 
+ * Arguments possibles :
+ * -* login
+ * -* pass
+ * -* id_gis (Obligatoire)
+ * -* lat : si disponible avec lon, on ajoute la distance dans les infos
+ * -* lon : si disponible avec lat, on ajoute la distance dans les infos
+ */
+function spip_lire_gis($args){
+       global $spip_xmlrpc_serveur;
+       
+       if(!$spip_xmlrpc_serveur)
+               return false;
+       
+       if(!intval($args['id_gis']) > 0){
+               $erreur = _T('xmlrpc:erreur_identifiant',array('objet'=>'gis'));
+               return new IXR_Error(-32601, attribut_html($erreur));
+       }
+       
+       $args_gis = array('objet'=>'gis','id_objet'=>$args['id_gis']);
+       $res = $spip_xmlrpc_serveur->read($args_gis);
+       if(!$res)
+               return $spip_xmlrpc_serveur->error;
+       
+       if(isset($args['lat']) && is_numeric($args['lat']) && isset($args['lon']) && is_numeric($args['lon'])){
+               $lat = $args['lat'];
+               $lon = $args['lon'];
+               $what[] = 'gis.id_gis';
+               $what[] = "(6371 * acos( cos( radians(\"$lat\") ) * cos( radians( gis.lat ) ) * cos( radians( gis.lon ) - radians(\"$lon\") ) + sin( radians(\"$lat\") ) * sin( radians( gis.lat ) ) ) ) AS distance";
+               $distance = sql_fetsel($what,"spip_gis AS gis","gis.id_gis=".intval($args['id_gis']));
+               $res['result'][0]['distance'] = $distance['distance'];
+       }
+       
+       if(autoriser('modifier','gis',$args['id_gis'],$GLOBALS['visiteur_session']))
+               $res['result'][0]['modifiable'] = 1;
+       else
+               $res['result'][0]['modifiable'] = 0;
+       $logo = quete_logo('id_gis','on', $res['result'][0]['id_gis'], '', false);
+       if(is_array($logo))
+               $res['result'][0]['logo'] = url_absolue($logo[0]);
+
+       if(defined('_DIR_PLUGIN_GISGEOM')){
+               if(isset($res['result'][0]['geo'])){
+                       include_spip('gisgeom_fonctions');
+                       $res['result'][0]['geo'] = wkt_to_json($wkt);
+               }
+       }
+
+       $gis_struct = $res['result'][0];
+       $gis_struct = array_map('texte_backend',$gis_struct);
+       return $gis_struct;
+}
+
+?>
\ No newline at end of file
diff --git a/www/plugins/gis/inc/iptc.php b/www/plugins/gis/inc/iptc.php
new file mode 100644 (file)
index 0000000..9eff943
--- /dev/null
@@ -0,0 +1,328 @@
+<?php
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+//#############################################################################################
+//##############################################################################################
+
+/* INFOS SUR LE FICHIER 
+
+Nom : iptc.class.php 
+Rôle : contient la classe permettant de gérer les IPTC d'un fichier image 
+Développeur(s) : Arica Alex, Thies C. Arntzen 
+
+FIN INFOS SUR LE FICHIER 
+
+
+INFOS SUR LA CLASSE 'class_iptc' 
+
+REFERENCES : Développée le 04 Octobre 02 par Arica Alex avec l'aide de Thies C. Arntzen 
+
+ROLE : permet de manipuler les iptc d'une image 
+
+VARIABLES : 
+- $h_codesIptc 
+- $h_cheminFichier 
+- $h_iptcData 
+
+METHODES : 
+- fct_lireIPTC 
+- fct_ecrireIPTC 
+- fct_iptcMaketag 
+
+FIN INFOS SUR LA CLASSE 
+
+*/ 
+
+
+class class_IPTC 
+{ 
+
+
+
+/* VARIABLES statics */ 
+
+var $h_codesIptc; /* $h_codesIptc : (tableau associatif) contient les codes des champs IPTC associés à un libellé */ 
+var $h_cheminImg; /* $h_cheminImg : (chaine) contient le chemin complet du fichier d'image */ 
+var $h_iptcData; /* $h_iptcData : (chaine) contient les données encodées de l'iptc de l'image */ 
+
+/* FIN VARIABLES statics 
+
+
+
+
+
+
+
+
+
+------------------------------------------------------------------------------------------------------- 
+
+
+
+
+
+
+
+
+INFOS SUR LA FONCTION 
+
+ROLE : constructeur 
+FONCTION : class_IPTC($cheminImg) 
+DESCRIPTION DES PARAMETRES : 
+- $cheminImg = (chaine) le chemin complet du fichier d'image à traiter 
+
+FIN INFOS SUR LA FONCTION */ 
+
+
+function class_IPTC($cheminImg) 
+{ 
+
+// Inititalisations 
+
+// Les valeurs IPTC pouvant être manipulées 
+$this -> h_codesIptc = array("005" => "objectName", 
+"007" => "editStatus", 
+"010" => "priority", 
+"015" => "category", 
+"020" => "supplementalCategory", 
+"022" => "fixtureIdentifier", 
+"025" => "keywords", 
+"030" => "releaseDate", 
+"035" => "releaseTime", 
+"040" => "specialInstructions", 
+"045" => "referenceService", 
+"047" => "referenceDate", 
+"050" => "referenceNumber", 
+"055" => "createdDate", 
+"060" => "createdTime", 
+"065" => "originatingProgram", 
+"070" => "programVersion", 
+"075" => "objectCycle", 
+"080" => "byline", 
+"085" => "bylineTitle", 
+"090" => "city", 
+"095" => "provinceState", 
+"100" => "countryCode", 
+"101" => "country", 
+"103" => "originalTransmissionReference", 
+"105" => "headline", 
+"110" => "credit", 
+"115" => "source", 
+"116" => "copyright", 
+"120" => "caption", 
+"121" => "localCaption", 
+"122" => "captionWriter"); 
+
+
+// On enregistre le chemin de l'image à traiter 
+$this -> h_cheminImg = $cheminImg; 
+
+
+// On extrait les données encodées de l'iptc 
+// getimagesize($this -> h_cheminImg, &$info); //avant,marche pas sinon
+getimagesize($this -> h_cheminImg, $info); //marche sans le & 
+$this -> h_iptcData = $info["APP13"]; 
+
+} 
+
+/* FIN FONCTION class_IPTC(); 
+
+
+
+
+
+
+
+
+
+------------------------------------------------------------------------------------------------------- 
+
+
+
+
+
+
+
+
+INFOS SUR LA FONCTION 
+
+ROLE : lit les IPTC d'une image et les renvoie dans un tableau associatif 
+FONCTION : fct_lireIPTC() 
+TYPE RETOURNE : chaine sous forme de tableau associatif 
+
+FIN INFOS SUR LA FONCTION */ 
+
+function fct_lireIPTC() 
+{ 
+       $tblIPTC = iptcparse($this -> h_iptcData); 
+       
+       while( (is_array($tblIPTC)) && (list($codeIPTC, $valeurIPTC) = each($tblIPTC)) ) 
+       { 
+               $codeIPTC = str_replace("2#", "", $codeIPTC); 
+               
+               if( ($codeIPTC != "000") && ($codeIPTC != "140")  && $this->h_codesIptc["$codeIPTC"]) 
+               { 
+                       while(list($index, ) = each($valeurIPTC)) 
+                       { 
+                               if ($this->h_codesIptc["$codeIPTC"]) $codeIPTC = $this->h_codesIptc["$codeIPTC"];
+                               $lesIptc[$codeIPTC] .= $valeurIPTC[$index].$retourLigne; 
+                               $retourLigne = "\n"; 
+                       } 
+               } 
+       } 
+       
+       if(is_array($lesIptc)) return $lesIptc; 
+       else return false; 
+} 
+
+/* FIN FONCTION fct_lireIPTC(); 
+
+
+
+
+
+
+
+
+------------------------------------------------------------------------------------------------------- 
+
+
+
+
+
+
+
+
+INFOS SUR LA FONCTION 
+
+ROLE : écrit des IPTC dans le fichier image 
+FONCTION : fct_ecrireIPTC() 
+DESCRIPTION DES PARAMETRES : 
+- $tblIPTC_util = (tableau associatif) contient les codes des champs IPTC à modifier associés leur valeur 
+- $cheminImgAModifier = (chaine) stocke le chemin de l'image dont l'IPTC est à modifier ; s'il est null 
+le chemin sera celui contenu dans '$this -> h_cheminImg' 
+TYPE RETOURNE : booléen 
+
+FIN INFOS SUR LA FONCTION */ 
+
+function fct_ecrireIPTC($tblIPTC_util, $cheminImgAModifier = "") 
+{ 
+
+// La tableau devant contenir des IPTC est vide ou n'est pas un tableau associatif 
+if( (empty($tblIPTC_util)) || (!is_array($tblIPTC_util)) ) return false; 
+
+
+// Si le chemin de l'image à modifier est vide alors on lui spécifie le chemin par défaut 
+if(empty($cheminImgAModifier)) $cheminImgAModifier = $this -> h_cheminImg; 
+
+
+// On récupère l'IPTC du fichier image courant 
+$tblIPTC_old = iptcparse($this -> h_iptcData); 
+
+
+// On prélève le tableau contenant les codes et les valeurs des IPTC de la photo 
+while(list($codeIPTC, $codeLibIPTC) = each($this -> h_codesIptc)) 
+{ 
+
+// On teste si les données originelles correspondant au code en cours sont présents 
+if (is_array($tblIPTC_old["2#".$codeIPTC])) $valIPTC_new = $tblIPTC_old["2#".$codeIPTC]; 
+else $valIPTC_new = array(); 
+
+
+// On remplace les valeurs des IPTC demandées 
+if (is_array($tblIPTC_util[$codeIPTC])) 
+{ 
+if (count($tblIPTC_util[$codeIPTC])) $valIPTC_new = $tblIPTC_util[$codeIPTC]; 
+
+}else{ 
+
+$val = trim(strval($tblIPTC_util[$codeIPTC])); 
+if (strlen($val)) $valIPTC_new[0] = $val; 
+} 
+
+
+// On crée un nouveau iptcData à partir de '$tblIPTC_new' qui contient le code et la valeur de l'IPTC 
+foreach($valIPTC_new as $val) 
+{ 
+$iptcData_new .= $this -> fct_iptcMaketag(2, $codeIPTC, $val); 
+} 
+
+} 
+
+
+/* A partir du nouveau iptcData contenu dans '$iptcData_new' on crée grâce à la fonction 'iptcembed()' 
+le contenu binaire du fichier image avec le nouveau IPTC inclu */ 
+$contenuImage = iptcembed($iptcData_new, $this -> h_cheminImg); 
+
+
+// Ecriture dans le fichier image 
+$idFichier = fopen($cheminImgAModifier, "wb"); 
+fwrite($idFichier, $contenuImage); 
+fclose($idFichier); 
+
+
+return true; 
+
+} 
+
+/* FIN FONCTION fct_ecrireIPTC(); 
+
+
+
+
+
+
+
+
+------------------------------------------------------------------------------------------------------- 
+
+
+
+
+
+
+
+
+INFOS SUR LA FONCTION 
+
+ROLE : permet de transformer une valeur de d'IPTC (code + valeur) en iptcData 
+AUTEUR : Thies C. Arntzen 
+FONCTION : fct_iptcMaketag($rec, $dat, $val) 
+DESCRIPTION DES PARAMETRES : 
+- $rec = (entier) toujours à mettre à 2 
+- $dat = (chaine) le code de l'IPTC (de type '110' et non '2#110') 
+- $val = (chaine) la valeur de l'IPTC 
+TYPE RETOURNE : booléen 
+
+FIN INFOS SUR LA FONCTION */ 
+
+function fct_iptcMaketag($rec, $dat, $val) 
+{ 
+$len = strlen($val); 
+if ($len < 0x8000) 
+return chr(0x1c).chr($rec).chr($dat). 
+chr($len >> 8). 
+chr($len & 0xff). 
+$val; 
+else 
+return chr(0x1c).chr($rec).chr($dat). 
+chr(0x80).chr(0x04). 
+chr(($len >> 24) & 0xff). 
+chr(($len >> 16) & 0xff). 
+chr(($len >> 8 ) & 0xff). 
+chr(($len ) & 0xff). 
+$val; 
+} 
+
+// FIN FONCTION fct_iptcMaketag(); 
+
+
+
+
+
+} 
+
+/* Fin class_IPTC */
+
+?>
\ No newline at end of file
diff --git a/www/plugins/gis/inc/kml_infos.php b/www/plugins/gis/inc/kml_infos.php
new file mode 100644 (file)
index 0000000..484c03e
--- /dev/null
@@ -0,0 +1,187 @@
+<?php
+/**
+ * Plugin GIS
+ * Récupération de données dans les fichiers kml permettant de :
+ * -* récupérer latitude et longitude d'un point correspondant centré sur la moyenne des points ou polygones du kml
+ * -* récupérer un titre
+ * -* récupérer un descriptif
+ */
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+function inc_kml_infos($id_document){
+       if(!intval($id_document))
+               return false;
+       include_spip('inc/documents');
+       $document = sql_fetsel("*", "spip_documents","id_document=".intval($id_document));
+       $chemin = $document['fichier'];
+       $chemin = get_spip_doc($chemin);
+       $extension = $document['extension'];
+       
+       if(in_array($extension,array('kml','kmz'))){
+               $supprimer_chemin = false;
+               /**
+                * Si on est dans un kmz (kml + autres fichiers compressés en zip),
+                * On dézip pour trouver le kml
+                */
+               if($extension == 'kmz'){
+                       include_spip('inc/pclzip');
+                       $zip = new PclZip($chemin);
+                       $list = $zip->listContent();
+                       foreach($list as $fichier => $info_fichier){
+                               if(substr(basename($info_fichier['filename']),-3) == 'kml'){
+                                       $zip->extractByIndex($info_fichier['index'],_DIR_TMP);
+                                       $chemin = _DIR_TMP.$info_fichier['filename'];
+                                       $supprimer_chemin = true;
+                                       break;
+                               }
+                       }
+               }
+               include_spip('inc/xml');
+               $ret = lire_fichier($chemin,$donnees);
+               $arbre = spip_xml_parse($donnees);
+               spip_xml_match_nodes(",^Document,",$arbre, $documents);
+               foreach($documents as $document => $info){
+                       $infos['titre'] = $info[0]['name'][0];
+                       $infos['descriptif'] = $info[0]['description'][0];
+                       $infos['longitude'] = $info[0]['LookAt'][0]['longitude'][0] ? $info[0]['LookAt'][0]['longitude'][0] : false;
+                       $infos['latitude'] = $info[0]['LookAt'][0]['latitude'][0] ? $info[0]['LookAt'][0]['latitude'][0] : false;
+               }
+               
+               /**
+                * Si on n'a pas de longitude ou de latitude, 
+                * on essaie de faire une moyenne des placemarks
+                */
+               if(!$infos['longitude'] OR !$infos['latitude']){
+                       spip_xml_match_nodes(",^Placemark,",$arbre, $placemarks);
+                       $latitude = 0;
+                       $longitude = 0;
+                       $compte = 0;
+                       foreach($placemarks as $places){
+                               foreach($places as $placemark => $lieu){
+                                       if($compte > 500)
+                                                       break;  
+                                       if($lieu['LookAt'][0]['longitude'][0] && $latitude + $lieu['LookAt'][0]['latitude'][0]){
+                                               if($compte > 500)
+                                                       break;  
+                                               $latitude = $latitude + $lieu['LookAt'][0]['latitude'][0];
+                                               $longitude = $longitude + $lieu['LookAt'][0]['longitude'][0];
+                                               $compte++;
+                                       }else if($lieu['Point'][0]['coordinates'][0]){
+                                               if($compte > 500)
+                                                       break;
+                                               $coordinates = explode(',',$lieu['Point'][0]['coordinates'][0]);
+                                               $latitude = $latitude + trim($coordinates[1]);
+                                               $longitude = $longitude + trim($coordinates[0]);
+                                               $compte++;
+                                       }else if($lieu['Polygon'][0]['outerBoundaryIs'][0]['LinearRing'][0]['coordinates'][0]){
+                                               if($compte > 500)
+                                                       break;
+                                               $coordinates = explode(' ',trim($lieu['Polygon'][0]['outerBoundaryIs'][0]['LinearRing'][0]['coordinates'][0]));
+                                               foreach($coordinates as $coordinate){
+                                                       if($compte > 500)
+                                                               break;
+                                                       $coordinate = explode(',',$coordinate);
+                                                       $latitude = $latitude + trim($coordinate[1]);
+                                                       $longitude = $longitude + trim($coordinate[0]);
+                                                       $compte++;
+                                               }
+                                       }
+                               }
+                       }
+                       if(($latitude != 0) && ($longitude != 0)){
+                               $infos['latitude'] = $latitude / $compte;
+                               $infos['longitude'] = $longitude / $compte; 
+                       }
+               }
+               
+               /**
+                * Si pas de titre ou si le titre est égal au nom de fichier ou contient kml ou kmz :
+                * -* on regarde s'il n'y a qu'un seul Folder et on récupère son nom;
+                * -* on regarde s'il n'y a qu'un seul Placemark et on récupère son nom;
+                */
+               if(!$infos['titre'] OR ($infos['titre'] == basename($chemin)) OR (preg_match(',\.km.,',$infos['titre']) > 0)){
+                       spip_xml_match_nodes(",^Folder,",$arbre, $folders);
+                       if(count($folders['Folder']) == 1){
+                               foreach($folders['Folder'] as $folder => $dossier){
+                                       if($dossier['name'][0])
+                                               $infos['titre'] = $dossier['name'][0];
+                                       if(!$infos['descriptif'] && $dossier['description'][0])
+                                               $infos['descriptif'] = $dossier['description'][0];
+                               }
+                       }else{
+                               if(!is_array($placemarks)){
+                                       spip_xml_match_nodes(",^Placemark,",$arbre, $placemarks);
+                               }
+                               if(count($placemarks) == 1){
+                                       foreach($placemarks as $places){
+                                               if(count($places) == 1){
+                                                       foreach($places as $placemark => $lieu){
+                                                               if($lieu['name'][0])
+                                                                       $infos['titre'] = $lieu['name'][0];
+                                                               if(!$infos['descriptif'] && $lieu['description'][0])
+                                                                       $infos['descriptif'] = $lieu['description'][0];
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }else if(in_array($extension,array('gpx'))){
+               $supprimer_chemin = false;
+               include_spip('inc/xml');
+               $ret = lire_fichier($chemin,$donnees);
+               $arbre = spip_xml_parse($donnees);
+               spip_xml_match_nodes(",^metadata,",$arbre, $metadatas);
+               foreach($metadatas as $metadata => $info){
+                       $infos['titre'] = $info[0]['name'][0];
+                       //$infos['date'] =  $info[0]['time'][0];
+                       $infos['descriptif'] = $info[0]['description'][0];
+                       foreach($info[0] as $meta => $data){
+                               if(preg_match(',^bounds ,',$meta)){
+                                       $meta = '<'.$meta.'>';
+                                       $maxlat = extraire_attribut($meta,'maxlat');
+                                       $minlat = extraire_attribut($meta,'minlat');
+                                       $maxlon = extraire_attribut($meta,'maxlon');
+                                       $minlon = extraire_attribut($meta,'minlon');
+                                       if($maxlat && $minlat)
+                                               $infos['latitude'] = (($maxlat+$minlat)/2);
+                                       if($maxlon && $minlon)
+                                               $infos['longitude'] = (($maxlon+$minlon)/2);
+                               }
+                       }
+               }
+               /**
+                * Si on n'a pas de longitude ou de latitude, 
+                * on essaie de faire une moyenne des placemarks
+                */
+               if(!$infos['longitude'] OR !$infos['latitude']){
+                       spip_xml_match_nodes(",^trkpt,",$arbre, $trackpoints);
+                       $latitude = 0;
+                       $longitude = 0;
+                       $compte = 0;
+                       foreach($trackpoints as $places => $place){
+                               foreach($place as $placemark => $lieu){
+                                       if($compte > 10)
+                                                       break;
+                               }
+                       }
+                       if(($latitude != 0) && ($longitude != 0)){
+                               $infos['latitude'] = $latitude / $compte;
+                               $infos['longitude'] = $longitude / $compte; 
+                       }
+               }
+       }else
+               return false;
+       
+       if(isset($infos['titre']))
+               $infos['titre'] = preg_replace('/<!\[cdata\[(.*?)\]\]>/is', '$1',$infos['titre']);
+       if(isset($infos['descriptif']))
+               $infos['descriptif'] = preg_replace('/<!\[cdata\[(.*?)\]\]>/is', '$1', $infos['descriptif']);
+               
+       if($supprimer_chemin){
+               supprimer_fichier($chemin);
+       }
+               
+       return $infos;
+}
+?>
\ No newline at end of file
diff --git a/www/plugins/gis/inclure/download_gpx.html b/www/plugins/gis/inclure/download_gpx.html
new file mode 100644 (file)
index 0000000..8191d49
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="#CHARSET" standalone="no" ?>
+<gpx version="1.1" creator="SPIP"
+       xmlns="http://www.topografix.com/GPX/1/1"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
+       [(#INCLURE{fond=inclure/gpx-item,id_gis})]
+</gpx>
\ No newline at end of file
diff --git a/www/plugins/gis/inclure/download_kml.html b/www/plugins/gis/inclure/download_kml.html
new file mode 100644 (file)
index 0000000..87d6aff
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="#CHARSET" ?>
+<kml xmlns="http://www.opengis.net/kml/2.2"
+       xmlns:atom="http://www.w3.org/2005/Atom">
+<Document>
+[<name>(#NOM_SITE_SPIP|texte_backend)</name>]
+[<description>(#DESCRIPTIF_SITE_SPIP|supprimer_tags|texte_backend)</description>]
+       [(#INCLURE{fond=inclure/kml-item,id_gis})]
+</Document>
+</kml>
\ No newline at end of file
diff --git a/www/plugins/gis/inclure/gpx-item.html b/www/plugins/gis/inclure/gpx-item.html
new file mode 100755 (executable)
index 0000000..5264c65
--- /dev/null
@@ -0,0 +1,9 @@
+<BOUCLE_gis(GIS){id_gis}>
+<trk>
+       [<name>(#TITRE|supprimer_numero|texte_backend)</name>][
+       <desc>(#DESCRIPTIF|texte_backend)</desc>
+       ]<trkseg>
+               [<trkpt lat="(#LAT)" lon="[(#LON)]"></trkpt>]
+       </trkseg>
+</trk>
+</BOUCLE_gis>
\ No newline at end of file
diff --git a/www/plugins/gis/inclure/kml-item.html b/www/plugins/gis/inclure/kml-item.html
new file mode 100755 (executable)
index 0000000..ade8ba0
--- /dev/null
@@ -0,0 +1,42 @@
+<BOUCLE_gis(GIS){id_gis}>
+       <Placemark id="#gis[(#ID_GIS)]">
+               <name>[(#TITRE|supprimer_numero|texte_backend)]</name>[
+               <atom:link rel="related" href="(#ID_OBJET|generer_url_entite{#OBJET}|url_absolue)" />
+               ]<description>
+                       <![CDATA[
+                               [(#DESCRIPTIF|texte_backend)]
+                       ]]>
+               </description>
+               [<Point>
+                       <coordinates>(#LON),[(#LAT)]</coordinates>
+               </Point>]
+               [(#SET{logo_doc,''})]
+               [(#LOGO_GIS|oui)
+               [(#SET{logo_doc,#LOGO_GIS|image_passe_partout{28,28}|image_recadre{28,28}|image_recadre{32,32,center}})]]
+               [(#LOGO_GIS|non)
+               [(#CHEMIN{images/marker_defaut.png}|oui)[
+                       (#SET{logo_doc,#CHEMIN{images/marker_defaut.png}|image_passe_partout{28,28}|image_recadre{28,28}})]
+               ]]
+               [(#GET{logo_doc}|oui)
+               #SET{icon_w,#GET{logo_doc}|extraire_attribut{src}|largeur}
+               #SET{icon_h,#GET{logo_doc}|extraire_attribut{src}|hauteur}
+               #SET{icon_anchorPoint,''}
+               <Style>
+                       <IconStyle>
+                               <scale>1.0</scale>
+                               [<Icon>
+                                       <href>(#GET{logo_doc}|extraire_attribut{src}|url_absolue)</href>
+                               </Icon>]
+                               <hotSpot x="0.5"  y="0.5" xunits="pixels" yunits="pixels"/>
+                       </IconStyle>
+               </Style>
+               <ExtendedData>
+                       [<Data name="iconSize">
+                               <value>(#GET{icon_w}),[(#GET{icon_h})]</value>
+                       </Data>]
+                       <Data name="iconAnchor">
+                               <value>0.5,0.5</value>
+                       </Data>
+               </ExtendedData>]
+       </Placemark>
+</BOUCLE_gis>
\ No newline at end of file
diff --git a/www/plugins/gis/javascript/gis.js.html b/www/plugins/gis/javascript/gis.js.html
new file mode 100644 (file)
index 0000000..8537b6a
--- /dev/null
@@ -0,0 +1,57 @@
+#HTTP_HEADER{Content-type:text/javascript}
+
+[(#INCLURE{lib/leaflet/dist/[(#CONFIG{auto_compress_js}|=={oui}|?{'leaflet','leaflet-src'})].js})]
+
+[L.Icon.Default.imagePath = "(#CHEMIN{lib/leaflet/dist/images}|url_absolue)";]
+
+(function() {
+
+L.gisConfig = {
+       "gis_layers":#EVAL{json_encode($GLOBALS['gis_layers'])},
+       "default_layer":"[(#REM|gis_layer_defaut)]",
+       "affiche_layers":[(#CONFIG{gis/layers,#ARRAY}|json_encode)]
+};
+
+L.geocoderConfig = {
+       "forwardUrl":"[(#VAL{gis_geocoder_rechercher}|generer_url_action{mode=search}|html_entity_decode)]",
+       "reverseUrl":"[(#VAL{gis_geocoder_rechercher}|generer_url_action{mode=reverse}|html_entity_decode)]"
+};
+
+})();
+
+[(#INCLURE{javascript/gis_geocoder.js})]
+
+[(#INCLURE{javascript/gis_utils.js})]
+
+[(#INCLURE{lib/leaflet/plugins/KML.js})]
+
+[(#INCLURE{lib/leaflet/plugins/GPX.js})]
+
+[(#INCLURE{lib/leaflet/plugins/leaflet-providers.js})]
+
+[(#INCLURE{lib/leaflet/plugins/Control.FullScreen.js})]
+
+[(#INCLURE{lib/leaflet/plugins/Control.MiniMap.js})]
+
+[(#REM) Scripts de google et bing si besoin ]
+
+#SET{layers,#CONFIG{gis/layers,#ARRAY{0,openstreetmap_mapnik}}}
+[(#VAL|gis_layer_defaut|in_array{#GET{layers}}|non)
+       #SET{layers,#GET{layers}|push{#VAL|gis_layer_defaut}}
+]
+
+[(#LISTE{google_roadmap,google_satellite,google_terrain}|array_intersect{#GET{layers}}|count|>{0}|oui)
+[(#INCLURE{lib/leaflet/plugins/Google.js})]
+]
+
+[(#VAL{bing_aerial}|in_array{#GET{layers}}|oui)
+[(#INCLURE{lib/leaflet/plugins/Bing.js})]
+]
+
+[(#INCLURE{lib/leaflet/plugins/leaflet.markercluster-src.js})]
+
+[(#INCLURE{javascript/leaflet.gis.js})]
+
+[(#CONFIG{auto_compress_js}|=={oui}|oui)
+#FILTRE{compacte}
+]
diff --git a/www/plugins/gis/javascript/gis_geocoder.js b/www/plugins/gis/javascript/gis_geocoder.js
new file mode 100644 (file)
index 0000000..822cb7f
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * L.Geocoder is used to make geocoding or reverse geocoding requests.
+ */
+
+L.Geocoder = L.Class.extend({
+
+       includes: L.Mixin.Events,
+
+       options: {
+               forwardUrl: L.geocoderConfig.forwardUrl,
+               reverseUrl: L.geocoderConfig.reverseUrl,
+               limit: 1,
+               acceptLanguage:'fr',
+               addressdetails: 1
+       },
+
+       initialize: function (callback, options) {
+               L.Util.setOptions(this, options);
+               this._user_callback = callback;
+       },
+
+       geocode: function (data) {
+               if (L.LatLng && (data instanceof L.LatLng)) {
+                       this._reverse_geocode(data);
+               } else if (typeof(data) == 'string') {
+                       this.options.search = data;
+                       this._geocode(data);
+               }
+       },
+
+       _geocode: function (text) {
+               this._request(
+                       this.options.forwardUrl,
+                       {
+                               format: 'json',
+                               q: text,
+                               limit: this.options.limit,
+                               addressdetails: this.options.addressdetails,
+                               "accept-language":this.options.acceptLanguage
+                       }
+               );
+       },
+
+       _reverse_geocode: function (latlng) {
+               this._request(
+                       this.options.reverseUrl,
+                       {
+                               format: 'json',
+                               lat: latlng.lat,
+                               lon: latlng.lng
+                       }
+               );
+       },
+
+       _request: function (url, data) {
+               jQuery.ajax({
+                       cache: true,
+                       context: this,
+                       data: data,
+                       dataType: 'jsonp',
+                       jsonp: 'json_callback',
+                       success: this._callback,
+                       url: url
+               });
+       },
+       
+       _callback: function (response,textStatus,jqXHR) {
+               var return_location = {};
+               if(this.options.search)
+                       return_location.search = this.options.search;
+               if (response instanceof Array && !response.length) {
+                       return_location.error = 'not found';
+               } else {
+                       return_location.street = return_location.postcode = return_location.postcode = 
+                       return_location.locality = return_location.region = return_location.country  = '';
+
+                       if (response.length > 0)
+                               place = response[0];
+                       else {
+                               place = response;
+                       }
+                       
+                       var street_components = [];
+                       
+                       if (place.address.country) {
+                               return_location.country = place.address.country;
+                       }
+                       if (place.address.country_code) {
+                               return_location.country_code = place.address.country_code;
+                       }
+                       if (place.address.state) {
+                               return_location.region = place.address.state;
+                       }
+                       //un jour peut-être…
+                       /*
+                       if (place.address.county) {
+                               return_location.departement = place.address.county;
+                       }
+                       */
+                       if (place.address.city) {
+                               return_location.locality = place.address.city;
+                       }else if(place.address.county){
+                               street_components.push(place.address.pedestrian);
+                       }
+                       if (place.address.postcode) {
+                               return_location.postcode = place.address.postcode;
+                       }
+                       if (place.address.road) {
+                               street_components.push(place.address.road);
+                       }else if(place.address.pedestrian){
+                               street_components.push(place.address.pedestrian);
+                       }
+                       if (place.address.house_number) {
+                               street_components.unshift(place.address.house_number);
+                       }
+                       
+                       if (return_location.street === '' && street_components.length > 0) {
+                               return_location.street = street_components.join(' ');
+                       }
+                       
+                       return_location.point = new L.LatLng(place.lat, place.lon);
+               }
+               this._user_callback(return_location);
+       }
+});
diff --git a/www/plugins/gis/javascript/gis_utils.js b/www/plugins/gis/javascript/gis_utils.js
new file mode 100644 (file)
index 0000000..0faa8fd
--- /dev/null
@@ -0,0 +1,11 @@
+function gis_focus_marker(id, map) {
+       var carte = eval('map'+ map);
+       var i, count = 0;
+       for(i in carte._layers) {
+               if ((carte._layers[i].feature) && (carte._layers[i].feature.id == id)) {
+                       carte.panTo(carte._layers[i].getLatLng());
+                       carte._layers[i].openPopup();
+               }
+       count++;
+       }
+}
\ No newline at end of file
diff --git a/www/plugins/gis/javascript/leaflet.gis.js b/www/plugins/gis/javascript/leaflet.gis.js
new file mode 100644 (file)
index 0000000..f55cd4a
--- /dev/null
@@ -0,0 +1,314 @@
+(function() {
+// Plugin Leaflet L.Map.Gis
+L.Map.Gis = L.Map.extend({
+       
+       includes: L.Mixin.Events,
+       
+       options:{
+               mapId: 'map_gis',
+               utiliser_bb: false,
+               sw_lat: 0,
+               ne_lat: 0,
+               sw_lon: 0,
+               ne_lon: 0,
+               gis_layers: L.gisConfig.gis_layers,
+               default_layer: L.gisConfig.default_layer,
+               affiche_layers: L.gisConfig.affiche_layers,
+               scaleControl: false,
+               overviewControl: false,
+               layersControl: false,
+               layersControlOptions: {},
+               noControl: false,
+               cluster: false,
+               clusterOptions: {
+                       disableClusteringAtZoom: 0,
+                       showCoverageOnHover: false,
+                       maxClusterRadius: 80,
+                       spiderfyOnMaxZoom: false
+               },
+               pathStyles: null,
+               autocenterandzoom: false,
+               openId: false,
+               affiche_points: true,
+               json_points: {
+                       url: "",
+                       objets: "",
+                       limit: 500,
+                       env: [],
+                       titre: "",
+                       description: "",
+                       icone: ""
+               },
+               localize_visitor: false,
+               localize_visitor_zoom: 0,
+               centrer_fichier: true,
+               kml: false,
+               gpx: false,
+               geojson: false
+       },
+       
+       initialize: function (id,options) {
+               L.Util.setOptions(this, options);
+               
+               this.on('load',function(e){
+                       // Affecter sur l'objet du DOM
+                       jQuery("#"+this._container.id).get(0).map = this;
+                       // Appeler l'éventuelle fonction de callback
+                       if (this.options.callback && typeof(this.options.callback) === "function")
+                               this.options.callback(this);
+                       // trigger load sur l'objet du DOM
+                       jQuery("#"+this._container.id).trigger('load',this);
+               });
+               
+               L.Map.prototype.initialize.call(this, id, options);
+               
+               if (this.options.utiliser_bb){
+                       this.fitBounds(
+                               L.latLngBounds(
+                                       [this.options.sw_lat, this.options.sw_lon],
+                                       [this.options.ne_lat, this.options.ne_lon]
+                               )
+                       );
+               }
+               
+               this.populateTileLayers(this.options.affiche_layers);
+               
+               this.initControls();
+               
+               this.loadData();
+               
+               this.addOverlays();
+               
+               if (this.options.localize_visitor){
+                       var maxZoom = this.options.localize_visitor_zoom;
+                       this.on('locationerror',function(e){
+                               maxZoom = this.options.zoom;
+                               alert(e.message);
+                       });
+                       this.locate({setView: true, maxZoom: maxZoom});
+               }
+               
+               // Si pas de points affichés trigger ready ici
+               if (!this.options.affiche_points || !this.options.json_points.length)
+                       jQuery("#"+this._container.id).trigger('ready',this);
+       },
+
+       populateTileLayers: function (tilelayers) {
+               // Fond de carte par défaut
+               var default_layer = this.createTileLayer(this.options.default_layer);
+               this.addLayer(default_layer);
+               // Fonds de carte supplémentaires
+               if (this.options.layersControl && !this.options.noControl && this.options.affiche_layers.length>1){
+                       var layers_control = L.control.layers('','',this.options.layersControlOptions);
+                       layers_control.addBaseLayer(default_layer,this.options.gis_layers[this.options.default_layer].nom);
+                       for(var l in this.options.affiche_layers){
+                               if (this.options.affiche_layers[l]!==this.options.default_layer){
+                                       var layer = this.createTileLayer(this.options.affiche_layers[l]);
+                                       if (typeof layer!=="undefined")
+                                               layers_control.addBaseLayer(layer,this.options.gis_layers[this.options.affiche_layers[l]].nom);
+                               }
+                       }
+                       this.addControl(layers_control);
+                       // Ajouter l'objet du controle de layers à la carte pour permettre d'y accéder depuis le callback
+                       this.layersControl = layers_control;
+                       // Classe noajax sur le layer_control pour éviter l'ajout de hidden par SPIP
+                       L.DomUtil.addClass(layers_control._form,'noajax');
+               }
+       },
+
+       initControls: function () {
+               this.attributionControl.setPrefix('');
+               if (this.options.scaleControl)
+                       L.control.scale().addTo(this);
+               if (this.options.overviewControl){
+                       // todo ajouter une option pour permettre de choisir la couche à afficher dans la minimap
+                       var minimap_layer = this.createTileLayer(this.options.default_layer);
+                       L.control.minimap(minimap_layer,{width: 100,height: 100, toggleDisplay: true}).addTo(this);
+               }
+       },
+       
+       createTileLayer: function (name) {
+               var layer;
+               if (typeof this.options.gis_layers[name]!=="undefined")
+                       eval("layer=new "+ this.options.gis_layers[name].layer +";");
+               return layer;
+       },
+
+       // API setGeoJsonFeatureIcon : Pour Ajouter l'icone d'un point (feature = item d'un GeoJson)
+       setGeoJsonFeatureIcon: function (feature, layer) {
+               // Déclarer l'icone du points, si défini
+               if (feature.properties && feature.properties.icon){
+                       icon_options = {
+                               'iconUrl': feature.properties.icon,
+                               'iconSize': [feature.properties.icon_size[0], feature.properties.icon_size[1]],
+                               'iconAnchor': [feature.properties.icon_anchor[0], feature.properties.icon_anchor[1]]
+                       };
+                       if (feature.properties.popup_anchor)
+                               icon_options.popupAnchor = [feature.properties.popup_anchor[0], feature.properties.popup_anchor[1]];
+                       if (feature.properties.shadow)
+                               icon_options.shadowUrl = feature.properties.shadow;
+                       if (feature.properties.shadow_size)
+                               icon_options.shadowSize = [feature.properties.shadow_size[0], feature.properties.shadow_size[1]];
+                       layer.setIcon(L.icon(icon_options));
+               }
+       },
+       
+       // API setGeoJsonFeaturePopup : Pour Ajouter le texte de popup d'un point (feature = item d'un GeoJson)
+       setGeoJsonFeaturePopup: function (feature, layer) {
+               // Déclarer le contenu de la popup s'il y en a
+               if (feature.properties && (feature.properties.title || feature.properties.description)){
+                       var popupContent = '';
+                       var popupOptions = '';
+                       if (feature.properties.title)
+                               popupContent = '<strong class="title">' + feature.properties.title + '</strong>';
+                       if (feature.properties.description)
+                               popupContent = popupContent + feature.properties.description;
+                       if (feature.properties.popup_options)
+                               popupOptions = feature.properties.popup_options;
+                       layer.bindPopup(popupContent,popupOptions);
+               }
+       },
+       
+       // API parseGeoJson
+       parseGeoJson: function(data) {
+               var map = this;
+               // Analyse des points et déclaration (sans regroupement des points en cluster)
+               if (!map.options.cluster){
+                       if (data.features.length > 0){
+                               var geojson = L.geoJson('', {
+                                       style: this.options.pathStyles,
+                                       onEachFeature: function (feature, layer) {
+                                               // Déclarer l'icone du point
+                                               map.setGeoJsonFeatureIcon(feature, layer);
+                                               // Déclarer le contenu de la popup s'il y en a
+                                               map.setGeoJsonFeaturePopup(feature, layer);
+                                       }
+                               }).addData(data).addTo(map);
+                               
+                               if (map.options.autocenterandzoom){
+                                       if (data.features.length == 1 && data.features[0].geometry.type == 'Point')
+                                               map.setView(geojson.getBounds().getCenter(), map.options.zoom);
+                                       else
+                                               map.fitBounds(geojson.getBounds());
+                               }
+                               if (map.options.openId)
+                                       gis_focus_marker(map.options.openId,map.options.mapId);
+
+                               if (typeof map.geojsons=="undefined") map.geojsons = [];
+                               map.geojsons.push(geojson);
+                       }
+               } else {
+                       map.markers = L.markerClusterGroup(map.options.clusterOptions);
+
+                       /* Pour chaque points présents, on crée un marqueur */
+                       jQuery.each(data.features, function(i, feature){
+                               if (feature.geometry.coordinates[0]){
+                                       var marker = L.marker([feature.geometry.coordinates[1], feature.geometry.coordinates[0]]);
+
+                                       // Déclarer l'icone du point
+                                       map.setGeoJsonFeatureIcon(feature, marker);
+                                       // Déclarer le contenu de la popup s'il y en a
+                                       map.setGeoJsonFeaturePopup(feature, marker);
+
+                                       marker.id = feature.id;
+                                       map.markers.addLayer(marker);
+                               }
+                       });
+
+                       map.addLayer(map.markers);
+
+                       if (map.options.autocenterandzoom){
+                               if (data.features.length > 1)
+                                       map.fitBounds(map.markers.getBounds());
+                               else
+                                       map.setView(map.markers.getBounds().getCenter(), map.options.zoom);
+                       }
+               }
+       },
+       
+       // API Compat GIS3
+       addJSON: function(data) {
+               return this.parseGeoJson(data);
+       },
+       
+       // API Compat GIS3
+       removeAllMarkers: function(){
+               if (typeof this.geojsons=="undefined") this.geojsons = [];
+               for(var i in this.geojsons){
+                       this.geojsons[i].clearLayers();
+                       this.removeLayer(this.geojsons[i]);
+               }
+               this.geojsons = [];
+       },
+       
+       loadData: function () {
+               var map = this;
+               if (map.options.affiche_points
+                       && typeof(map.options.json_points) !== "undefined"
+                       && map.options.json_points.url.length){
+                       // Récupération des points à mettre sur la carte, via json externe
+                       var args = {};
+                       jQuery.extend(true, args, map.options.json_points.env);
+                       if (typeof map.options.json_points.objets !== "undefined"){
+                               args.objets = map.options.json_points.objets;
+                               if (args.objets == "point_libre"){
+                                       args.lat = map.options.center[0];
+                                       args.lon = map.options.center[1];
+                                       if (typeof map.options.json_points.titre !== "undefined")
+                                               args.titre = map.options.json_points.titre;
+                                       if (typeof map.options.json_points.description !== "undefined")
+                                               args.description = map.options.json_points.description;
+                                       if (typeof map.options.json_points.icone !== "undefined")
+                                               args.icone = map.options.json_points.icone;
+                               }
+                       }
+                       if (typeof map.options.json_points.limit !== "undefined")
+                               args.limit = map.options.json_points.limit;
+                       jQuery.getJSON(map.options.json_points.url,args,
+                               function(data) {
+                                       if (data){
+                                               // Charger le json (data) et déclarer les points
+                                               map.parseGeoJson(data);
+                                               jQuery("#"+map._container.id).trigger('ready',map);
+                                       }
+                               }
+                       );
+               }
+       },
+       
+       addOverlays: function () {
+               var map = this;
+               if (map.options.kml && map.options.kml.length){
+                       map.kml = {};
+                       for(var i in map.options.kml){
+                               map.kml[i] = new L.KML(map.options.kml[i], {async: true});
+                               if (map.options.centrer_fichier)
+                                       map.kml[i].on("loaded", function(e) { map.fitBounds(e.target.getBounds()); });
+                               map.addLayer(map.kml[i]);
+                       }
+               }
+               if (map.options.gpx && map.options.gpx.length){
+                       map.gpx = {};
+                       for(var i in map.options.gpx){
+                               map.gpx[i] = new L.GPX(map.options.gpx[i], {async: true});
+                               if (map.options.centrer_fichier)
+                                       map.gpx[i].on("loaded", function(e) { map.fitBounds(e.target.getBounds()); });
+                               map.addLayer(map.gpx[i]);
+                       }
+               }
+               if (map.options.geojson && map.options.geojson.length){
+                       for(var i in map.options.geojson){
+                               jQuery.getJSON(map.options.geojson[i], function(data){
+                                       if (data)
+                                               map.parseGeoJson(data);
+                               });
+                       }
+               }
+       }
+});
+
+L.map.gis = function (id, options) {
+       return new L.Map.Gis(id, options);
+};
+
+})();
\ No newline at end of file
diff --git a/www/plugins/gis/json/gis.html b/www/plugins/gis/json/gis.html
new file mode 100644 (file)
index 0000000..cab4848
--- /dev/null
@@ -0,0 +1,9 @@
+               <BOUCLE_gis(GIS){id_gis ?}{objet ?}{id_objet ?}{id_rubrique ?}{id_article ?}{id_breve ?}{id_document ?}{id_mot ?}{id_auteur ?}{id_syndic ?}{recherche ?}{0, #ENV{limit}}{","}>
+               {"type": "Feature",
+               "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
+               "id":"#ID_GIS",
+               "properties": {
+                       "title":[(#TITRE*|extraire_multi|supprimer_numero|json_encode)],
+                       "description":[(#DESCRIPTIF|json_encode)][
+                       (#LOGO_GIS|gis_icon_properties)]
+               }}</BOUCLE_gis>
diff --git a/www/plugins/gis/json/gis_articles.html b/www/plugins/gis/json/gis_articles.html
new file mode 100644 (file)
index 0000000..d46943d
--- /dev/null
@@ -0,0 +1,9 @@
+               <BOUCLE_art(ARTICLES){gis}{id_article ?}{id_rubrique ?}{id_secteur ?}{id_mot ?}{id_groupe ?}{id_auteur ?}{recherche ?}{0, #ENV{limit}}{","}>
+               {"type": "Feature",
+               "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
+               "id":"#ID_GIS",
+               "properties": {
+                       "title":[(#TITRE_GIS*|sinon{#TITRE*}|extraire_multi|supprimer_numero|json_encode)],
+                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][
+                       (#LOGO_GIS|gis_icon_properties)]
+               }}</BOUCLE_art>
\ No newline at end of file
diff --git a/www/plugins/gis/json/gis_articles_branche.html b/www/plugins/gis/json/gis_articles_branche.html
new file mode 100644 (file)
index 0000000..1f3de60
--- /dev/null
@@ -0,0 +1,9 @@
+               <BOUCLE_art(ARTICLES){gis}{id_article ?}{branche ?}{id_mot ?}{id_groupe ?}{id_auteur ?}{recherche ?}{0, #ENV{limit}}{","}>
+               {"type": "Feature",
+               "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
+               "id":"#ID_GIS",
+               "properties": {
+                       "title":[(#TITRE_GIS*|sinon{#TITRE*}|extraire_multi|supprimer_numero|json_encode)],
+                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][
+                       (#LOGO_GIS|gis_icon_properties)]
+               }}</BOUCLE_art>
\ No newline at end of file
diff --git a/www/plugins/gis/json/gis_articles_plus_sites.html b/www/plugins/gis/json/gis_articles_plus_sites.html
new file mode 100644 (file)
index 0000000..8527953
--- /dev/null
@@ -0,0 +1,2 @@
+[(#INCLURE{fond=json/gis_articles,env})#SET{articlesvus,1}][[(#GET{articlesvus}|?{','})]\r
+               (#INCLURE{fond=json/gis_sites,env})]
\ No newline at end of file
diff --git a/www/plugins/gis/json/gis_auteurs.html b/www/plugins/gis/json/gis_auteurs.html
new file mode 100644 (file)
index 0000000..e9d81cc
--- /dev/null
@@ -0,0 +1,9 @@
+               <BOUCLE_auteurs(AUTEURS){gis}{id_article ?}{id_auteur ?}{id_mot ?}{recherche ?}{0, #ENV{limit}}{","}>
+               {"type": "Feature",
+               "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
+               "id":"#ID_GIS",
+               "properties": {
+                       "title":[(#TITRE_GIS*|sinon{#NOM*}|extraire_multi|supprimer_numero|json_encode)],
+                       "description":[(#DESCRIPTIF_GIS|sinon{#BIO}|json_encode)][
+                       (#LOGO_GIS|gis_icon_properties)]
+               }}</BOUCLE_auteurs>
\ No newline at end of file
diff --git a/www/plugins/gis/json/gis_documents.html b/www/plugins/gis/json/gis_documents.html
new file mode 100644 (file)
index 0000000..c7b66b0
--- /dev/null
@@ -0,0 +1,9 @@
+               <BOUCLE_doc(DOCUMENTS){gis}{id_article ?}{id_rubrique ?}{id_secteur ?}{id_mot ?}{recherche ?}{media ?}{0, #ENV{limit}}{","}>
+               {"type": "Feature",
+               "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
+               "id":"#ID_GIS",
+               "properties": {
+                       "title":[(#TITRE_GIS*|sinon{#TITRE*}|extraire_multi|supprimer_numero|json_encode)],
+                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][
+                       (#LOGO_GIS|gis_icon_properties)]
+               }}</BOUCLE_doc>
\ No newline at end of file
diff --git a/www/plugins/gis/json/gis_evenements.html b/www/plugins/gis/json/gis_evenements.html
new file mode 100644 (file)
index 0000000..9c975ac
--- /dev/null
@@ -0,0 +1,9 @@
+               <BOUCLE_events(EVENEMENTS){gis}{id_evenement ?}{id_article ?}{id_rubrique ?}{id_secteur ?}{id_mot ?}{id_auteur ?}{recherche ?}{0, #ENV{limit}}{","}>
+               {"type": "Feature",
+               "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
+               "id":"#ID_GIS",
+               "properties": {
+                       "title":[(#TITRE_GIS*|sinon{#TITRE*}|extraire_multi|supprimer_numero|json_encode)],
+                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][
+                       (#LOGO_GIS|gis_icon_properties)]
+               }}</BOUCLE_events>
diff --git a/www/plugins/gis/json/gis_mots.html b/www/plugins/gis/json/gis_mots.html
new file mode 100644 (file)
index 0000000..ea3455f
--- /dev/null
@@ -0,0 +1,9 @@
+               <BOUCLE_mots(MOTS){gis}{id_mot ?}{id_groupe ?}{id_article ?}{id_rubrique ?}{id_breve ?}{id_syndic ?}{id_forum ?}{recherche ?}{0, #ENV{limit}}{","}>
+               {"type": "Feature",
+               "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
+               "id":"#ID_GIS",
+               "properties": {
+                       "title":[(#TITRE_GIS*|sinon{#TITRE*}|extraire_multi|supprimer_numero|json_encode)],
+                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][
+                       (#LOGO_GIS|gis_icon_properties)]
+               }}</BOUCLE_mots>
\ No newline at end of file
diff --git a/www/plugins/gis/json/gis_point_libre.html b/www/plugins/gis/json/gis_point_libre.html
new file mode 100644 (file)
index 0000000..8e5c3fa
--- /dev/null
@@ -0,0 +1,10 @@
+{
+       "type": "Feature",
+       "geometry": {"type": "Point", "coordinates": [[(#ENV{lon})], [(#ENV{lat})]]},
+       "id":"1",
+       "properties": {
+               "title":[(#ENV{titre}|?{#ENV{titre},''}|json_encode)],
+               "description":[(#ENV{description}|?{#ENV{description}|wrap{<p>},''}|json_encode)][
+               (#CHEMIN_IMAGE{#ENV*{icone,0}}|sinon{#CHEMIN{#ENV*{icone,0}}}|gis_icon_properties)]
+       }
+}
diff --git a/www/plugins/gis/json/gis_rubriques.html b/www/plugins/gis/json/gis_rubriques.html
new file mode 100644 (file)
index 0000000..7d64147
--- /dev/null
@@ -0,0 +1,9 @@
+               <BOUCLE_rub(RUBRIQUES){gis}{id_rubrique ?}{id_secteur ?}{id_parent?=#ENV{id_parent}}{id_mot ?}{recherche ?}{0, #ENV{limit}}{","}>
+               {"type": "Feature",
+               "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
+               "id":"#ID_GIS",
+               "properties": {
+                       "title":[(#TITRE_GIS*|sinon{#TITRE*}|extraire_multi|supprimer_numero|json_encode)],
+                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][
+                       (#LOGO_GIS|gis_icon_properties)]
+               }}</BOUCLE_rub>
\ No newline at end of file
diff --git a/www/plugins/gis/json/gis_sites.html b/www/plugins/gis/json/gis_sites.html
new file mode 100644 (file)
index 0000000..965e291
--- /dev/null
@@ -0,0 +1,9 @@
+               <BOUCLE_sit(SITES){gis}{id_syndic ?}{id_rubrique ?}{id_secteur ?}{id_mot ?}{recherche ?}{0, #ENV{limit}}{","}>
+               {"type": "Feature",
+               "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
+               "id":"#ID_GIS",
+               "properties": {
+                       "title":[(#TITRE_GIS*|sinon{#NOM_SITE*}|extraire_multi|supprimer_numero|json_encode)],
+                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][
+                       (#LOGO_GIS|gis_icon_properties)]
+               }}</BOUCLE_sit>
\ No newline at end of file
diff --git a/www/plugins/gis/json/gis_tous_avec_liens_espace_prive.html b/www/plugins/gis/json/gis_tous_avec_liens_espace_prive.html
new file mode 100644 (file)
index 0000000..d714179
--- /dev/null
@@ -0,0 +1,9 @@
+               <BOUCLE_gis(GIS){id_gis ?}{id_rubrique ?}{id_article ?}{id_breve ?}{id_document ?}{id_mot ?}{id_auteur ?}{id_syndic ?}{recherche ?}{0, #ENV{limit}}{","}>
+               {"type": "Feature",
+               "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
+               "id":"#ID_GIS",
+               "properties": {
+                       "title":[(#VAL{<a href='[(#ID_GIS|generer_url_entite{gis,'','',0})]'>[(#TITRE*|extraire_multi|supprimer_numero|sinon{----})]</a>}|json_encode)],
+                       "description":[(#DESCRIPTIF|json_encode)][(#SET{logo_doc,''})][
+                       (#LOGO_GIS|gis_icon_properties)]
+               }}</BOUCLE_gis>
diff --git a/www/plugins/gis/lang/gis.xml b/www/plugins/gis/lang/gis.xml
new file mode 100644 (file)
index 0000000..7a671c4
--- /dev/null
@@ -0,0 +1,27 @@
+<traduction module="gis" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/gis/trunk/lang/" reference="fr">
+       <langue code="en" url="http://trad.spip.net/tradlang_module/gis?lang_cible=en" total="140" traduits="140" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="b_b" lien="http://trad.spip.net/auteur/b_b" />
+               <traducteur nom="Benitron" lien="http://trad.spip.net/auteur/benitron" />
+               <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
+               <traducteur nom="kent1" lien="http://trad.spip.net/auteur/kent1" />
+       </langue>
+       <langue code="es" url="http://trad.spip.net/tradlang_module/gis?lang_cible=es" total="140" traduits="140" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="alf" lien="http://trad.spip.net/auteur/alf" />
+               <traducteur nom="b_b" lien="http://trad.spip.net/auteur/b_b" />
+               <traducteur nom="Raquel S. Bujaldón" lien="http://trad.spip.net/auteur/raquel-s-bujaldon" />
+               <traducteur nom="severo" lien="http://trad.spip.net/auteur/severo" />
+       </langue>
+       <langue code="fr" url="http://trad.spip.net/tradlang_module/gis?lang_cible=fr" total="140" traduits="140" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       </langue>
+       <langue code="nl" url="http://trad.spip.net/tradlang_module/gis?lang_cible=nl" total="140" traduits="140" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="b_b" lien="http://trad.spip.net/auteur/b_b" />
+               <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
+       </langue>
+       <langue code="ru" url="http://trad.spip.net/tradlang_module/gis?lang_cible=ru" total="140" traduits="122" relire="0" modifs="2" nouveaux="16" pourcent="87.14">
+               <traducteur nom="Serge Markitanenko" lien="http://trad.spip.net/auteur/serge-markitanenko" />
+       </langue>
+       <langue code="sk" url="http://trad.spip.net/tradlang_module/gis?lang_cible=sk" total="140" traduits="139" relire="0" modifs="1" nouveaux="0" pourcent="99.29">
+               <traducteur nom="b_b" lien="http://trad.spip.net/auteur/b_b" />
+               <traducteur nom="jaro" lien="http://trad.spip.net/auteur/jaro" />
+       </langue>
+</traduction>
diff --git a/www/plugins/gis/lang/gis_en.php b/www/plugins/gis/lang/gis_en.php
new file mode 100644 (file)
index 0000000..b8281e4
--- /dev/null
@@ -0,0 +1,171 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/gis?lang_cible=en
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucun_gis' => 'No point',
+       'aucun_objet' => 'No object',
+
+       // B
+       'bouton_lier' => 'Link this point',
+       'bouton_supprimer_gis' => 'Delete this point permanently',
+       'bouton_supprimer_lien' => 'Remove this link',
+
+       // C
+       'cfg_descr_gis' => 'Geographic Information System.<br /><a href="http://contrib.spip.net/4189" class="spip_out">Link to the documentation</a>.',
+       'cfg_inf_adresse' => 'Displays additional address fields (country, city, state, address ...)',
+       'cfg_inf_bing' => 'The Bing Aerial layer needs a key you can create on <a href=\'@url@\' class="spip_out">the Bing website</a>.',
+       'cfg_inf_cloudmade' => 'This API needs a key you can create on <a href=\'@url@\' class="spip_out">the CloudMade website</a>.',
+       'cfg_inf_geocoder' => 'Enable geocoder functions (search from an address, recovery of the address from the coordinates).',
+       'cfg_inf_geolocaliser_user_html5' => 'If the user’s browser allows it, its approximate geographic location is retrieved to give the default position when creating a new point.',
+       'cfg_inf_google' => 'This API needs a key you can create on <a href=\'@url@\' class="spip_out">the GoogleMaps website</a>.',
+       'cfg_inf_yandex' => 'This API needs a key you can create on <a href=\'@url@\' class="spip_out">the yandex website</a>.',
+       'cfg_lbl_activer_objets' => 'Enable geotagging of content:',
+       'cfg_lbl_adresse' => 'Show address fields',
+       'cfg_lbl_api' => 'Geolocation API',
+       'cfg_lbl_api_cloudmade' => 'CloudMade',
+       'cfg_lbl_api_google' => 'Google Maps v2',
+       'cfg_lbl_api_googlev3' => 'Google Maps v3',
+       'cfg_lbl_api_key_bing' => 'Bing key',
+       'cfg_lbl_api_key_cloudmade' => 'CloudMade API key',
+       'cfg_lbl_api_key_google' => 'GoogleMaps API key',
+       'cfg_lbl_api_key_yandex' => 'Yandex API key',
+       'cfg_lbl_api_mapquest' => 'MapQuest',
+       'cfg_lbl_api_microsoft' => 'Microsoft Bing',
+       'cfg_lbl_api_openlayers' => 'OpenLayers',
+       'cfg_lbl_api_ovi' => 'Ovi Nokia',
+       'cfg_lbl_api_yandex' => 'Yandex',
+       'cfg_lbl_geocoder' => 'Geocoder',
+       'cfg_lbl_geolocaliser_user_html5' => 'Center the map on the location of the user at the creation step',
+       'cfg_lbl_layer_defaut' => 'Default layer',
+       'cfg_lbl_layers' => 'Proposed layers',
+       'cfg_lbl_maptype' => 'Base map',
+       'cfg_lbl_maptype_carte' => 'Map',
+       'cfg_lbl_maptype_hybride' => 'Hybrid',
+       'cfg_lbl_maptype_relief' => 'Relief',
+       'cfg_lbl_maptype_satellite' => 'Satellite',
+       'cfg_titre_gis' => 'GIS configuration',
+
+       // E
+       'editer_gis_editer' => 'Edit this point',
+       'editer_gis_nouveau' => 'Create a new point',
+       'editer_gis_titre' => 'The location-based points',
+       'erreur_geocoder' => 'No results for your search:',
+       'erreur_recherche_pas_resultats' => 'No point corresponds to the searched text.',
+       'erreur_xmlrpc_lat_lon' => 'Latitude and longitude should be set as arguments',
+       'explication_api_forcee' => 'The is imposed by another plugin or skeleton.',
+       'explication_import' => 'Import a file in GPX or KML format.',
+       'explication_layer_forcee' => 'The layer is imposed by another plugin or skeleton.',
+       'explication_maptype_force' => 'The base map is imposed by another plugin or skeleton.',
+
+       // F
+       'formulaire_creer_gis' => 'Create a new location-based point:',
+       'formulaire_modifier_gis' => 'Modify the location-based point:',
+
+       // G
+       'gis_pluriel' => 'Location-based points',
+       'gis_singulier' => 'Location-based point',
+
+       // I
+       'icone_gis_tous' => 'Location-based points',
+       'info_1_gis' => 'A location-based point',
+       'info_1_objet_gis' => '1 object linked to that point',
+       'info_aucun_gis' => 'No location-based point',
+       'info_aucun_objet_gis' => 'No object linked to that point',
+       'info_geolocalisation' => 'Geolocation',
+       'info_id_objet' => 'N°',
+       'info_liste_gis' => 'Location-based points',
+       'info_nb_gis' => '@nb@ location-based points',
+       'info_nb_objets_gis' => '@nb@ objects linked to that point',
+       'info_numero_gis' => 'Point number',
+       'info_objet' => 'Object',
+       'info_recherche_gis_zero' => 'No result for « @cherche_gis@ ».',
+       'info_supprimer_lien' => 'Unlink',
+       'info_supprimer_liens' => 'Unlink all the points',
+       'info_voir_fiche_objet' => 'Go to page',
+
+       // L
+       'label_adress' => 'Address',
+       'label_code_pays' => 'Country code',
+       'label_code_postal' => 'Postal code',
+       'label_departement' => 'Department',
+       'label_import' => 'Import',
+       'label_inserer_modele_articles' => 'linked to articles',
+       'label_inserer_modele_articles_sites' => 'linked to articles + websites',
+       'label_inserer_modele_auteurs' => 'linked to authors',
+       'label_inserer_modele_centrer_auto' => 'No automatic centring',
+       'label_inserer_modele_centrer_fichier' => 'Do not center the map on the KLM/GPX files',
+       'label_inserer_modele_controle' => 'Hide controls',
+       'label_inserer_modele_controle_type' => 'Hide types',
+       'label_inserer_modele_description' => 'Description',
+       'label_inserer_modele_documents' => 'linked to documents',
+       'label_inserer_modele_echelle' => 'Scale',
+       'label_inserer_modele_fullscreen' => 'Full screen button',
+       'label_inserer_modele_gpx' => 'GPX file to overlay',
+       'label_inserer_modele_hauteur_carte' => 'Map height',
+       'label_inserer_modele_identifiant' => 'ID',
+       'label_inserer_modele_identifiant_opt' => 'ID (optionnal)',
+       'label_inserer_modele_identifiant_placeholder' => 'id_gis',
+       'label_inserer_modele_kml' => 'KML files to overlay',
+       'label_inserer_modele_kml_gpx' => 'id_document or url',
+       'label_inserer_modele_largeur_carte' => 'Map width',
+       'label_inserer_modele_limite' => 'Maximum number of points',
+       'label_inserer_modele_localiser_visiteur' => 'Center on the visitor',
+       'label_inserer_modele_mini_carte' => 'Mini situation map',
+       'label_inserer_modele_molette' => 'Disable the scroll wheel',
+       'label_inserer_modele_mots' => 'Linked to words',
+       'label_inserer_modele_objets' => 'Point(s) category',
+       'label_inserer_modele_point_gis' => 'single point recorded',
+       'label_inserer_modele_point_libre' => 'free single point',
+       'label_inserer_modele_points' => 'Hide points',
+       'label_inserer_modele_rubriques' => 'linked to sections',
+       'label_inserer_modele_sites' => 'linked to websites',
+       'label_inserer_modele_titre_carte' => 'Map title',
+       'label_pays' => 'Country',
+       'label_rechercher_address' => 'Search for an address',
+       'label_rechercher_point' => 'Search for a point',
+       'label_region' => 'Region',
+       'label_ville' => 'Town',
+       'lat' => 'Latitude',
+       'libelle_logo_gis' => 'POINT\\’S LOGO',
+       'lien_ajouter_gis' => 'Add this point',
+       'lon' => 'Longitude',
+
+       // T
+       'telecharger_gis' => 'Download in @format@ format',
+       'texte_ajouter_gis' => 'Add a location-based point',
+       'texte_creer_associer_gis' => 'Create and link a location-based point',
+       'texte_creer_gis' => 'Create a location-based point',
+       'texte_modifier_gis' => 'Modify the location-based point',
+       'texte_voir_gis' => 'Show the location-based point',
+       'titre_bloc_creer_point' => 'Link a new point',
+       'titre_bloc_points_lies' => 'Linked points',
+       'titre_bloc_rechercher_point' => 'Search for a point',
+       'titre_nombre_utilisation' => 'One use',
+       'titre_nombre_utilisations' => '@nb@ uses',
+       'titre_nouveau_point' => 'New point',
+       'titre_objet' => 'Title',
+       'toolbar_actions_title' => 'Cancel the drawing',
+       'toolbar_buttons_marker' => 'Plot a point',
+       'toolbar_buttons_polygon' => 'Draw a polygon',
+       'toolbar_buttons_polyline' => 'Draw a line',
+       'toolbar_handlers_marker_tooltip_start' => 'Click to set marker',
+       'toolbar_handlers_polygon_tooltip_cont' => 'Click to continue drawing the polygon',
+       'toolbar_handlers_polygon_tooltip_end' => 'Click the first point to close the polygon',
+       'toolbar_handlers_polygon_tooltip_start' => 'Click to start drawing the polygon',
+       'toolbar_handlers_polyline_tooltip_cont' => 'Click to continue to draw the line',
+       'toolbar_handlers_polyline_tooltip_end' => 'Click the last point to end line',
+       'toolbar_handlers_polyline_tooltip_start' => 'Click to start drawing the line',
+       'toolbar_undo_text' => 'Delete last point',
+       'toolbar_undo_title' => 'Delete last point drawn',
+
+       // Z
+       'zoom' => 'Zoom'
+);
+
+?>
diff --git a/www/plugins/gis/lang/gis_es.php b/www/plugins/gis/lang/gis_es.php
new file mode 100644 (file)
index 0000000..71747e8
--- /dev/null
@@ -0,0 +1,171 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/gis?lang_cible=es
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucun_gis' => 'Ningún punto',
+       'aucun_objet' => 'Ningún objeto',
+
+       // B
+       'bouton_lier' => 'Asociar este punto',
+       'bouton_supprimer_gis' => 'Eliminar definitivamente este punto',
+       'bouton_supprimer_lien' => 'Eliminar este enlace',
+
+       // C
+       'cfg_descr_gis' => 'Sistema de Información Geográfica.<br /><a href="http://contrib.spip.net/4189" class="spip_out">Ir a la documentación</a>.',
+       'cfg_inf_adresse' => 'Mostrar campos adicionales de dirección (país, ciudad, región, dirección...)',
+       'cfg_inf_bing' => 'La capa Bing Aerial necesita crear una clave <a href=\'@url@\' class="spip_out">el sitio de Bing</a>.',
+       'cfg_inf_cloudmade' => 'Esta API necesita crear una clave en <a href=\'@url@\'>el sitio de CloudMade</a>.',
+       'cfg_inf_geocoder' => 'Activar las funciones del geocoder (búsqueda desde una dirección, recuperación de una dirección partiendo de coordenadas).',
+       'cfg_inf_geolocaliser_user_html5' => 'Si el navegador del usuario lo permite, su ubicación geográfica aproximada se recupera para dar la posición por defecto durante la creación de un punto.',
+       'cfg_inf_google' => 'Esta API necesita crear una clave en <a href=\'@url@\'>el sitio de GoogleMaps</a>.',
+       'cfg_inf_yandex' => 'Esta API necesita crear una clave en <a href=\'@url@\'>el sitio de Yandex</a>.',
+       'cfg_lbl_activer_objets' => 'Activar la geolocalización en los contenidos:',
+       'cfg_lbl_adresse' => 'Mostrar los campos de dirección',
+       'cfg_lbl_api' => 'API de cartografía',
+       'cfg_lbl_api_cloudmade' => 'CloudMade',
+       'cfg_lbl_api_google' => 'Google Maps v2',
+       'cfg_lbl_api_googlev3' => 'Google Maps v3',
+       'cfg_lbl_api_key_bing' => 'Clave Bing',
+       'cfg_lbl_api_key_cloudmade' => 'Clave CloudMade',
+       'cfg_lbl_api_key_google' => 'Clave GoogleMaps',
+       'cfg_lbl_api_key_yandex' => 'Clave Yandex',
+       'cfg_lbl_api_mapquest' => 'MapQuest',
+       'cfg_lbl_api_microsoft' => 'Microsoft Bing',
+       'cfg_lbl_api_openlayers' => 'OpenLayers',
+       'cfg_lbl_api_ovi' => 'Ovi Nokia',
+       'cfg_lbl_api_yandex' => 'Yandex',
+       'cfg_lbl_geocoder' => 'Geocoder',
+       'cfg_lbl_geolocaliser_user_html5' => 'Centrar el mapa en base a la ubicación del usuario durante la creación',
+       'cfg_lbl_layer_defaut' => 'Capa por defecto',
+       'cfg_lbl_layers' => 'Capas propuestas',
+       'cfg_lbl_maptype' => 'Fondo cartográfico',
+       'cfg_lbl_maptype_carte' => 'Mapa',
+       'cfg_lbl_maptype_hybride' => 'Híbrido',
+       'cfg_lbl_maptype_relief' => 'Relieve',
+       'cfg_lbl_maptype_satellite' => 'Satélite',
+       'cfg_titre_gis' => 'configuración de GIS',
+
+       // E
+       'editer_gis_editer' => 'Modificar este punto',
+       'editer_gis_nouveau' => 'Crear un nuevo punto',
+       'editer_gis_titre' => 'Puntos geolocalizados',
+       'erreur_geocoder' => 'Ningún resultado para su búsqueda:',
+       'erreur_recherche_pas_resultats' => 'Ningún punto corresponde a la búsqueda.',
+       'erreur_xmlrpc_lat_lon' => 'La latitud y la longitud deben indicarse como parámetros',
+       'explication_api_forcee' => 'La API esta impuesta por otro plugin o esqueleto.',
+       'explication_import' => 'Importar un archivo en formato GPX o KML.',
+       'explication_layer_forcee' => 'La capa se ha impuesto por otro plugin o esqueleto. ',
+       'explication_maptype_force' => 'El fondo del mapa está impuesto por otro plugin o esqueleto.',
+
+       // F
+       'formulaire_creer_gis' => 'Crear un punto geolocalizado:',
+       'formulaire_modifier_gis' => 'Modificar el punto geolocalizado:',
+
+       // G
+       'gis_pluriel' => 'Puntos geolocalizados',
+       'gis_singulier' => 'Punto geolocalizado',
+
+       // I
+       'icone_gis_tous' => 'Puntos geolocalizados',
+       'info_1_gis' => 'Un punto geolocalizado',
+       'info_1_objet_gis' => '1 objeto asociado a este punto',
+       'info_aucun_gis' => 'Ningún punto geolocalizado',
+       'info_aucun_objet_gis' => 'Ningún objeto asociado a este punto',
+       'info_geolocalisation' => 'Geolocalización',
+       'info_id_objet' => 'N°',
+       'info_liste_gis' => 'Puntos geolocalizados',
+       'info_nb_gis' => '@nb@ puntos geolocalizados',
+       'info_nb_objets_gis' => '@nb@ objetos asociados a este punto',
+       'info_numero_gis' => 'Punto número',
+       'info_objet' => 'Objeto',
+       'info_recherche_gis_zero' => 'Ningún resultados para «@cherche_gis@».',
+       'info_supprimer_lien' => 'Desunir',
+       'info_supprimer_liens' => 'Desunir todos los puntos',
+       'info_voir_fiche_objet' => 'Ver la ficha',
+
+       // L
+       'label_adress' => 'Dirección',
+       'label_code_pays' => 'Código país',
+       'label_code_postal' => 'Código postal',
+       'label_departement' => 'Departamento',
+       'label_import' => 'Importar',
+       'label_inserer_modele_articles' => 'asociados a los artículos',
+       'label_inserer_modele_articles_sites' => 'asociados a los artículos + sitios',
+       'label_inserer_modele_auteurs' => 'asociados a los autores',
+       'label_inserer_modele_centrer_auto' => 'Ningún autocentrado',
+       'label_inserer_modele_centrer_fichier' => 'No centrar el mapa en los archivos KLM/GPX',
+       'label_inserer_modele_controle' => 'Esconder los controles',
+       'label_inserer_modele_controle_type' => 'Esconder los tipos',
+       'label_inserer_modele_description' => 'Descripción',
+       'label_inserer_modele_documents' => 'asociados a los documentos',
+       'label_inserer_modele_echelle' => 'Escala',
+       'label_inserer_modele_fullscreen' => 'Botón de pantalla completa',
+       'label_inserer_modele_gpx' => 'Archivo GPX para sobreponer',
+       'label_inserer_modele_hauteur_carte' => 'Altura del mapa',
+       'label_inserer_modele_identifiant' => 'Identificador',
+       'label_inserer_modele_identifiant_opt' => 'Identificador (opcional)',
+       'label_inserer_modele_identifiant_placeholder' => 'id_gis',
+       'label_inserer_modele_kml' => 'Archivo KML para sobreponer',
+       'label_inserer_modele_kml_gpx' => 'id_document o url',
+       'label_inserer_modele_largeur_carte' => 'Anchura del mapa',
+       'label_inserer_modele_limite' => 'Número máximo de puntos',
+       'label_inserer_modele_localiser_visiteur' => 'Centrar en el visitante',
+       'label_inserer_modele_mini_carte' => 'Mini mapa de situación',
+       'label_inserer_modele_molette' => 'Desactivar la rueda',
+       'label_inserer_modele_mots' => 'asociados a las palabras',
+       'label_inserer_modele_objets' => 'Tipo de punto(s)',
+       'label_inserer_modele_point_gis' => 'punto único registrado',
+       'label_inserer_modele_point_libre' => 'punto único libre',
+       'label_inserer_modele_points' => 'Esconder los puntos',
+       'label_inserer_modele_rubriques' => 'asociados a las secciones',
+       'label_inserer_modele_sites' => 'asociados a los sitios',
+       'label_inserer_modele_titre_carte' => 'Título del mapa',
+       'label_pays' => 'País',
+       'label_rechercher_address' => 'Buscar una dirección',
+       'label_rechercher_point' => 'Buscar un punto',
+       'label_region' => 'Región',
+       'label_ville' => 'Ciudad',
+       'lat' => 'Latitud',
+       'libelle_logo_gis' => 'LOGOTIPO DEL PUNTO',
+       'lien_ajouter_gis' => 'Añadir este punto',
+       'lon' => 'Longitud',
+
+       // T
+       'telecharger_gis' => 'Descargar en formato @format@',
+       'texte_ajouter_gis' => 'Añadir un punto geolocalizado',
+       'texte_creer_associer_gis' => 'Crear y asociar un punto geolocalizado',
+       'texte_creer_gis' => 'Crear un punto geolocalizado',
+       'texte_modifier_gis' => 'Modificar el punto geolocalizado',
+       'texte_voir_gis' => 'Ver el punto geolocalizado',
+       'titre_bloc_creer_point' => 'Asociar un nuevo punto',
+       'titre_bloc_points_lies' => 'Puntos asociados',
+       'titre_bloc_rechercher_point' => 'Buscar un punto',
+       'titre_nombre_utilisation' => 'Una utilización',
+       'titre_nombre_utilisations' => '@nb@ utilizaciones',
+       'titre_nouveau_point' => 'Nuevo punto',
+       'titre_objet' => 'Título',
+       'toolbar_actions_title' => 'Cancelar el trazado',
+       'toolbar_buttons_marker' => 'Trazar un punto',
+       'toolbar_buttons_polygon' => 'Trazar un polígono',
+       'toolbar_buttons_polyline' => 'Trazar una línea',
+       'toolbar_handlers_marker_tooltip_start' => 'Haga clic para situar el marcador',
+       'toolbar_handlers_polygon_tooltip_cont' => 'Haga clic para continuar trazando el polígono',
+       'toolbar_handlers_polygon_tooltip_end' => 'Haga clic sobre el primer punto para cerrar el polígono',
+       'toolbar_handlers_polygon_tooltip_start' => 'Haga clic para comenzar a trazar el polígono',
+       'toolbar_handlers_polyline_tooltip_cont' => 'Haga clic para continuar trazando la línea',
+       'toolbar_handlers_polyline_tooltip_end' => 'Haga clic sobre el último punto para terminar la línea',
+       'toolbar_handlers_polyline_tooltip_start' => 'Haga clic para comenzar a trazar la línea',
+       'toolbar_undo_text' => 'Borrar el última punto',
+       'toolbar_undo_title' => 'Borrar el último punto dibujado',
+
+       // Z
+       'zoom' => 'Zoom'
+);
+
+?>
diff --git a/www/plugins/gis/lang/gis_fr.php b/www/plugins/gis/lang/gis_fr.php
new file mode 100644 (file)
index 0000000..82eebf7
--- /dev/null
@@ -0,0 +1,169 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// Fichier source, a modifier dans svn://zone.spip.org/spip-zone/_plugins_/gis/trunk/lang/
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucun_gis' => 'Aucun point',
+       'aucun_objet' => 'Aucun objet',
+
+       // B
+       'bouton_lier' => 'Lier ce point',
+       'bouton_supprimer_gis' => 'Supprimer définitivement ce point',
+       'bouton_supprimer_lien' => 'Supprimer ce lien',
+
+       // C
+       'cfg_descr_gis' => 'Système d’Information Géographique.<br /><a href="http://contrib.spip.net/4189" class="spip_out">Accéder la documentation</a>.',
+       'cfg_inf_adresse' => 'Affiche des champs supplémentaires d’adresse (pays, ville, région, adresse...)',
+       'cfg_inf_bing' => 'La couche Bing Aerial nécessite une clé à créer sur <a href=\'@url@\' class="spip_out">le site de Bing</a>.',
+       'cfg_inf_cloudmade' => 'Cette API nécessite une clé à créer sur <a href=\'@url@\' class="spip_out">le site de CloudMade</a>.',
+       'cfg_inf_geocoder' => 'Activer les fonctions du geocoder (recherche à partir d’une adresse, récupération de l’adresse à partir des coordonnées).',
+       'cfg_inf_geolocaliser_user_html5' => 'Si le navigateur de l’utilisateur le permet, son emplacement géographique approximatif est récupéré pour donner la position par défaut lors de la création d’un point.',
+       'cfg_inf_google' => 'Cette API nécessite une clé à créer sur <a href=\'@url@\' class="spip_out">le site de GoogleMaps</a>.',
+       'cfg_inf_yandex' => 'Cette API nécessite une clé à créer sur <a href=\'@url@\' class="spip_out">le site de Yandex</a>.',
+       'cfg_lbl_activer_objets' => 'Activer la géolocalisation sur les contenus :',
+       'cfg_lbl_adresse' => 'Afficher les champs d’adresse',
+       'cfg_lbl_api' => 'API de cartographie',
+       'cfg_lbl_api_cloudmade' => 'CloudMade',
+       'cfg_lbl_api_google' => 'Google Maps v2',
+       'cfg_lbl_api_googlev3' => 'Google Maps v3',
+       'cfg_lbl_api_key_bing' => 'Clé Bing',
+       'cfg_lbl_api_key_cloudmade' => 'Clé CloudMade',
+       'cfg_lbl_api_key_google' => 'Clé GoogleMaps',
+       'cfg_lbl_api_key_yandex' => 'Clé Yandex',
+       'cfg_lbl_api_mapquest' => 'MapQuest',
+       'cfg_lbl_api_microsoft' => 'Microsoft Bing',
+       'cfg_lbl_api_openlayers' => 'OpenLayers',
+       'cfg_lbl_api_ovi' => 'Ovi Nokia',
+       'cfg_lbl_api_yandex' => 'Yandex',
+       'cfg_lbl_geocoder' => 'Geocoder',
+       'cfg_lbl_geolocaliser_user_html5' => 'Centrer la carte sur l’emplacement de l’utilisateur à la création',
+       'cfg_lbl_layer_defaut' => 'Couche par défaut',
+       'cfg_lbl_layers' => 'Couches proposées',
+       'cfg_lbl_maptype' => 'Fond cartographique',
+       'cfg_lbl_maptype_carte' => 'Carte',
+       'cfg_lbl_maptype_hybride' => 'Hybride',
+       'cfg_lbl_maptype_relief' => 'Relief',
+       'cfg_lbl_maptype_satellite' => 'Satellite',
+       'cfg_titre_gis' => 'Configuration de GIS',
+
+       // E
+       'editer_gis_editer' => 'Modifier ce point',
+       'editer_gis_nouveau' => 'Créer un nouveau point',
+       'editer_gis_titre' => 'Les points géolocalisés',
+       'erreur_geocoder' => 'Aucun résultat pour votre recherche :',
+       'erreur_recherche_pas_resultats' => 'Aucun point ne correspond à la recherche.',
+       'erreur_xmlrpc_lat_lon' => 'La latitude et la longitude doivent être passées en argument',
+       'explication_api_forcee' => 'L’API est imposée par un autre plugin ou squelette.',
+       'explication_import' => 'Importer un fichier au format GPX ou KML.',
+       'explication_layer_forcee' => 'La couche est imposée par un autre plugin ou un squelette.',
+       'explication_maptype_force' => 'Le fond cartographique est imposé par un autre plugin ou squelette.',
+
+       // F
+       'formulaire_creer_gis' => 'Créer un point géolocalisé :',
+       'formulaire_modifier_gis' => 'Modifier le point géolocalisé :',
+
+       // G
+       'gis_pluriel' => 'Points géolocalisés',
+       'gis_singulier' => 'Point géolocalisé',
+
+       // I
+       'icone_gis_tous' => 'Points géolocalisés',
+       'info_1_gis' => 'Un point géolocalisé',
+       'info_1_objet_gis' => '1 objet lié à ce point',
+       'info_aucun_gis' => 'Aucun point géolocalisé',
+       'info_aucun_objet_gis' => 'Aucun objet lié à ce point',
+       'info_geolocalisation' => 'Géolocalisation',
+       'info_id_objet' => 'N°',
+       'info_liste_gis' => 'Points géolocalisés',
+       'info_nb_gis' => '@nb@ points géolocalisés',
+       'info_nb_objets_gis' => '@nb@ objets liés à ce point',
+       'info_numero_gis' => 'Point numéro',
+       'info_objet' => 'Objet',
+       'info_recherche_gis_zero' => 'Aucun résultat pour « @cherche_gis@ ».',
+       'info_supprimer_lien' => 'Détacher',
+       'info_supprimer_liens' => 'Détacher tous les points',
+       'info_voir_fiche_objet' => 'Voir la fiche',
+
+       // L
+       'label_adress' => 'Adresse',
+       'label_code_pays' => 'Code pays',
+       'label_code_postal' => 'Code postal',
+       'label_departement' => 'Département',
+       'label_import' => 'Importer',
+       'label_inserer_modele_articles' => 'liés aux articles',
+       'label_inserer_modele_articles_sites' => 'liés aux articles + sites',
+       'label_inserer_modele_auteurs' => 'liés aux auteurs',
+       'label_inserer_modele_centrer_auto' => 'Pas de centrage auto',
+       'label_inserer_modele_centrer_fichier' => 'Ne pas centrer la carte sur les fichiers KLM/GPX',
+       'label_inserer_modele_controle' => 'Cacher les contrôles',
+       'label_inserer_modele_controle_type' => 'Cacher les types',
+       'label_inserer_modele_description' => 'Description',
+       'label_inserer_modele_documents' => 'liés aux documents',
+       'label_inserer_modele_echelle' => 'Echelle',
+       'label_inserer_modele_fullscreen' => 'Bouton plein écran',
+       'label_inserer_modele_gpx' => 'Fichier GPX à superposer',
+       'label_inserer_modele_hauteur_carte' => 'Hauteur de la carte',
+       'label_inserer_modele_identifiant' => 'Identifiant',
+       'label_inserer_modele_identifiant_opt' => 'Identifiant (optionnel)',
+       'label_inserer_modele_identifiant_placeholder' => 'id_gis',
+       'label_inserer_modele_kml' => 'Fichier KML à superposer',
+       'label_inserer_modele_kml_gpx' => 'id_document ou url',
+       'label_inserer_modele_largeur_carte' => 'Largeur de la carte',
+       'label_inserer_modele_limite' => 'Nombre de points maximum',
+       'label_inserer_modele_localiser_visiteur' => 'Centrer sur le visiteur',
+       'label_inserer_modele_mini_carte' => 'Mini carte de situation',
+       'label_inserer_modele_molette' => 'Désactiver la molette',
+       'label_inserer_modele_mots' => 'liés aux mots',
+       'label_inserer_modele_objets' => 'Type de point(s)',
+       'label_inserer_modele_point_gis' => 'point unique enregistré',
+       'label_inserer_modele_point_libre' => 'point unique libre',
+       'label_inserer_modele_points' => 'Cacher les points',
+       'label_inserer_modele_rubriques' => 'liés aux rubriques',
+       'label_inserer_modele_sites' => 'liés aux sites',
+       'label_inserer_modele_titre_carte' => 'Titre de la carte',
+       'label_pays' => 'Pays',
+       'label_rechercher_address' => 'Rechercher une adresse',
+       'label_rechercher_point' => 'Rechercher un point',
+       'label_region' => 'Région',
+       'label_ville' => 'Ville',
+       'lat' => 'Latitude',
+       'libelle_logo_gis' => 'LOGO DU POINT',
+       'lien_ajouter_gis' => 'Ajouter ce point',
+       'lon' => 'Longitude',
+
+       // T
+       'telecharger_gis' => 'Télécharger au format @format@',
+       'texte_ajouter_gis' => 'Ajouter un point géolocalisé',
+       'texte_creer_associer_gis' => 'Créer et associer un point géolocalisé',
+       'texte_creer_gis' => 'Créer un point géolocalisé',
+       'texte_modifier_gis' => 'Modifier le point géolocalisé',
+       'texte_voir_gis' => 'Voir le point géolocalisé',
+       'titre_bloc_creer_point' => 'Lier un nouveau point',
+       'titre_bloc_points_lies' => 'Points liés',
+       'titre_bloc_rechercher_point' => 'Rechercher un point',
+       'titre_nombre_utilisation' => 'Une utilisation',
+       'titre_nombre_utilisations' => '@nb@ utilisations',
+       'titre_nouveau_point' => 'Nouveau point',
+       'titre_objet' => 'Titre',
+       'toolbar_actions_title' => 'Annuler le tracé',
+       'toolbar_buttons_marker' => 'Tracer un point',
+       'toolbar_buttons_polygon' => 'Tracer un polygone',
+       'toolbar_buttons_polyline' => 'Tracer une ligne',
+       'toolbar_handlers_marker_tooltip_start' => 'Cliquez pour placer le marqueur',
+       'toolbar_handlers_polygon_tooltip_cont' => 'Cliquez pour continuer à tracer le polygone',
+       'toolbar_handlers_polygon_tooltip_end' => 'Cliquez sur le premier point pour fermer le polygone',
+       'toolbar_handlers_polygon_tooltip_start' => 'Cliquez pour commencer à tracer le polygone',
+       'toolbar_handlers_polyline_tooltip_cont' => 'Cliquez pour continuer à tracer la ligne',
+       'toolbar_handlers_polyline_tooltip_end' => 'Cliquez sur le dernier point pour terminer la ligne',
+       'toolbar_handlers_polyline_tooltip_start' => 'Cliquez pour commencer à tracer la ligne',
+       'toolbar_undo_text' => 'Effacer le dernier point',
+       'toolbar_undo_title' => 'Effacer le dernier point tracé',
+
+       // Z
+       'zoom' => 'Zoom'
+);
+
+?>
diff --git a/www/plugins/gis/lang/gis_nl.php b/www/plugins/gis/lang/gis_nl.php
new file mode 100644 (file)
index 0000000..8c62b01
--- /dev/null
@@ -0,0 +1,171 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/gis?lang_cible=nl
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucun_gis' => 'Geen enkel punt',
+       'aucun_objet' => 'Geen object',
+
+       // B
+       'bouton_lier' => 'Koppel dit punt',
+       'bouton_supprimer_gis' => 'Verwijder dit punt permanent',
+       'bouton_supprimer_lien' => 'Verwijder deze link',
+
+       // C
+       'cfg_descr_gis' => 'Geografisch Informatie Systeem.<br /><a href="http://www.spip-contrib.net/4189" class="spip_out">Naar de documentatie</a>.',
+       'cfg_inf_adresse' => 'Toon extra addresvelden (land, stad, staat, adres ...)',
+       'cfg_inf_bing' => 'De Bing Aerial layer heeft een sleutel nodig die je kunt aanmaken op <a href=\'@url@\' class="spip_out">de Bing website</a>.',
+       'cfg_inf_cloudmade' => 'Deze API heeft een sleutel nodig die je kunt aanmaken op <a href=\'@url@\' class="spip_out">de CloudMade website</a>.',
+       'cfg_inf_geocoder' => 'Maak geocoder functies mogelijk (adres zoeken, adres vinden aan de hand van coördinaten).',
+       'cfg_inf_geolocaliser_user_html5' => 'Als de browser het toestaat, wordt de geografische locatie gebruikt als default positie bij het aanmaken van een nieuw punt.',
+       'cfg_inf_google' => 'Deze API heeft een sleutel nodig die je kunt aanmaken op <a href=\'@url@\' class="spip_out">de GoogleMaps website</a>.',
+       'cfg_inf_yandex' => 'Deze API heeft een sleutel nodig die je kunt aanmaken op <a href=\'@url@\' class="spip_out">de Yandex website</a>.',
+       'cfg_lbl_activer_objets' => 'Maak geotagging mogelijk van inhoud:',
+       'cfg_lbl_adresse' => 'Toon adresvelden',
+       'cfg_lbl_api' => 'Geolocatie API',
+       'cfg_lbl_api_cloudmade' => 'CloudMade',
+       'cfg_lbl_api_google' => 'Google Maps v2',
+       'cfg_lbl_api_googlev3' => 'Google Maps v3',
+       'cfg_lbl_api_key_bing' => 'Bing key',
+       'cfg_lbl_api_key_cloudmade' => 'CloudMade API key',
+       'cfg_lbl_api_key_google' => 'GoogleMaps API key',
+       'cfg_lbl_api_key_yandex' => 'Yandex API key',
+       'cfg_lbl_api_mapquest' => 'MapQuest',
+       'cfg_lbl_api_microsoft' => 'Microsoft Bing',
+       'cfg_lbl_api_openlayers' => 'OpenLayers',
+       'cfg_lbl_api_ovi' => 'Ovi Nokia',
+       'cfg_lbl_api_yandex' => 'Yandex',
+       'cfg_lbl_geocoder' => 'Geocoder',
+       'cfg_lbl_geolocaliser_user_html5' => 'Centreer de map op de locatie van de gebruiker tijdens het aanmaken',
+       'cfg_lbl_layer_defaut' => 'Standaard layer',
+       'cfg_lbl_layers' => 'Voorgestelde layers',
+       'cfg_lbl_maptype' => 'Basiskaart',
+       'cfg_lbl_maptype_carte' => 'Kaart',
+       'cfg_lbl_maptype_hybride' => 'Hybrid',
+       'cfg_lbl_maptype_relief' => 'Relief',
+       'cfg_lbl_maptype_satellite' => 'Satelliet',
+       'cfg_titre_gis' => 'Configuratie van GIS',
+
+       // E
+       'editer_gis_editer' => 'Pas dit punt aan',
+       'editer_gis_nouveau' => 'Maak een nieuw punt',
+       'editer_gis_titre' => 'Locatiepunten',
+       'erreur_geocoder' => 'Geen resultaat voor zoekopdracht:',
+       'erreur_recherche_pas_resultats' => 'Geen enkel punt correspondeert met de gezochte tekst.',
+       'erreur_xmlrpc_lat_lon' => 'Latitude en longitude moeten worden ingevuld',
+       'explication_api_forcee' => 'Dit is vereist door een andere plugin or een ander model.',
+       'explication_import' => 'Importeer een bestand in GPX of KML formaat.',
+       'explication_layer_forcee' => 'De layer is vereist door een andere plugin of een ander model.',
+       'explication_maptype_force' => 'De basiskaart is vereist door een andere plugin of een ander model.',
+
+       // F
+       'formulaire_creer_gis' => 'Maak een nieuw locatiepunt:',
+       'formulaire_modifier_gis' => 'Pas het locatiepunt aan:',
+
+       // G
+       'gis_pluriel' => 'Locatiepunten',
+       'gis_singulier' => 'Locatiepunt',
+
+       // I
+       'icone_gis_tous' => 'Locatiepunten',
+       'info_1_gis' => 'Een locatiepunt',
+       'info_1_objet_gis' => '1 object gekoppeld aan dat punt',
+       'info_aucun_gis' => 'Geen locatie punt',
+       'info_aucun_objet_gis' => 'Geen object gekoppeld aan dat punt',
+       'info_geolocalisation' => 'Geolocatie',
+       'info_id_objet' => 'Nr',
+       'info_liste_gis' => 'Locatiepunten',
+       'info_nb_gis' => '@nb@ location-based punts',
+       'info_nb_objets_gis' => '@nb@ objecten gekoppeld aan dat punt',
+       'info_numero_gis' => 'Punt nummer',
+       'info_objet' => 'Object',
+       'info_recherche_gis_zero' => 'Geen resultaat voor « @cherche_gis@ ».',
+       'info_supprimer_lien' => 'Ontkoppel',
+       'info_supprimer_liens' => 'Ontkoppel alle punten',
+       'info_voir_fiche_objet' => 'Ga naar bladzijde',
+
+       // L
+       'label_adress' => 'Adres',
+       'label_code_pays' => 'Landcode',
+       'label_code_postal' => 'Postcode',
+       'label_departement' => 'Departement',
+       'label_import' => 'Importeer',
+       'label_inserer_modele_articles' => 'gekoppeld aan artikelen',
+       'label_inserer_modele_articles_sites' => 'gekoppeld aan artikelen + websites',
+       'label_inserer_modele_auteurs' => 'gekoppeld aan auteurs',
+       'label_inserer_modele_centrer_auto' => 'Geen automatische centrering',
+       'label_inserer_modele_centrer_fichier' => 'Centreer de kaart niet op KLM/GPX bestanden',
+       'label_inserer_modele_controle' => 'Verberg bedieningsknoppen',
+       'label_inserer_modele_controle_type' => 'Verberg types',
+       'label_inserer_modele_description' => 'Omschrijving',
+       'label_inserer_modele_documents' => 'gekoppeld aan documenten',
+       'label_inserer_modele_echelle' => 'Schaal',
+       'label_inserer_modele_fullscreen' => 'Knop volledig scherm',
+       'label_inserer_modele_gpx' => 'GPX bestand naar overlay',
+       'label_inserer_modele_hauteur_carte' => 'Kaarthoogte',
+       'label_inserer_modele_identifiant' => 'ID',
+       'label_inserer_modele_identifiant_opt' => 'ID (optioneel)',
+       'label_inserer_modele_identifiant_placeholder' => 'id_gis',
+       'label_inserer_modele_kml' => 'KML bestanden naar overlay',
+       'label_inserer_modele_kml_gpx' => 'id_document of url',
+       'label_inserer_modele_largeur_carte' => 'Kaartbreedte',
+       'label_inserer_modele_limite' => 'Maximum aantal punten',
+       'label_inserer_modele_localiser_visiteur' => 'Centreer op lokatie bezoeker',
+       'label_inserer_modele_mini_carte' => 'Mini situatiekaart',
+       'label_inserer_modele_molette' => 'Schakel het scroll-wiel uit',
+       'label_inserer_modele_mots' => 'Gekoppeld aan woorden',
+       'label_inserer_modele_objets' => 'Categorie van punt(en)',
+       'label_inserer_modele_point_gis' => 'enkel punt geregistreerd',
+       'label_inserer_modele_point_libre' => 'enkel vrij punt',
+       'label_inserer_modele_points' => 'Verberg punten',
+       'label_inserer_modele_rubriques' => 'gekoppeld aan rubrieken',
+       'label_inserer_modele_sites' => 'gekoppeld aan websites',
+       'label_inserer_modele_titre_carte' => 'Kaarttitel',
+       'label_pays' => 'Land',
+       'label_rechercher_address' => 'Zoek een adres',
+       'label_rechercher_point' => 'Zoek een punt',
+       'label_region' => 'Regio',
+       'label_ville' => 'Stad',
+       'lat' => 'Breedtegraad',
+       'libelle_logo_gis' => 'LOGO VAN HET PUNT',
+       'lien_ajouter_gis' => 'Voeg dit punt toe',
+       'lon' => 'Lengtegraad',
+
+       // T
+       'telecharger_gis' => 'Download in @format@ formaat',
+       'texte_ajouter_gis' => 'Voeg een locatiepunt toe',
+       'texte_creer_associer_gis' => 'Maak en link een locatiepunt',
+       'texte_creer_gis' => 'Maak een locatiepunt',
+       'texte_modifier_gis' => 'Pas het locatiepunt aan',
+       'texte_voir_gis' => 'Toon het locatiepunt',
+       'titre_bloc_creer_point' => 'Koppel een nieuw punt',
+       'titre_bloc_points_lies' => 'Gekoppelde punten',
+       'titre_bloc_rechercher_point' => 'Zoek een punt',
+       'titre_nombre_utilisation' => 'Eén toepassing',
+       'titre_nombre_utilisations' => '@nb@ toepassingen',
+       'titre_nouveau_point' => 'Nieuw punt',
+       'titre_objet' => 'Titel',
+       'toolbar_actions_title' => 'Annuleer de tekening',
+       'toolbar_buttons_marker' => 'Plot een punt',
+       'toolbar_buttons_polygon' => 'Teken een polygoon',
+       'toolbar_buttons_polyline' => 'Teken een lijn',
+       'toolbar_handlers_marker_tooltip_start' => 'Klik om de marker in te stellen',
+       'toolbar_handlers_polygon_tooltip_cont' => 'Klik om de polygoon verder te tekenen',
+       'toolbar_handlers_polygon_tooltip_end' => 'Klik op het eerste punt om de polygoon te sluiten',
+       'toolbar_handlers_polygon_tooltip_start' => 'Klik om een polygoon te gaan tekenen',
+       'toolbar_handlers_polyline_tooltip_cont' => 'Klik om de lijn verder te tekenen',
+       'toolbar_handlers_polyline_tooltip_end' => 'Klik het laatste punt voor het einde van de lijn',
+       'toolbar_handlers_polyline_tooltip_start' => 'Klik om een lijn te gaan tekenen',
+       'toolbar_undo_text' => 'Wis het laatste punt',
+       'toolbar_undo_title' => 'Wis het laatst uitgestippelde punt',
+
+       // Z
+       'zoom' => 'Zoom'
+);
+
+?>
diff --git a/www/plugins/gis/lang/gis_ru.php b/www/plugins/gis/lang/gis_ru.php
new file mode 100644 (file)
index 0000000..0e8d01f
--- /dev/null
@@ -0,0 +1,155 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/gis?lang_cible=ru
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucun_gis' => 'Нет ни одной точки на карте',
+       'aucun_objet' => 'Нет связанных объектов',
+
+       // B
+       'bouton_lier' => 'Связать точку',
+       'bouton_supprimer_gis' => 'Удалить точку',
+       'bouton_supprimer_lien' => 'Удалить связь',
+
+       // C
+       'cfg_descr_gis' => 'Географическая Информационная Система (GIS).<br /><a href="http://contrib.spip.net/3887" class="spip_out">Документация</a>.', # MODIF
+       'cfg_inf_adresse' => 'Показываются дополнительные поля для ввода адреса (страна, город, область, адрес...)',
+       'cfg_inf_bing' => 'Для использования карты Bing Aerial необходимо создать ключ  <a href=\'@url@\' class="spip_out">на сайте Bing</a>.',
+       'cfg_inf_cloudmade' => 'Для использования карты необходимо создать ключ <a href=\'@url@\' class="spip_out">на сайте CloudMade</a>.',
+       'cfg_inf_geocoder' => 'Включить функцию геопоиска (поиск точки на карте по адресу).',
+       'cfg_inf_geolocaliser_user_html5' => 'Новая карта центрируется по расположению пользователя ( если позволяет его браузер).',
+       'cfg_inf_google' => 'Для работы с картой необходим API ключ, который  можно создать на <a href=\'@url@\' class="spip_out">сайте  GoogleMaps</a>.',
+       'cfg_inf_yandex' => 'Для работы с картой необходим API ключ. <a href=\'@url@\' class="spip_out"> Получить на сайте Yandex</a>.',
+       'cfg_lbl_activer_objets' => 'Связывать карту с объектами:',
+       'cfg_lbl_adresse' => 'Показать поля для адреса',
+       'cfg_lbl_api' => 'Используемое API',
+       'cfg_lbl_api_cloudmade' => 'CloudMade',
+       'cfg_lbl_api_google' => 'Google Maps v2',
+       'cfg_lbl_api_googlev3' => 'Google Maps v3',
+       'cfg_lbl_api_key_bing' => 'API ключ  Bing',
+       'cfg_lbl_api_key_cloudmade' => 'API ключ CloudMade ',
+       'cfg_lbl_api_key_google' => 'API ключ  GoogleMaps',
+       'cfg_lbl_api_key_yandex' => 'Yandex API ключ',
+       'cfg_lbl_api_mapquest' => 'MapQuest',
+       'cfg_lbl_api_microsoft' => 'Microsoft Bing',
+       'cfg_lbl_api_openlayers' => 'OpenLayers',
+       'cfg_lbl_api_ovi' => 'Ovi Nokia',
+       'cfg_lbl_api_yandex' => 'Yandex',
+       'cfg_lbl_geocoder' => 'Geocoder',
+       'cfg_lbl_geolocaliser_user_html5' => 'Центрировать карту по месту расположения пользователя, создавшего карту',
+       'cfg_lbl_layer_defaut' => 'Слой по умолчанию',
+       'cfg_lbl_layers' => 'Предложенные слои',
+       'cfg_lbl_maptype' => 'Тип карты',
+       'cfg_lbl_maptype_carte' => 'Карта',
+       'cfg_lbl_maptype_hybride' => 'Гибрид',
+       'cfg_lbl_maptype_relief' => 'Рельеф',
+       'cfg_lbl_maptype_satellite' => 'Спутник',
+       'cfg_titre_gis' => 'GIS', # MODIF
+
+       // E
+       'editer_gis_editer' => 'Изменить точку',
+       'editer_gis_nouveau' => 'Создать точку',
+       'editer_gis_titre' => 'Точки на карте',
+       'erreur_recherche_pas_resultats' => 'Нет точек, соответствующих поисковому запросу.',
+       'erreur_xmlrpc_lat_lon' => 'В качестве аргумента должна быть указанна долгота и широта',
+       'explication_api_forcee' => 'На API накладывается другой плагин или шаблон.',
+       'explication_import' => 'Импортировать GPX или KML файл.',
+       'explication_layer_forcee' => 'На слой накладывается другой плагин или шаблон.',
+       'explication_maptype_force' => 'На базовую карту накладывается другой плагин или шаблон.',
+
+       // F
+       'formulaire_creer_gis' => 'Создание новой точки :',
+       'formulaire_modifier_gis' => 'Изменить точку :',
+
+       // G
+       'gis_pluriel' => 'Точки на карте',
+       'gis_singulier' => 'Точка на карте',
+
+       // I
+       'icone_gis_tous' => 'Точки на карте',
+       'info_1_gis' => 'Точка на карте',
+       'info_1_objet_gis' => '1 материал связан с точкой',
+       'info_aucun_gis' => 'Нет точек на карте',
+       'info_aucun_objet_gis' => 'У точки нет связанных материалов',
+       'info_geolocalisation' => 'Расположение (Geolocation)',
+       'info_id_objet' => 'N°',
+       'info_liste_gis' => 'Точки на карте',
+       'info_nb_gis' => '@nb@ точек на карте',
+       'info_nb_objets_gis' => '@nb@ объектов связано с точкой',
+       'info_numero_gis' => 'ID точки',
+       'info_objet' => 'Объект',
+       'info_recherche_gis_zero' => 'Ничего не найдено по запросу « @cherche_gis@ ».',
+       'info_supprimer_lien' => 'Убрать',
+       'info_supprimer_liens' => 'Убрать все точки',
+       'info_voir_fiche_objet' => 'Перейти на страницу',
+
+       // L
+       'label_adress' => 'Адрес',
+       'label_code_postal' => 'Индекс',
+       'label_import' => 'Импорт',
+       'label_inserer_modele_articles' => 'связано со статьями',
+       'label_inserer_modele_articles_sites' => 'связано с авторами и сайтами',
+       'label_inserer_modele_auteurs' => 'связано с авторами',
+       'label_inserer_modele_centrer_auto' => 'Без автоматического центрирования',
+       'label_inserer_modele_centrer_fichier' => 'Не центрировать карту по KLM/GPX файлу.',
+       'label_inserer_modele_controle' => 'Спрятать управление картой',
+       'label_inserer_modele_controle_type' => 'Спрятать выбор типа карты',
+       'label_inserer_modele_description' => 'Описание',
+       'label_inserer_modele_documents' => 'связано с документами',
+       'label_inserer_modele_echelle' => 'Масштаб',
+       'label_inserer_modele_fullscreen' => 'Переход в полноэкранный режим',
+       'label_inserer_modele_gpx' => 'GPX файл для наложения',
+       'label_inserer_modele_hauteur_carte' => 'Высота карта',
+       'label_inserer_modele_identifiant' => 'ID',
+       'label_inserer_modele_identifiant_opt' => 'ID (не обязательно)',
+       'label_inserer_modele_identifiant_placeholder' => 'id_gis',
+       'label_inserer_modele_kml' => 'KML файл для наложения',
+       'label_inserer_modele_kml_gpx' => 'id_document или url',
+       'label_inserer_modele_largeur_carte' => 'Ширина карты',
+       'label_inserer_modele_limite' => 'Максимальное количество точек',
+       'label_inserer_modele_localiser_visiteur' => 'Центрировать по посетителю',
+       'label_inserer_modele_mini_carte' => 'Мини карта',
+       'label_inserer_modele_molette' => 'Отключить прокрутку колесиком мышки',
+       'label_inserer_modele_mots' => 'связано с ключами',
+       'label_inserer_modele_objets' => 'Виды точек',
+       'label_inserer_modele_point_gis' => 'записана одиночная точка',
+       'label_inserer_modele_point_libre' => 'свободная точка',
+       'label_inserer_modele_points' => 'Спрятать точки',
+       'label_inserer_modele_rubriques' => 'связано с разделами',
+       'label_inserer_modele_sites' => 'связано с сайтами',
+       'label_inserer_modele_titre_carte' => 'Название карты',
+       'label_pays' => 'Страна',
+       'label_rechercher_address' => 'Искать по адресу',
+       'label_rechercher_point' => 'Найти точку',
+       'label_region' => 'Область',
+       'label_ville' => 'Город',
+       'lat' => 'Широта',
+       'libelle_logo_gis' => 'Лого точки',
+       'lien_ajouter_gis' => 'Добавить точку',
+       'lon' => 'Долгота',
+
+       // T
+       'telecharger_gis' => 'Скачать в @format@ формате',
+       'texte_ajouter_gis' => 'Добавить точку на карте',
+       'texte_creer_associer_gis' => 'Создать точку и связать ее',
+       'texte_creer_gis' => 'Создать точку',
+       'texte_modifier_gis' => 'Изменить точку',
+       'texte_voir_gis' => 'Показать точку на карте',
+       'titre_bloc_creer_point' => 'Новая точка на карте',
+       'titre_bloc_points_lies' => 'Связанные точки',
+       'titre_bloc_rechercher_point' => 'Найти существующую точку',
+       'titre_nombre_utilisation' => 'Используется 1 раз',
+       'titre_nombre_utilisations' => 'используется @nb@ раз',
+       'titre_nouveau_point' => 'Новая точка',
+       'titre_objet' => 'Название',
+
+       // Z
+       'zoom' => 'Zoom'
+);
+
+?>
diff --git a/www/plugins/gis/lang/gis_sk.php b/www/plugins/gis/lang/gis_sk.php
new file mode 100644 (file)
index 0000000..0b7c38b
--- /dev/null
@@ -0,0 +1,171 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/gis?lang_cible=sk
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucun_gis' => 'Žiaden bod',
+       'aucun_objet' => 'Žiaden objekt',
+
+       // B
+       'bouton_lier' => 'Prepojiť tento bod',
+       'bouton_supprimer_gis' => 'Natrvalo odstrániť tento bod',
+       'bouton_supprimer_lien' => 'Odstrániť tento odkaz',
+
+       // C
+       'cfg_descr_gis' => 'Geografický informačný systém.<br /><a href="http://contrib.spip.net/4189" class="spip_out">Prejsť na dokumentáciu.</a>',
+       'cfg_inf_adresse' => 'Zobrazí ďalšie polia adresy (krajinu, mesto, štát, adresu a pod.)',
+       'cfg_inf_bing' => 'Vrstva Bing Aerial si vyžaduje, aby ste <a href=\'@url@\' class="spip_out">na stránke vyhľadávača Bing</a> vytvorili kľúč.',
+       'cfg_inf_cloudmade' => 'Táto aplikácia potrebuje kľúč na vytvorenie <a href=\'@url@\' class="spip_out">stránky v Cloude.</a>',
+       'cfg_inf_geocoder' => 'Aktivovať funkciu geokódera (vyhľadávanie z jednej adresy, zistenie adresy zo súradníc).',
+       'cfg_inf_geolocaliser_user_html5' => 'Ak to povoľuje prehliadač používateľa, na určenie predvolenej polohy pri vytváraní nového bodu sa ukladá približná geografická poloha používateľa.',
+       'cfg_inf_google' => 'Táto aplikácia potrebuje kľúč, ktorý si treba vytvoriť na <a href=\'@url@\' class="spip_out">stránke GoogleMaps.</a>',
+       'cfg_inf_yandex' => 'Táto aplikácia potrebuje kľúč na vytvorenie <a href=\'@url@\' class="spip_out">stránky v Yandexe.</a>',
+       'cfg_lbl_activer_objets' => 'Aktivovať geolokalizáciu obsahu:',
+       'cfg_lbl_adresse' => 'Zobraziť polia adresy',
+       'cfg_lbl_api' => 'Geolokačná API',
+       'cfg_lbl_api_cloudmade' => 'CloudMade',
+       'cfg_lbl_api_google' => 'Google Maps v2',
+       'cfg_lbl_api_googlev3' => 'Google Maps v3',
+       'cfg_lbl_api_key_bing' => 'Kľúč pre Bing',
+       'cfg_lbl_api_key_cloudmade' => 'Kľúč CloudMade',
+       'cfg_lbl_api_key_google' => 'Kľúč GoogleMaps',
+       'cfg_lbl_api_key_yandex' => 'Kľúč Yandex',
+       'cfg_lbl_api_mapquest' => 'MapQuest',
+       'cfg_lbl_api_microsoft' => 'Microsoft Bing',
+       'cfg_lbl_api_openlayers' => 'OpenLayers',
+       'cfg_lbl_api_ovi' => 'Ovi Nokia',
+       'cfg_lbl_api_yandex' => 'Yandex',
+       'cfg_lbl_geocoder' => 'Geocoder',
+       'cfg_lbl_geolocaliser_user_html5' => 'Pri vytváraní vycentrujte mapu na polohe používateľa',
+       'cfg_lbl_layer_defaut' => 'Predvolená vrstva',
+       'cfg_lbl_layers' => 'Navrhované vrstvy',
+       'cfg_lbl_maptype' => 'Základná mapa',
+       'cfg_lbl_maptype_carte' => 'Mapa',
+       'cfg_lbl_maptype_hybride' => 'Zmiešaná',
+       'cfg_lbl_maptype_relief' => 'Reliéf',
+       'cfg_lbl_maptype_satellite' => 'Satelitná',
+       'cfg_titre_gis' => 'GIS', # MODIF
+
+       // E
+       'editer_gis_editer' => 'Upraviť tento bod',
+       'editer_gis_nouveau' => 'Vytvoriť nový bod',
+       'editer_gis_titre' => 'Geolokalizované body',
+       'erreur_geocoder' => 'Žiaden výsledok k vášmu vyhľadávaniu:',
+       'erreur_recherche_pas_resultats' => 'Vyhľadávania sa netýka žiaden bod.',
+       'erreur_xmlrpc_lat_lon' => 'Zemepisná šírka a dĺžka musia byť odovzdané ako parameter',
+       'explication_api_forcee' => 'Túto aplikáciu používa iný zásuvný modul alebo iná šablóna.',
+       'explication_import' => 'Nahrá súbor vo formáte GPX alebo KML.',
+       'explication_layer_forcee' => 'Vrstvu zaviedol iný zásuvný modul alebo iná šablóna.',
+       'explication_maptype_force' => 'Základnú mapu si vyžaduje iný zásuvný modul alebo šablóna.',
+
+       // F
+       'formulaire_creer_gis' => 'Vytvoriť geolokalizovaný bod:',
+       'formulaire_modifier_gis' => 'Upraviť geolokalizovaný bod:',
+
+       // G
+       'gis_pluriel' => 'Geolokalizované body',
+       'gis_singulier' => 'Geolokalizovaný bod',
+
+       // I
+       'icone_gis_tous' => 'Geolokalizované body',
+       'info_1_gis' => 'Jeden geolokalizovaný bod',
+       'info_1_objet_gis' => '1 objekt prepojený s týmto bodom',
+       'info_aucun_gis' => 'Žiaden geolokalizovaný bod',
+       'info_aucun_objet_gis' => 'Žiaden objekt prepojený s týmto bodom',
+       'info_geolocalisation' => 'Geolokalizácia',
+       'info_id_objet' => 'Č.',
+       'info_liste_gis' => 'Geolokalizované body',
+       'info_nb_gis' => '@nb@ geolokalizovaných bodov',
+       'info_nb_objets_gis' => '@nb@ objektov prepojených s týmto bodom',
+       'info_numero_gis' => 'Bod číslo',
+       'info_objet' => 'Objekt',
+       'info_recherche_gis_zero' => 'Žiadne výsledky pre "@cherche_gis@".',
+       'info_supprimer_lien' => 'Zrušiť prepojenie',
+       'info_supprimer_liens' => 'Zrušiť všetky body',
+       'info_voir_fiche_objet' => 'Prejsť na stránku',
+
+       // L
+       'label_adress' => 'Adresa',
+       'label_code_pays' => 'Kód krajiny',
+       'label_code_postal' => 'PSČ',
+       'label_departement' => 'Kraj',
+       'label_import' => 'Nahrať',
+       'label_inserer_modele_articles' => 'prepojené s článkami',
+       'label_inserer_modele_articles_sites' => 'prepojené s článkami a stránkami',
+       'label_inserer_modele_auteurs' => 'prepojené s autormi',
+       'label_inserer_modele_centrer_auto' => 'Nevystreďovať automaticky',
+       'label_inserer_modele_centrer_fichier' => 'Nedávať mapu na súbory KLM alebo GPX',
+       'label_inserer_modele_controle' => 'Schovať ovládacie prvky',
+       'label_inserer_modele_controle_type' => 'Skryť typy',
+       'label_inserer_modele_description' => 'Opis',
+       'label_inserer_modele_documents' => 'prepojené s dokumentmi',
+       'label_inserer_modele_echelle' => 'Mierka',
+       'label_inserer_modele_fullscreen' => 'Tlačidlo "Na celú obrazovka"',
+       'label_inserer_modele_gpx' => 'Prekryť súborom GPX',
+       'label_inserer_modele_hauteur_carte' => 'Výška mapy',
+       'label_inserer_modele_identifiant' => 'Identifikátor',
+       'label_inserer_modele_identifiant_opt' => 'Identifikátor (nepovinné)',
+       'label_inserer_modele_identifiant_placeholder' => 'id_gis',
+       'label_inserer_modele_kml' => 'Prekryť súborom KML',
+       'label_inserer_modele_kml_gpx' => 'id_document alebo url',
+       'label_inserer_modele_largeur_carte' => 'Šírka mapy',
+       'label_inserer_modele_limite' => 'Maximálny počet bodov',
+       'label_inserer_modele_localiser_visiteur' => 'Zacieliť na návštevníka',
+       'label_inserer_modele_mini_carte' => 'Malá situačná mapa',
+       'label_inserer_modele_molette' => 'Vypnúť koliesko',
+       'label_inserer_modele_mots' => 'prepojené so slovami',
+       'label_inserer_modele_objets' => 'Typ bodov',
+       'label_inserer_modele_point_gis' => 'jeden zaregistrovaný bod',
+       'label_inserer_modele_point_libre' => 'jeden voľný bod',
+       'label_inserer_modele_points' => 'Schovať body',
+       'label_inserer_modele_rubriques' => 'prepojené s rubrikami',
+       'label_inserer_modele_sites' => 'prepojené so stránkami',
+       'label_inserer_modele_titre_carte' => 'Názov mapy',
+       'label_pays' => 'Krajina',
+       'label_rechercher_address' => 'Vyhľadať adresu',
+       'label_rechercher_point' => 'Vyhľadať bod',
+       'label_region' => 'Región (kraj)',
+       'label_ville' => 'Mesto',
+       'lat' => 'Zemepisná šírka',
+       'libelle_logo_gis' => 'LOGO BODU',
+       'lien_ajouter_gis' => 'Pridať tento bod',
+       'lon' => 'Zemepisná dĺžka',
+
+       // T
+       'telecharger_gis' => 'Stiahnuť vo formáte @format@',
+       'texte_ajouter_gis' => 'Pridať geolokalizovaný bod',
+       'texte_creer_associer_gis' => 'Vytvoriť a prepojiť geolokalizovaný bod',
+       'texte_creer_gis' => 'Vytvoriť geolokalizovaný bod',
+       'texte_modifier_gis' => 'Upraviť geolokalizovaný bod',
+       'texte_voir_gis' => 'Zobraziť geolokalizovaný bod',
+       'titre_bloc_creer_point' => 'Prepojiť nový bod',
+       'titre_bloc_points_lies' => 'Prepojené body',
+       'titre_bloc_rechercher_point' => 'Vyhľadať bod',
+       'titre_nombre_utilisation' => 'Jedno použitie',
+       'titre_nombre_utilisations' => '@nb@ použití',
+       'titre_nouveau_point' => 'Nový bod',
+       'titre_objet' => 'Názov',
+       'toolbar_actions_title' => 'Zrušiť trasu',
+       'toolbar_buttons_marker' => 'Nakresliť bod',
+       'toolbar_buttons_polygon' => 'Nakresliť mnohouholník',
+       'toolbar_buttons_polyline' => 'Nakresliť čiaru',
+       'toolbar_handlers_marker_tooltip_start' => 'Ak chcete vložiť značku, kliknite sem',
+       'toolbar_handlers_polygon_tooltip_cont' => 'Kliknite, ak chcete pokračovať v kreslení mnohouholníka',
+       'toolbar_handlers_polygon_tooltip_end' => 'Kliknite na prvý bod, aby bol mnohouholník uzavretý',
+       'toolbar_handlers_polygon_tooltip_start' => 'Ak chcete začať kresliť mnohouholník, kliknite sem',
+       'toolbar_handlers_polyline_tooltip_cont' => 'Ak chcete pokračovať v kreslení čiary, kliknite sem',
+       'toolbar_handlers_polyline_tooltip_end' => 'Kliknite na posledný bod, aby bola čiara ukončená',
+       'toolbar_handlers_polyline_tooltip_start' => 'Ak chcete začať kresliť čiaru, kliknite sem',
+       'toolbar_undo_text' => 'Vymazať posledný bod',
+       'toolbar_undo_title' => 'Vymazať posledný nakreslený bod',
+
+       // Z
+       'zoom' => 'Lupa'
+);
+
+?>
diff --git a/www/plugins/gis/lang/paquet-gis.xml b/www/plugins/gis/lang/paquet-gis.xml
new file mode 100644 (file)
index 0000000..12799ff
--- /dev/null
@@ -0,0 +1,19 @@
+<traduction module="paquet-gis" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/gis/trunk/lang/" reference="fr">
+       <langue code="en" url="http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=en" total="2" traduits="2" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="kent1" lien="http://trad.spip.net/auteur/kent1" />
+       </langue>
+       <langue code="es" url="http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=es" total="2" traduits="2" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Raquel S. Bujaldón" lien="http://trad.spip.net/auteur/raquel-s-bujaldon" />
+       </langue>
+       <langue code="fr" url="http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=fr" total="2" traduits="2" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       </langue>
+       <langue code="nl" url="http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=nl" total="2" traduits="2" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
+       </langue>
+       <langue code="ru" url="http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=ru" total="2" traduits="2" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Serge Markitanenko" lien="http://trad.spip.net/auteur/serge-markitanenko" />
+       </langue>
+       <langue code="sk" url="http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=sk" total="2" traduits="2" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="jaro" lien="http://trad.spip.net/auteur/jaro" />
+       </langue>
+</traduction>
diff --git a/www/plugins/gis/lang/paquet-gis_en.php b/www/plugins/gis/lang/paquet-gis_en.php
new file mode 100644 (file)
index 0000000..9a87053
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=en
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // G
+       'gis_description' => 'This plugin allows you to create location-based points that can be attached to SPIP objects to display them on maps in the pages of your site. They can use the tiles from different maps vendors through the Leaflet library.',
+       'gis_slogan' => 'Geographic Information System'
+);
+
+?>
diff --git a/www/plugins/gis/lang/paquet-gis_es.php b/www/plugins/gis/lang/paquet-gis_es.php
new file mode 100644 (file)
index 0000000..c433e2f
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=es
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // G
+       'gis_description' => 'Este plugin permite crear puntos geolocalizados que pueden adjuntarse a los objetos de SPIP a fin de mostrarlos en los mapas de las páginas de su sitio web. Éstos útlimos pueden utilizar las tejas de diferentes proveedores gracias a la librería Leaflet. ',
+       'gis_slogan' => 'Sistema de información geográfica'
+);
+
+?>
diff --git a/www/plugins/gis/lang/paquet-gis_fr.php b/www/plugins/gis/lang/paquet-gis_fr.php
new file mode 100644 (file)
index 0000000..efdd91a
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// Fichier source, a modifier dans svn://zone.spip.org/spip-zone/_plugins_/gis/trunk/lang/
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // G
+       'gis_description' => 'Ce plugin permet de créer des points géolocalisés qui peuvent être attachés aux objets de SPIP afin de les afficher sur des cartes dans les pages de votre site. Ces dernières peuvent utiliser les tuiles de différents fournisseurs grâce à la librairie Leaflet.',
+       'gis_slogan' => 'Système d’information géographique'
+);
+
+?>
diff --git a/www/plugins/gis/lang/paquet-gis_nl.php b/www/plugins/gis/lang/paquet-gis_nl.php
new file mode 100644 (file)
index 0000000..442fb70
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=nl
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // G
+       'gis_description' => 'Deze plugin laat je locatiepunten maken die aan SPIP-objecten kunnen worden gekoppeld om een kaart te tonen. Verschillende soorten kaarten zijn mogelijk dankzij de Leaflet bibliotheek.',
+       'gis_slogan' => 'Geografisch Informatie Systeem'
+);
+
+?>
diff --git a/www/plugins/gis/lang/paquet-gis_ru.php b/www/plugins/gis/lang/paquet-gis_ru.php
new file mode 100644 (file)
index 0000000..5a85d3b
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=ru
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // G
+       'gis_description' => 'Плагин GIS позволяет создавать элементы на карте и привязвать их к существующим объектам SPIP. Вы можете использовать созданные карты на страницах своего сайта. Могут использовать разные карты, доступ к которым обеспечивается при помощи библиотеки Leaflet.',
+       'gis_slogan' => 'Географическая  Информационная Система (GIS)'
+);
+
+?>
diff --git a/www/plugins/gis/lang/paquet-gis_sk.php b/www/plugins/gis/lang/paquet-gis_sk.php
new file mode 100644 (file)
index 0000000..50cfd09
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=sk
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // G
+       'gis_description' => 'Tento zásuvný modul umožňuje vytvárať zemepisné body, ktoré môžete pripojiť k objektom SPIPu, aby sa dali zobraziť na mapách na stránkach vášho webu. Vďaka knižnici Leaflet môžete používať rozhranie od rôznych výrobcov.',
+       'gis_slogan' => 'Geografický informačný systém'
+);
+
+?>
diff --git a/www/plugins/gis/lib/leaflet/LICENSE b/www/plugins/gis/lib/leaflet/LICENSE
new file mode 100644 (file)
index 0000000..46cd121
--- /dev/null
@@ -0,0 +1,23 @@
+Copyright (c) 2010-2013, Vladimir Agafonkin\r
+Copyright (c) 2010-2011, CloudMade\r
+All rights reserved.\r
+\r
+Redistribution and use in source and binary forms, with or without modification, are\r
+permitted provided that the following conditions are met:\r
+\r
+   1. Redistributions of source code must retain the above copyright notice, this list of\r
+      conditions and the following disclaimer.\r
+\r
+   2. Redistributions in binary form must reproduce the above copyright notice, this list\r
+      of conditions and the following disclaimer in the documentation and/or other materials\r
+      provided with the distribution.\r
+\r
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY\r
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\r
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\r
+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\r
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\r
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
diff --git a/www/plugins/gis/lib/leaflet/README.md b/www/plugins/gis/lib/leaflet/README.md
new file mode 100644 (file)
index 0000000..add1f0e
--- /dev/null
@@ -0,0 +1,33 @@
+Sources utilisées dans la lib :\r
+\r
+* dist/\r
+       * http://leafletjs.com/download.html\r
+* plugins/\r
+       * Bing.js\r
+               * https://github.com/shramov/leaflet-plugins/blob/master/layer/tile/Bing.js\r
+       * Control.FullScreen.js\r
+               * https://github.com/brunob/leaflet.fullscreen/blob/master/Control.FullScreen.js\r
+       * Control.MiniMap.js\r
+               * https://github.com/Norkart/Leaflet-MiniMap/blob/master/src/Control.MiniMap.js\r
+       * Google.js\r
+               * https://github.com/shramov/leaflet-plugins/blob/master/layer/tile/Google.js\r
+       * GPX.js\r
+               * https://github.com/shramov/leaflet-plugins/blob/master/layer/vector/GPX.js\r
+       * GPX.Speed.js\r
+               * https://github.com/shramov/leaflet-plugins/blob/master/layer/vector/GPX.Speed.js\r
+       * images\r
+               * mixed from all sources\r
+       * KML.js\r
+               * https://github.com/shramov/leaflet-plugins/blob/master/layer/vector/KML.js\r
+       * leaflet.markercluster.css\r
+               * https://github.com/Leaflet/Leaflet.markercluster/blob/master/dist/MarkerCluster.css\r
+               * https://github.com/Leaflet/Leaflet.markercluster/blob/master/dist/MarkerCluster.Default.css\r
+       * leaflet.markercluster-src.js\r
+               * https://github.com/Leaflet/Leaflet.markercluster/blob/master/dist/leaflet.markercluster-src.js\r
+       * leaflet-plugins.css\r
+               * https://github.com/brunob/leaflet.fullscreen/blob/master/Control.FullScreen.css\r
+               * https://github.com/Norkart/Leaflet-MiniMap/blob/master/src/Control.MiniMap.css\r
+       * leaflet-providers.js\r
+               * https://github.com/leaflet-extras/leaflet-providers/blob/master/leaflet-providers.js\r
+       * Marker.Rotate.js\r
+               * https://github.com/shramov/leaflet-plugins/blob/master/layer/Marker.Rotate.js\r
diff --git a/www/plugins/gis/lib/leaflet/dist/images/layers-2x.png b/www/plugins/gis/lib/leaflet/dist/images/layers-2x.png
new file mode 100644 (file)
index 0000000..a2cf7f9
Binary files /dev/null and b/www/plugins/gis/lib/leaflet/dist/images/layers-2x.png differ
diff --git a/www/plugins/gis/lib/leaflet/dist/images/layers.png b/www/plugins/gis/lib/leaflet/dist/images/layers.png
new file mode 100644 (file)
index 0000000..bca0a0e
Binary files /dev/null and b/www/plugins/gis/lib/leaflet/dist/images/layers.png differ
diff --git a/www/plugins/gis/lib/leaflet/dist/images/marker-icon-2x.png b/www/plugins/gis/lib/leaflet/dist/images/marker-icon-2x.png
new file mode 100644 (file)
index 0000000..0015b64
Binary files /dev/null and b/www/plugins/gis/lib/leaflet/dist/images/marker-icon-2x.png differ
diff --git a/www/plugins/gis/lib/leaflet/dist/images/marker-icon.png b/www/plugins/gis/lib/leaflet/dist/images/marker-icon.png
new file mode 100644 (file)
index 0000000..e2e9f75
Binary files /dev/null and b/www/plugins/gis/lib/leaflet/dist/images/marker-icon.png differ
diff --git a/www/plugins/gis/lib/leaflet/dist/images/marker-shadow.png b/www/plugins/gis/lib/leaflet/dist/images/marker-shadow.png
new file mode 100644 (file)
index 0000000..d1e773c
Binary files /dev/null and b/www/plugins/gis/lib/leaflet/dist/images/marker-shadow.png differ
diff --git a/www/plugins/gis/lib/leaflet/dist/leaflet-src.js b/www/plugins/gis/lib/leaflet/dist/leaflet-src.js
new file mode 100644 (file)
index 0000000..640ef73
--- /dev/null
@@ -0,0 +1,9180 @@
+/*
+ Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com
+ (c) 2010-2013, Vladimir Agafonkin
+ (c) 2010-2011, CloudMade
+*/
+(function (window, document, undefined) {\r
+var oldL = window.L,\r
+    L = {};\r
+\r
+L.version = '0.7.3';\r
+\r
+// define Leaflet for Node module pattern loaders, including Browserify\r
+if (typeof module === 'object' && typeof module.exports === 'object') {\r
+       module.exports = L;\r
+\r
+// define Leaflet as an AMD module\r
+} else if (typeof define === 'function' && define.amd) {\r
+       define(L);\r
+}\r
+\r
+// define Leaflet as a global L variable, saving the original L to restore later if needed\r
+\r
+L.noConflict = function () {\r
+       window.L = oldL;\r
+       return this;\r
+};\r
+\r
+window.L = L;\r
+
+
+/*\r
+ * L.Util contains various utility functions used throughout Leaflet code.\r
+ */\r
+\r
+L.Util = {\r
+       extend: function (dest) { // (Object[, Object, ...]) ->\r
+               var sources = Array.prototype.slice.call(arguments, 1),\r
+                   i, j, len, src;\r
+\r
+               for (j = 0, len = sources.length; j < len; j++) {\r
+                       src = sources[j] || {};\r
+                       for (i in src) {\r
+                               if (src.hasOwnProperty(i)) {\r
+                                       dest[i] = src[i];\r
+                               }\r
+                       }\r
+               }\r
+               return dest;\r
+       },\r
+\r
+       bind: function (fn, obj) { // (Function, Object) -> Function\r
+               var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null;\r
+               return function () {\r
+                       return fn.apply(obj, args || arguments);\r
+               };\r
+       },\r
+\r
+       stamp: (function () {\r
+               var lastId = 0,\r
+                   key = '_leaflet_id';\r
+               return function (obj) {\r
+                       obj[key] = obj[key] || ++lastId;\r
+                       return obj[key];\r
+               };\r
+       }()),\r
+\r
+       invokeEach: function (obj, method, context) {\r
+               var i, args;\r
+\r
+               if (typeof obj === 'object') {\r
+                       args = Array.prototype.slice.call(arguments, 3);\r
+\r
+                       for (i in obj) {\r
+                               method.apply(context, [i, obj[i]].concat(args));\r
+                       }\r
+                       return true;\r
+               }\r
+\r
+               return false;\r
+       },\r
+\r
+       limitExecByInterval: function (fn, time, context) {\r
+               var lock, execOnUnlock;\r
+\r
+               return function wrapperFn() {\r
+                       var args = arguments;\r
+\r
+                       if (lock) {\r
+                               execOnUnlock = true;\r
+                               return;\r
+                       }\r
+\r
+                       lock = true;\r
+\r
+                       setTimeout(function () {\r
+                               lock = false;\r
+\r
+                               if (execOnUnlock) {\r
+                                       wrapperFn.apply(context, args);\r
+                                       execOnUnlock = false;\r
+                               }\r
+                       }, time);\r
+\r
+                       fn.apply(context, args);\r
+               };\r
+       },\r
+\r
+       falseFn: function () {\r
+               return false;\r
+       },\r
+\r
+       formatNum: function (num, digits) {\r
+               var pow = Math.pow(10, digits || 5);\r
+               return Math.round(num * pow) / pow;\r
+       },\r
+\r
+       trim: function (str) {\r
+               return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');\r
+       },\r
+\r
+       splitWords: function (str) {\r
+               return L.Util.trim(str).split(/\s+/);\r
+       },\r
+\r
+       setOptions: function (obj, options) {\r
+               obj.options = L.extend({}, obj.options, options);\r
+               return obj.options;\r
+       },\r
+\r
+       getParamString: function (obj, existingUrl, uppercase) {\r
+               var params = [];\r
+               for (var i in obj) {\r
+                       params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));\r
+               }\r
+               return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');\r
+       },\r
+       template: function (str, data) {\r
+               return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {\r
+                       var value = data[key];\r
+                       if (value === undefined) {\r
+                               throw new Error('No value provided for variable ' + str);\r
+                       } else if (typeof value === 'function') {\r
+                               value = value(data);\r
+                       }\r
+                       return value;\r
+               });\r
+       },\r
+\r
+       isArray: Array.isArray || function (obj) {\r
+               return (Object.prototype.toString.call(obj) === '[object Array]');\r
+       },\r
+\r
+       emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='\r
+};\r
+\r
+(function () {\r
+\r
+       // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/\r
+\r
+       function getPrefixed(name) {\r
+               var i, fn,\r
+                   prefixes = ['webkit', 'moz', 'o', 'ms'];\r
+\r
+               for (i = 0; i < prefixes.length && !fn; i++) {\r
+                       fn = window[prefixes[i] + name];\r
+               }\r
+\r
+               return fn;\r
+       }\r
+\r
+       var lastTime = 0;\r
+\r
+       function timeoutDefer(fn) {\r
+               var time = +new Date(),\r
+                   timeToCall = Math.max(0, 16 - (time - lastTime));\r
+\r
+               lastTime = time + timeToCall;\r
+               return window.setTimeout(fn, timeToCall);\r
+       }\r
+\r
+       var requestFn = window.requestAnimationFrame ||\r
+               getPrefixed('RequestAnimationFrame') || timeoutDefer;\r
+\r
+       var cancelFn = window.cancelAnimationFrame ||\r
+               getPrefixed('CancelAnimationFrame') ||\r
+               getPrefixed('CancelRequestAnimationFrame') ||\r
+               function (id) { window.clearTimeout(id); };\r
+\r
+\r
+       L.Util.requestAnimFrame = function (fn, context, immediate, element) {\r
+               fn = L.bind(fn, context);\r
+\r
+               if (immediate && requestFn === timeoutDefer) {\r
+                       fn();\r
+               } else {\r
+                       return requestFn.call(window, fn, element);\r
+               }\r
+       };\r
+\r
+       L.Util.cancelAnimFrame = function (id) {\r
+               if (id) {\r
+                       cancelFn.call(window, id);\r
+               }\r
+       };\r
+\r
+}());\r
+\r
+// shortcuts for most used utility functions\r
+L.extend = L.Util.extend;\r
+L.bind = L.Util.bind;\r
+L.stamp = L.Util.stamp;\r
+L.setOptions = L.Util.setOptions;\r
+
+
+/*\r
+ * L.Class powers the OOP facilities of the library.\r
+ * Thanks to John Resig and Dean Edwards for inspiration!\r
+ */\r
+\r
+L.Class = function () {};\r
+\r
+L.Class.extend = function (props) {\r
+\r
+       // extended class with the new prototype\r
+       var NewClass = function () {\r
+\r
+               // call the constructor\r
+               if (this.initialize) {\r
+                       this.initialize.apply(this, arguments);\r
+               }\r
+\r
+               // call all constructor hooks\r
+               if (this._initHooks) {\r
+                       this.callInitHooks();\r
+               }\r
+       };\r
+\r
+       // instantiate class without calling constructor\r
+       var F = function () {};\r
+       F.prototype = this.prototype;\r
+\r
+       var proto = new F();\r
+       proto.constructor = NewClass;\r
+\r
+       NewClass.prototype = proto;\r
+\r
+       //inherit parent's statics\r
+       for (var i in this) {\r
+               if (this.hasOwnProperty(i) && i !== 'prototype') {\r
+                       NewClass[i] = this[i];\r
+               }\r
+       }\r
+\r
+       // mix static properties into the class\r
+       if (props.statics) {\r
+               L.extend(NewClass, props.statics);\r
+               delete props.statics;\r
+       }\r
+\r
+       // mix includes into the prototype\r
+       if (props.includes) {\r
+               L.Util.extend.apply(null, [proto].concat(props.includes));\r
+               delete props.includes;\r
+       }\r
+\r
+       // merge options\r
+       if (props.options && proto.options) {\r
+               props.options = L.extend({}, proto.options, props.options);\r
+       }\r
+\r
+       // mix given properties into the prototype\r
+       L.extend(proto, props);\r
+\r
+       proto._initHooks = [];\r
+\r
+       var parent = this;\r
+       // jshint camelcase: false\r
+       NewClass.__super__ = parent.prototype;\r
+\r
+       // add method for calling all hooks\r
+       proto.callInitHooks = function () {\r
+\r
+               if (this._initHooksCalled) { return; }\r
+\r
+               if (parent.prototype.callInitHooks) {\r
+                       parent.prototype.callInitHooks.call(this);\r
+               }\r
+\r
+               this._initHooksCalled = true;\r
+\r
+               for (var i = 0, len = proto._initHooks.length; i < len; i++) {\r
+                       proto._initHooks[i].call(this);\r
+               }\r
+       };\r
+\r
+       return NewClass;\r
+};\r
+\r
+\r
+// method for adding properties to prototype\r
+L.Class.include = function (props) {\r
+       L.extend(this.prototype, props);\r
+};\r
+\r
+// merge new default options to the Class\r
+L.Class.mergeOptions = function (options) {\r
+       L.extend(this.prototype.options, options);\r
+};\r
+\r
+// add a constructor hook\r
+L.Class.addInitHook = function (fn) { // (Function) || (String, args...)\r
+       var args = Array.prototype.slice.call(arguments, 1);\r
+\r
+       var init = typeof fn === 'function' ? fn : function () {\r
+               this[fn].apply(this, args);\r
+       };\r
+\r
+       this.prototype._initHooks = this.prototype._initHooks || [];\r
+       this.prototype._initHooks.push(init);\r
+};\r
+
+
+/*\r
+ * L.Mixin.Events is used to add custom events functionality to Leaflet classes.\r
+ */\r
+\r
+var eventsKey = '_leaflet_events';\r
+\r
+L.Mixin = {};\r
+\r
+L.Mixin.Events = {\r
+\r
+       addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object])\r
+\r
+               // types can be a map of types/handlers\r
+               if (L.Util.invokeEach(types, this.addEventListener, this, fn, context)) { return this; }\r
+\r
+               var events = this[eventsKey] = this[eventsKey] || {},\r
+                   contextId = context && context !== this && L.stamp(context),\r
+                   i, len, event, type, indexKey, indexLenKey, typeIndex;\r
+\r
+               // types can be a string of space-separated words\r
+               types = L.Util.splitWords(types);\r
+\r
+               for (i = 0, len = types.length; i < len; i++) {\r
+                       event = {\r
+                               action: fn,\r
+                               context: context || this\r
+                       };\r
+                       type = types[i];\r
+\r
+                       if (contextId) {\r
+                               // store listeners of a particular context in a separate hash (if it has an id)\r
+                               // gives a major performance boost when removing thousands of map layers\r
+\r
+                               indexKey = type + '_idx';\r
+                               indexLenKey = indexKey + '_len';\r
+\r
+                               typeIndex = events[indexKey] = events[indexKey] || {};\r
+\r
+                               if (!typeIndex[contextId]) {\r
+                                       typeIndex[contextId] = [];\r
+\r
+                                       // keep track of the number of keys in the index to quickly check if it's empty\r
+                                       events[indexLenKey] = (events[indexLenKey] || 0) + 1;\r
+                               }\r
+\r
+                               typeIndex[contextId].push(event);\r
+\r
+\r
+                       } else {\r
+                               events[type] = events[type] || [];\r
+                               events[type].push(event);\r
+                       }\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       hasEventListeners: function (type) { // (String) -> Boolean\r
+               var events = this[eventsKey];\r
+               return !!events && ((type in events && events[type].length > 0) ||\r
+                                   (type + '_idx' in events && events[type + '_idx_len'] > 0));\r
+       },\r
+\r
+       removeEventListener: function (types, fn, context) { // ([String, Function, Object]) or (Object[, Object])\r
+\r
+               if (!this[eventsKey]) {\r
+                       return this;\r
+               }\r
+\r
+               if (!types) {\r
+                       return this.clearAllEventListeners();\r
+               }\r
+\r
+               if (L.Util.invokeEach(types, this.removeEventListener, this, fn, context)) { return this; }\r
+\r
+               var events = this[eventsKey],\r
+                   contextId = context && context !== this && L.stamp(context),\r
+                   i, len, type, listeners, j, indexKey, indexLenKey, typeIndex, removed;\r
+\r
+               types = L.Util.splitWords(types);\r
+\r
+               for (i = 0, len = types.length; i < len; i++) {\r
+                       type = types[i];\r
+                       indexKey = type + '_idx';\r
+                       indexLenKey = indexKey + '_len';\r
+\r
+                       typeIndex = events[indexKey];\r
+\r
+                       if (!fn) {\r
+                               // clear all listeners for a type if function isn't specified\r
+                               delete events[type];\r
+                               delete events[indexKey];\r
+                               delete events[indexLenKey];\r
+\r
+                       } else {\r
+                               listeners = contextId && typeIndex ? typeIndex[contextId] : events[type];\r
+\r
+                               if (listeners) {\r
+                                       for (j = listeners.length - 1; j >= 0; j--) {\r
+                                               if ((listeners[j].action === fn) && (!context || (listeners[j].context === context))) {\r
+                                                       removed = listeners.splice(j, 1);\r
+                                                       // set the old action to a no-op, because it is possible\r
+                                                       // that the listener is being iterated over as part of a dispatch\r
+                                                       removed[0].action = L.Util.falseFn;\r
+                                               }\r
+                                       }\r
+\r
+                                       if (context && typeIndex && (listeners.length === 0)) {\r
+                                               delete typeIndex[contextId];\r
+                                               events[indexLenKey]--;\r
+                                       }\r
+                               }\r
+                       }\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       clearAllEventListeners: function () {\r
+               delete this[eventsKey];\r
+               return this;\r
+       },\r
+\r
+       fireEvent: function (type, data) { // (String[, Object])\r
+               if (!this.hasEventListeners(type)) {\r
+                       return this;\r
+               }\r
+\r
+               var event = L.Util.extend({}, data, { type: type, target: this });\r
+\r
+               var events = this[eventsKey],\r
+                   listeners, i, len, typeIndex, contextId;\r
+\r
+               if (events[type]) {\r
+                       // make sure adding/removing listeners inside other listeners won't cause infinite loop\r
+                       listeners = events[type].slice();\r
+\r
+                       for (i = 0, len = listeners.length; i < len; i++) {\r
+                               listeners[i].action.call(listeners[i].context, event);\r
+                       }\r
+               }\r
+\r
+               // fire event for the context-indexed listeners as well\r
+               typeIndex = events[type + '_idx'];\r
+\r
+               for (contextId in typeIndex) {\r
+                       listeners = typeIndex[contextId].slice();\r
+\r
+                       if (listeners) {\r
+                               for (i = 0, len = listeners.length; i < len; i++) {\r
+                                       listeners[i].action.call(listeners[i].context, event);\r
+                               }\r
+                       }\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       addOneTimeEventListener: function (types, fn, context) {\r
+\r
+               if (L.Util.invokeEach(types, this.addOneTimeEventListener, this, fn, context)) { return this; }\r
+\r
+               var handler = L.bind(function () {\r
+                       this\r
+                           .removeEventListener(types, fn, context)\r
+                           .removeEventListener(types, handler, context);\r
+               }, this);\r
+\r
+               return this\r
+                   .addEventListener(types, fn, context)\r
+                   .addEventListener(types, handler, context);\r
+       }\r
+};\r
+\r
+L.Mixin.Events.on = L.Mixin.Events.addEventListener;\r
+L.Mixin.Events.off = L.Mixin.Events.removeEventListener;\r
+L.Mixin.Events.once = L.Mixin.Events.addOneTimeEventListener;\r
+L.Mixin.Events.fire = L.Mixin.Events.fireEvent;\r
+
+
+/*\r
+ * L.Browser handles different browser and feature detections for internal Leaflet use.\r
+ */\r
+\r
+(function () {\r
+\r
+       var ie = 'ActiveXObject' in window,\r
+               ielt9 = ie && !document.addEventListener,\r
+\r
+           // terrible browser detection to work around Safari / iOS / Android browser bugs\r
+           ua = navigator.userAgent.toLowerCase(),\r
+           webkit = ua.indexOf('webkit') !== -1,\r
+           chrome = ua.indexOf('chrome') !== -1,\r
+           phantomjs = ua.indexOf('phantom') !== -1,\r
+           android = ua.indexOf('android') !== -1,\r
+           android23 = ua.search('android [23]') !== -1,\r
+               gecko = ua.indexOf('gecko') !== -1,\r
+\r
+           mobile = typeof orientation !== undefined + '',\r
+           msPointer = window.navigator && window.navigator.msPointerEnabled &&\r
+                     window.navigator.msMaxTouchPoints && !window.PointerEvent,\r
+               pointer = (window.PointerEvent && window.navigator.pointerEnabled && window.navigator.maxTouchPoints) ||\r
+                                 msPointer,\r
+           retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) ||\r
+                    ('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') &&\r
+                     window.matchMedia('(min-resolution:144dpi)').matches),\r
+\r
+           doc = document.documentElement,\r
+           ie3d = ie && ('transition' in doc.style),\r
+           webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,\r
+           gecko3d = 'MozPerspective' in doc.style,\r
+           opera3d = 'OTransition' in doc.style,\r
+           any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d) && !phantomjs;\r
+\r
+\r
+       // PhantomJS has 'ontouchstart' in document.documentElement, but doesn't actually support touch.\r
+       // https://github.com/Leaflet/Leaflet/pull/1434#issuecomment-13843151\r
+\r
+       var touch = !window.L_NO_TOUCH && !phantomjs && (function () {\r
+\r
+               var startName = 'ontouchstart';\r
+\r
+               // IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.Pointer) or WebKit, etc.\r
+               if (pointer || (startName in doc)) {\r
+                       return true;\r
+               }\r
+\r
+               // Firefox/Gecko\r
+               var div = document.createElement('div'),\r
+                   supported = false;\r
+\r
+               if (!div.setAttribute) {\r
+                       return false;\r
+               }\r
+               div.setAttribute(startName, 'return;');\r
+\r
+               if (typeof div[startName] === 'function') {\r
+                       supported = true;\r
+               }\r
+\r
+               div.removeAttribute(startName);\r
+               div = null;\r
+\r
+               return supported;\r
+       }());\r
+\r
+\r
+       L.Browser = {\r
+               ie: ie,\r
+               ielt9: ielt9,\r
+               webkit: webkit,\r
+               gecko: gecko && !webkit && !window.opera && !ie,\r
+\r
+               android: android,\r
+               android23: android23,\r
+\r
+               chrome: chrome,\r
+\r
+               ie3d: ie3d,\r
+               webkit3d: webkit3d,\r
+               gecko3d: gecko3d,\r
+               opera3d: opera3d,\r
+               any3d: any3d,\r
+\r
+               mobile: mobile,\r
+               mobileWebkit: mobile && webkit,\r
+               mobileWebkit3d: mobile && webkit3d,\r
+               mobileOpera: mobile && window.opera,\r
+\r
+               touch: touch,\r
+               msPointer: msPointer,\r
+               pointer: pointer,\r
+\r
+               retina: retina\r
+       };\r
+\r
+}());\r
+
+
+/*\r
+ * L.Point represents a point with x and y coordinates.\r
+ */\r
+\r
+L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) {\r
+       this.x = (round ? Math.round(x) : x);\r
+       this.y = (round ? Math.round(y) : y);\r
+};\r
+\r
+L.Point.prototype = {\r
+\r
+       clone: function () {\r
+               return new L.Point(this.x, this.y);\r
+       },\r
+\r
+       // non-destructive, returns a new point\r
+       add: function (point) {\r
+               return this.clone()._add(L.point(point));\r
+       },\r
+\r
+       // destructive, used directly for performance in situations where it's safe to modify existing point\r
+       _add: function (point) {\r
+               this.x += point.x;\r
+               this.y += point.y;\r
+               return this;\r
+       },\r
+\r
+       subtract: function (point) {\r
+               return this.clone()._subtract(L.point(point));\r
+       },\r
+\r
+       _subtract: function (point) {\r
+               this.x -= point.x;\r
+               this.y -= point.y;\r
+               return this;\r
+       },\r
+\r
+       divideBy: function (num) {\r
+               return this.clone()._divideBy(num);\r
+       },\r
+\r
+       _divideBy: function (num) {\r
+               this.x /= num;\r
+               this.y /= num;\r
+               return this;\r
+       },\r
+\r
+       multiplyBy: function (num) {\r
+               return this.clone()._multiplyBy(num);\r
+       },\r
+\r
+       _multiplyBy: function (num) {\r
+               this.x *= num;\r
+               this.y *= num;\r
+               return this;\r
+       },\r
+\r
+       round: function () {\r
+               return this.clone()._round();\r
+       },\r
+\r
+       _round: function () {\r
+               this.x = Math.round(this.x);\r
+               this.y = Math.round(this.y);\r
+               return this;\r
+       },\r
+\r
+       floor: function () {\r
+               return this.clone()._floor();\r
+       },\r
+\r
+       _floor: function () {\r
+               this.x = Math.floor(this.x);\r
+               this.y = Math.floor(this.y);\r
+               return this;\r
+       },\r
+\r
+       distanceTo: function (point) {\r
+               point = L.point(point);\r
+\r
+               var x = point.x - this.x,\r
+                   y = point.y - this.y;\r
+\r
+               return Math.sqrt(x * x + y * y);\r
+       },\r
+\r
+       equals: function (point) {\r
+               point = L.point(point);\r
+\r
+               return point.x === this.x &&\r
+                      point.y === this.y;\r
+       },\r
+\r
+       contains: function (point) {\r
+               point = L.point(point);\r
+\r
+               return Math.abs(point.x) <= Math.abs(this.x) &&\r
+                      Math.abs(point.y) <= Math.abs(this.y);\r
+       },\r
+\r
+       toString: function () {\r
+               return 'Point(' +\r
+                       L.Util.formatNum(this.x) + ', ' +\r
+                       L.Util.formatNum(this.y) + ')';\r
+       }\r
+};\r
+\r
+L.point = function (x, y, round) {\r
+       if (x instanceof L.Point) {\r
+               return x;\r
+       }\r
+       if (L.Util.isArray(x)) {\r
+               return new L.Point(x[0], x[1]);\r
+       }\r
+       if (x === undefined || x === null) {\r
+               return x;\r
+       }\r
+       return new L.Point(x, y, round);\r
+};\r
+
+
+/*\r
+ * L.Bounds represents a rectangular area on the screen in pixel coordinates.\r
+ */\r
+\r
+L.Bounds = function (a, b) { //(Point, Point) or Point[]\r
+       if (!a) { return; }\r
+\r
+       var points = b ? [a, b] : a;\r
+\r
+       for (var i = 0, len = points.length; i < len; i++) {\r
+               this.extend(points[i]);\r
+       }\r
+};\r
+\r
+L.Bounds.prototype = {\r
+       // extend the bounds to contain the given point\r
+       extend: function (point) { // (Point)\r
+               point = L.point(point);\r
+\r
+               if (!this.min && !this.max) {\r
+                       this.min = point.clone();\r
+                       this.max = point.clone();\r
+               } else {\r
+                       this.min.x = Math.min(point.x, this.min.x);\r
+                       this.max.x = Math.max(point.x, this.max.x);\r
+                       this.min.y = Math.min(point.y, this.min.y);\r
+                       this.max.y = Math.max(point.y, this.max.y);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       getCenter: function (round) { // (Boolean) -> Point\r
+               return new L.Point(\r
+                       (this.min.x + this.max.x) / 2,\r
+                       (this.min.y + this.max.y) / 2, round);\r
+       },\r
+\r
+       getBottomLeft: function () { // -> Point\r
+               return new L.Point(this.min.x, this.max.y);\r
+       },\r
+\r
+       getTopRight: function () { // -> Point\r
+               return new L.Point(this.max.x, this.min.y);\r
+       },\r
+\r
+       getSize: function () {\r
+               return this.max.subtract(this.min);\r
+       },\r
+\r
+       contains: function (obj) { // (Bounds) or (Point) -> Boolean\r
+               var min, max;\r
+\r
+               if (typeof obj[0] === 'number' || obj instanceof L.Point) {\r
+                       obj = L.point(obj);\r
+               } else {\r
+                       obj = L.bounds(obj);\r
+               }\r
+\r
+               if (obj instanceof L.Bounds) {\r
+                       min = obj.min;\r
+                       max = obj.max;\r
+               } else {\r
+                       min = max = obj;\r
+               }\r
+\r
+               return (min.x >= this.min.x) &&\r
+                      (max.x <= this.max.x) &&\r
+                      (min.y >= this.min.y) &&\r
+                      (max.y <= this.max.y);\r
+       },\r
+\r
+       intersects: function (bounds) { // (Bounds) -> Boolean\r
+               bounds = L.bounds(bounds);\r
+\r
+               var min = this.min,\r
+                   max = this.max,\r
+                   min2 = bounds.min,\r
+                   max2 = bounds.max,\r
+                   xIntersects = (max2.x >= min.x) && (min2.x <= max.x),\r
+                   yIntersects = (max2.y >= min.y) && (min2.y <= max.y);\r
+\r
+               return xIntersects && yIntersects;\r
+       },\r
+\r
+       isValid: function () {\r
+               return !!(this.min && this.max);\r
+       }\r
+};\r
+\r
+L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[])\r
+       if (!a || a instanceof L.Bounds) {\r
+               return a;\r
+       }\r
+       return new L.Bounds(a, b);\r
+};\r
+
+
+/*\r
+ * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix.\r
+ */\r
+\r
+L.Transformation = function (a, b, c, d) {\r
+       this._a = a;\r
+       this._b = b;\r
+       this._c = c;\r
+       this._d = d;\r
+};\r
+\r
+L.Transformation.prototype = {\r
+       transform: function (point, scale) { // (Point, Number) -> Point\r
+               return this._transform(point.clone(), scale);\r
+       },\r
+\r
+       // destructive transform (faster)\r
+       _transform: function (point, scale) {\r
+               scale = scale || 1;\r
+               point.x = scale * (this._a * point.x + this._b);\r
+               point.y = scale * (this._c * point.y + this._d);\r
+               return point;\r
+       },\r
+\r
+       untransform: function (point, scale) {\r
+               scale = scale || 1;\r
+               return new L.Point(\r
+                       (point.x / scale - this._b) / this._a,\r
+                       (point.y / scale - this._d) / this._c);\r
+       }\r
+};\r
+
+
+/*\r
+ * L.DomUtil contains various utility functions for working with DOM.\r
+ */\r
+\r
+L.DomUtil = {\r
+       get: function (id) {\r
+               return (typeof id === 'string' ? document.getElementById(id) : id);\r
+       },\r
+\r
+       getStyle: function (el, style) {\r
+\r
+               var value = el.style[style];\r
+\r
+               if (!value && el.currentStyle) {\r
+                       value = el.currentStyle[style];\r
+               }\r
+\r
+               if ((!value || value === 'auto') && document.defaultView) {\r
+                       var css = document.defaultView.getComputedStyle(el, null);\r
+                       value = css ? css[style] : null;\r
+               }\r
+\r
+               return value === 'auto' ? null : value;\r
+       },\r
+\r
+       getViewportOffset: function (element) {\r
+\r
+               var top = 0,\r
+                   left = 0,\r
+                   el = element,\r
+                   docBody = document.body,\r
+                   docEl = document.documentElement,\r
+                   pos;\r
+\r
+               do {\r
+                       top  += el.offsetTop  || 0;\r
+                       left += el.offsetLeft || 0;\r
+\r
+                       //add borders\r
+                       top += parseInt(L.DomUtil.getStyle(el, 'borderTopWidth'), 10) || 0;\r
+                       left += parseInt(L.DomUtil.getStyle(el, 'borderLeftWidth'), 10) || 0;\r
+\r
+                       pos = L.DomUtil.getStyle(el, 'position');\r
+\r
+                       if (el.offsetParent === docBody && pos === 'absolute') { break; }\r
+\r
+                       if (pos === 'fixed') {\r
+                               top  += docBody.scrollTop  || docEl.scrollTop  || 0;\r
+                               left += docBody.scrollLeft || docEl.scrollLeft || 0;\r
+                               break;\r
+                       }\r
+\r
+                       if (pos === 'relative' && !el.offsetLeft) {\r
+                               var width = L.DomUtil.getStyle(el, 'width'),\r
+                                   maxWidth = L.DomUtil.getStyle(el, 'max-width'),\r
+                                   r = el.getBoundingClientRect();\r
+\r
+                               if (width !== 'none' || maxWidth !== 'none') {\r
+                                       left += r.left + el.clientLeft;\r
+                               }\r
+\r
+                               //calculate full y offset since we're breaking out of the loop\r
+                               top += r.top + (docBody.scrollTop  || docEl.scrollTop  || 0);\r
+\r
+                               break;\r
+                       }\r
+\r
+                       el = el.offsetParent;\r
+\r
+               } while (el);\r
+\r
+               el = element;\r
+\r
+               do {\r
+                       if (el === docBody) { break; }\r
+\r
+                       top  -= el.scrollTop  || 0;\r
+                       left -= el.scrollLeft || 0;\r
+\r
+                       el = el.parentNode;\r
+               } while (el);\r
+\r
+               return new L.Point(left, top);\r
+       },\r
+\r
+       documentIsLtr: function () {\r
+               if (!L.DomUtil._docIsLtrCached) {\r
+                       L.DomUtil._docIsLtrCached = true;\r
+                       L.DomUtil._docIsLtr = L.DomUtil.getStyle(document.body, 'direction') === 'ltr';\r
+               }\r
+               return L.DomUtil._docIsLtr;\r
+       },\r
+\r
+       create: function (tagName, className, container) {\r
+\r
+               var el = document.createElement(tagName);\r
+               el.className = className;\r
+\r
+               if (container) {\r
+                       container.appendChild(el);\r
+               }\r
+\r
+               return el;\r
+       },\r
+\r
+       hasClass: function (el, name) {\r
+               if (el.classList !== undefined) {\r
+                       return el.classList.contains(name);\r
+               }\r
+               var className = L.DomUtil._getClass(el);\r
+               return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);\r
+       },\r
+\r
+       addClass: function (el, name) {\r
+               if (el.classList !== undefined) {\r
+                       var classes = L.Util.splitWords(name);\r
+                       for (var i = 0, len = classes.length; i < len; i++) {\r
+                               el.classList.add(classes[i]);\r
+                       }\r
+               } else if (!L.DomUtil.hasClass(el, name)) {\r
+                       var className = L.DomUtil._getClass(el);\r
+                       L.DomUtil._setClass(el, (className ? className + ' ' : '') + name);\r
+               }\r
+       },\r
+\r
+       removeClass: function (el, name) {\r
+               if (el.classList !== undefined) {\r
+                       el.classList.remove(name);\r
+               } else {\r
+                       L.DomUtil._setClass(el, L.Util.trim((' ' + L.DomUtil._getClass(el) + ' ').replace(' ' + name + ' ', ' ')));\r
+               }\r
+       },\r
+\r
+       _setClass: function (el, name) {\r
+               if (el.className.baseVal === undefined) {\r
+                       el.className = name;\r
+               } else {\r
+                       // in case of SVG element\r
+                       el.className.baseVal = name;\r
+               }\r
+       },\r
+\r
+       _getClass: function (el) {\r
+               return el.className.baseVal === undefined ? el.className : el.className.baseVal;\r
+       },\r
+\r
+       setOpacity: function (el, value) {\r
+\r
+               if ('opacity' in el.style) {\r
+                       el.style.opacity = value;\r
+\r
+               } else if ('filter' in el.style) {\r
+\r
+                       var filter = false,\r
+                           filterName = 'DXImageTransform.Microsoft.Alpha';\r
+\r
+                       // filters collection throws an error if we try to retrieve a filter that doesn't exist\r
+                       try {\r
+                               filter = el.filters.item(filterName);\r
+                       } catch (e) {\r
+                               // don't set opacity to 1 if we haven't already set an opacity,\r
+                               // it isn't needed and breaks transparent pngs.\r
+                               if (value === 1) { return; }\r
+                       }\r
+\r
+                       value = Math.round(value * 100);\r
+\r
+                       if (filter) {\r
+                               filter.Enabled = (value !== 100);\r
+                               filter.Opacity = value;\r
+                       } else {\r
+                               el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';\r
+                       }\r
+               }\r
+       },\r
+\r
+       testProp: function (props) {\r
+\r
+               var style = document.documentElement.style;\r
+\r
+               for (var i = 0; i < props.length; i++) {\r
+                       if (props[i] in style) {\r
+                               return props[i];\r
+                       }\r
+               }\r
+               return false;\r
+       },\r
+\r
+       getTranslateString: function (point) {\r
+               // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate\r
+               // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care\r
+               // (same speed either way), Opera 12 doesn't support translate3d\r
+\r
+               var is3d = L.Browser.webkit3d,\r
+                   open = 'translate' + (is3d ? '3d' : '') + '(',\r
+                   close = (is3d ? ',0' : '') + ')';\r
+\r
+               return open + point.x + 'px,' + point.y + 'px' + close;\r
+       },\r
+\r
+       getScaleString: function (scale, origin) {\r
+\r
+               var preTranslateStr = L.DomUtil.getTranslateString(origin.add(origin.multiplyBy(-1 * scale))),\r
+                   scaleStr = ' scale(' + scale + ') ';\r
+\r
+               return preTranslateStr + scaleStr;\r
+       },\r
+\r
+       setPosition: function (el, point, disable3D) { // (HTMLElement, Point[, Boolean])\r
+\r
+               // jshint camelcase: false\r
+               el._leaflet_pos = point;\r
+\r
+               if (!disable3D && L.Browser.any3d) {\r
+                       el.style[L.DomUtil.TRANSFORM] =  L.DomUtil.getTranslateString(point);\r
+               } else {\r
+                       el.style.left = point.x + 'px';\r
+                       el.style.top = point.y + 'px';\r
+               }\r
+       },\r
+\r
+       getPosition: function (el) {\r
+               // this method is only used for elements previously positioned using setPosition,\r
+               // so it's safe to cache the position for performance\r
+\r
+               // jshint camelcase: false\r
+               return el._leaflet_pos;\r
+       }\r
+};\r
+\r
+\r
+// prefix style property names\r
+\r
+L.DomUtil.TRANSFORM = L.DomUtil.testProp(\r
+        ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);\r
+\r
+// webkitTransition comes first because some browser versions that drop vendor prefix don't do\r
+// the same for the transitionend event, in particular the Android 4.1 stock browser\r
+\r
+L.DomUtil.TRANSITION = L.DomUtil.testProp(\r
+        ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);\r
+\r
+L.DomUtil.TRANSITION_END =\r
+        L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ?\r
+        L.DomUtil.TRANSITION + 'End' : 'transitionend';\r
+\r
+(function () {\r
+    if ('onselectstart' in document) {\r
+        L.extend(L.DomUtil, {\r
+            disableTextSelection: function () {\r
+                L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);\r
+            },\r
+\r
+            enableTextSelection: function () {\r
+                L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);\r
+            }\r
+        });\r
+    } else {\r
+        var userSelectProperty = L.DomUtil.testProp(\r
+            ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);\r
+\r
+        L.extend(L.DomUtil, {\r
+            disableTextSelection: function () {\r
+                if (userSelectProperty) {\r
+                    var style = document.documentElement.style;\r
+                    this._userSelect = style[userSelectProperty];\r
+                    style[userSelectProperty] = 'none';\r
+                }\r
+            },\r
+\r
+            enableTextSelection: function () {\r
+                if (userSelectProperty) {\r
+                    document.documentElement.style[userSelectProperty] = this._userSelect;\r
+                    delete this._userSelect;\r
+                }\r
+            }\r
+        });\r
+    }\r
+\r
+       L.extend(L.DomUtil, {\r
+               disableImageDrag: function () {\r
+                       L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);\r
+               },\r
+\r
+               enableImageDrag: function () {\r
+                       L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);\r
+               }\r
+       });\r
+})();\r
+
+
+/*\r
+ * L.LatLng represents a geographical point with latitude and longitude coordinates.\r
+ */\r
+\r
+L.LatLng = function (lat, lng, alt) { // (Number, Number, Number)\r
+       lat = parseFloat(lat);\r
+       lng = parseFloat(lng);\r
+\r
+       if (isNaN(lat) || isNaN(lng)) {\r
+               throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');\r
+       }\r
+\r
+       this.lat = lat;\r
+       this.lng = lng;\r
+\r
+       if (alt !== undefined) {\r
+               this.alt = parseFloat(alt);\r
+       }\r
+};\r
+\r
+L.extend(L.LatLng, {\r
+       DEG_TO_RAD: Math.PI / 180,\r
+       RAD_TO_DEG: 180 / Math.PI,\r
+       MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check\r
+});\r
+\r
+L.LatLng.prototype = {\r
+       equals: function (obj) { // (LatLng) -> Boolean\r
+               if (!obj) { return false; }\r
+\r
+               obj = L.latLng(obj);\r
+\r
+               var margin = Math.max(\r
+                       Math.abs(this.lat - obj.lat),\r
+                       Math.abs(this.lng - obj.lng));\r
+\r
+               return margin <= L.LatLng.MAX_MARGIN;\r
+       },\r
+\r
+       toString: function (precision) { // (Number) -> String\r
+               return 'LatLng(' +\r
+                       L.Util.formatNum(this.lat, precision) + ', ' +\r
+                       L.Util.formatNum(this.lng, precision) + ')';\r
+       },\r
+\r
+       // Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula\r
+       // TODO move to projection code, LatLng shouldn't know about Earth\r
+       distanceTo: function (other) { // (LatLng) -> Number\r
+               other = L.latLng(other);\r
+\r
+               var R = 6378137, // earth radius in meters\r
+                   d2r = L.LatLng.DEG_TO_RAD,\r
+                   dLat = (other.lat - this.lat) * d2r,\r
+                   dLon = (other.lng - this.lng) * d2r,\r
+                   lat1 = this.lat * d2r,\r
+                   lat2 = other.lat * d2r,\r
+                   sin1 = Math.sin(dLat / 2),\r
+                   sin2 = Math.sin(dLon / 2);\r
+\r
+               var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);\r
+\r
+               return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\r
+       },\r
+\r
+       wrap: function (a, b) { // (Number, Number) -> LatLng\r
+               var lng = this.lng;\r
+\r
+               a = a || -180;\r
+               b = b ||  180;\r
+\r
+               lng = (lng + b) % (b - a) + (lng < a || lng === b ? b : a);\r
+\r
+               return new L.LatLng(this.lat, lng);\r
+       }\r
+};\r
+\r
+L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Number)\r
+       if (a instanceof L.LatLng) {\r
+               return a;\r
+       }\r
+       if (L.Util.isArray(a)) {\r
+               if (typeof a[0] === 'number' || typeof a[0] === 'string') {\r
+                       return new L.LatLng(a[0], a[1], a[2]);\r
+               } else {\r
+                       return null;\r
+               }\r
+       }\r
+       if (a === undefined || a === null) {\r
+               return a;\r
+       }\r
+       if (typeof a === 'object' && 'lat' in a) {\r
+               return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon);\r
+       }\r
+       if (b === undefined) {\r
+               return null;\r
+       }\r
+       return new L.LatLng(a, b);\r
+};\r
+\r
+
+
+/*\r
+ * L.LatLngBounds represents a rectangular area on the map in geographical coordinates.\r
+ */\r
+\r
+L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])\r
+       if (!southWest) { return; }\r
+\r
+       var latlngs = northEast ? [southWest, northEast] : southWest;\r
+\r
+       for (var i = 0, len = latlngs.length; i < len; i++) {\r
+               this.extend(latlngs[i]);\r
+       }\r
+};\r
+\r
+L.LatLngBounds.prototype = {\r
+       // extend the bounds to contain the given point or bounds\r
+       extend: function (obj) { // (LatLng) or (LatLngBounds)\r
+               if (!obj) { return this; }\r
+\r
+               var latLng = L.latLng(obj);\r
+               if (latLng !== null) {\r
+                       obj = latLng;\r
+               } else {\r
+                       obj = L.latLngBounds(obj);\r
+               }\r
+\r
+               if (obj instanceof L.LatLng) {\r
+                       if (!this._southWest && !this._northEast) {\r
+                               this._southWest = new L.LatLng(obj.lat, obj.lng);\r
+                               this._northEast = new L.LatLng(obj.lat, obj.lng);\r
+                       } else {\r
+                               this._southWest.lat = Math.min(obj.lat, this._southWest.lat);\r
+                               this._southWest.lng = Math.min(obj.lng, this._southWest.lng);\r
+\r
+                               this._northEast.lat = Math.max(obj.lat, this._northEast.lat);\r
+                               this._northEast.lng = Math.max(obj.lng, this._northEast.lng);\r
+                       }\r
+               } else if (obj instanceof L.LatLngBounds) {\r
+                       this.extend(obj._southWest);\r
+                       this.extend(obj._northEast);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       // extend the bounds by a percentage\r
+       pad: function (bufferRatio) { // (Number) -> LatLngBounds\r
+               var sw = this._southWest,\r
+                   ne = this._northEast,\r
+                   heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,\r
+                   widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;\r
+\r
+               return new L.LatLngBounds(\r
+                       new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),\r
+                       new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));\r
+       },\r
+\r
+       getCenter: function () { // -> LatLng\r
+               return new L.LatLng(\r
+                       (this._southWest.lat + this._northEast.lat) / 2,\r
+                       (this._southWest.lng + this._northEast.lng) / 2);\r
+       },\r
+\r
+       getSouthWest: function () {\r
+               return this._southWest;\r
+       },\r
+\r
+       getNorthEast: function () {\r
+               return this._northEast;\r
+       },\r
+\r
+       getNorthWest: function () {\r
+               return new L.LatLng(this.getNorth(), this.getWest());\r
+       },\r
+\r
+       getSouthEast: function () {\r
+               return new L.LatLng(this.getSouth(), this.getEast());\r
+       },\r
+\r
+       getWest: function () {\r
+               return this._southWest.lng;\r
+       },\r
+\r
+       getSouth: function () {\r
+               return this._southWest.lat;\r
+       },\r
+\r
+       getEast: function () {\r
+               return this._northEast.lng;\r
+       },\r
+\r
+       getNorth: function () {\r
+               return this._northEast.lat;\r
+       },\r
+\r
+       contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean\r
+               if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {\r
+                       obj = L.latLng(obj);\r
+               } else {\r
+                       obj = L.latLngBounds(obj);\r
+               }\r
+\r
+               var sw = this._southWest,\r
+                   ne = this._northEast,\r
+                   sw2, ne2;\r
+\r
+               if (obj instanceof L.LatLngBounds) {\r
+                       sw2 = obj.getSouthWest();\r
+                       ne2 = obj.getNorthEast();\r
+               } else {\r
+                       sw2 = ne2 = obj;\r
+               }\r
+\r
+               return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&\r
+                      (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);\r
+       },\r
+\r
+       intersects: function (bounds) { // (LatLngBounds)\r
+               bounds = L.latLngBounds(bounds);\r
+\r
+               var sw = this._southWest,\r
+                   ne = this._northEast,\r
+                   sw2 = bounds.getSouthWest(),\r
+                   ne2 = bounds.getNorthEast(),\r
+\r
+                   latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),\r
+                   lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);\r
+\r
+               return latIntersects && lngIntersects;\r
+       },\r
+\r
+       toBBoxString: function () {\r
+               return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');\r
+       },\r
+\r
+       equals: function (bounds) { // (LatLngBounds)\r
+               if (!bounds) { return false; }\r
+\r
+               bounds = L.latLngBounds(bounds);\r
+\r
+               return this._southWest.equals(bounds.getSouthWest()) &&\r
+                      this._northEast.equals(bounds.getNorthEast());\r
+       },\r
+\r
+       isValid: function () {\r
+               return !!(this._southWest && this._northEast);\r
+       }\r
+};\r
+\r
+//TODO International date line?\r
+\r
+L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng)\r
+       if (!a || a instanceof L.LatLngBounds) {\r
+               return a;\r
+       }\r
+       return new L.LatLngBounds(a, b);\r
+};\r
+
+
+/*\r
+ * L.Projection contains various geographical projections used by CRS classes.\r
+ */\r
+\r
+L.Projection = {};\r
+
+
+/*\r
+ * Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default.\r
+ */\r
+\r
+L.Projection.SphericalMercator = {\r
+       MAX_LATITUDE: 85.0511287798,\r
+\r
+       project: function (latlng) { // (LatLng) -> Point\r
+               var d = L.LatLng.DEG_TO_RAD,\r
+                   max = this.MAX_LATITUDE,\r
+                   lat = Math.max(Math.min(max, latlng.lat), -max),\r
+                   x = latlng.lng * d,\r
+                   y = lat * d;\r
+\r
+               y = Math.log(Math.tan((Math.PI / 4) + (y / 2)));\r
+\r
+               return new L.Point(x, y);\r
+       },\r
+\r
+       unproject: function (point) { // (Point, Boolean) -> LatLng\r
+               var d = L.LatLng.RAD_TO_DEG,\r
+                   lng = point.x * d,\r
+                   lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d;\r
+\r
+               return new L.LatLng(lat, lng);\r
+       }\r
+};\r
+
+
+/*\r
+ * Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple.\r
+ */\r
+\r
+L.Projection.LonLat = {\r
+       project: function (latlng) {\r
+               return new L.Point(latlng.lng, latlng.lat);\r
+       },\r
+\r
+       unproject: function (point) {\r
+               return new L.LatLng(point.y, point.x);\r
+       }\r
+};\r
+
+
+/*\r
+ * L.CRS is a base object for all defined CRS (Coordinate Reference Systems) in Leaflet.\r
+ */\r
+\r
+L.CRS = {\r
+       latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point\r
+               var projectedPoint = this.projection.project(latlng),\r
+                   scale = this.scale(zoom);\r
+\r
+               return this.transformation._transform(projectedPoint, scale);\r
+       },\r
+\r
+       pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng\r
+               var scale = this.scale(zoom),\r
+                   untransformedPoint = this.transformation.untransform(point, scale);\r
+\r
+               return this.projection.unproject(untransformedPoint);\r
+       },\r
+\r
+       project: function (latlng) {\r
+               return this.projection.project(latlng);\r
+       },\r
+\r
+       scale: function (zoom) {\r
+               return 256 * Math.pow(2, zoom);\r
+       },\r
+\r
+       getSize: function (zoom) {\r
+               var s = this.scale(zoom);\r
+               return L.point(s, s);\r
+       }\r
+};\r
+
+
+/*
+ * A simple CRS that can be used for flat non-Earth maps like panoramas or game maps.
+ */
+
+L.CRS.Simple = L.extend({}, L.CRS, {
+       projection: L.Projection.LonLat,
+       transformation: new L.Transformation(1, 0, -1, 0),
+
+       scale: function (zoom) {
+               return Math.pow(2, zoom);
+       }
+});
+
+
+/*\r
+ * L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping\r
+ * and is used by Leaflet by default.\r
+ */\r
+\r
+L.CRS.EPSG3857 = L.extend({}, L.CRS, {\r
+       code: 'EPSG:3857',\r
+\r
+       projection: L.Projection.SphericalMercator,\r
+       transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5),\r
+\r
+       project: function (latlng) { // (LatLng) -> Point\r
+               var projectedPoint = this.projection.project(latlng),\r
+                   earthRadius = 6378137;\r
+               return projectedPoint.multiplyBy(earthRadius);\r
+       }\r
+});\r
+\r
+L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {\r
+       code: 'EPSG:900913'\r
+});\r
+
+
+/*\r
+ * L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists.\r
+ */\r
+\r
+L.CRS.EPSG4326 = L.extend({}, L.CRS, {\r
+       code: 'EPSG:4326',\r
+\r
+       projection: L.Projection.LonLat,\r
+       transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5)\r
+});\r
+
+
+/*\r
+ * L.Map is the central class of the API - it is used to create a map.\r
+ */\r
+\r
+L.Map = L.Class.extend({\r
+\r
+       includes: L.Mixin.Events,\r
+\r
+       options: {\r
+               crs: L.CRS.EPSG3857,\r
+\r
+               /*\r
+               center: LatLng,\r
+               zoom: Number,\r
+               layers: Array,\r
+               */\r
+\r
+               fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23,\r
+               trackResize: true,\r
+               markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d\r
+       },\r
+\r
+       initialize: function (id, options) { // (HTMLElement or String, Object)\r
+               options = L.setOptions(this, options);\r
+\r
+\r
+               this._initContainer(id);\r
+               this._initLayout();\r
+\r
+               // hack for https://github.com/Leaflet/Leaflet/issues/1980\r
+               this._onResize = L.bind(this._onResize, this);\r
+\r
+               this._initEvents();\r
+\r
+               if (options.maxBounds) {\r
+                       this.setMaxBounds(options.maxBounds);\r
+               }\r
+\r
+               if (options.center && options.zoom !== undefined) {\r
+                       this.setView(L.latLng(options.center), options.zoom, {reset: true});\r
+               }\r
+\r
+               this._handlers = [];\r
+\r
+               this._layers = {};\r
+               this._zoomBoundLayers = {};\r
+               this._tileLayersNum = 0;\r
+\r
+               this.callInitHooks();\r
+\r
+               this._addLayers(options.layers);\r
+       },\r
+\r
+\r
+       // public methods that modify map state\r
+\r
+       // replaced by animation-powered implementation in Map.PanAnimation.js\r
+       setView: function (center, zoom) {\r
+               zoom = zoom === undefined ? this.getZoom() : zoom;\r
+               this._resetView(L.latLng(center), this._limitZoom(zoom));\r
+               return this;\r
+       },\r
+\r
+       setZoom: function (zoom, options) {\r
+               if (!this._loaded) {\r
+                       this._zoom = this._limitZoom(zoom);\r
+                       return this;\r
+               }\r
+               return this.setView(this.getCenter(), zoom, {zoom: options});\r
+       },\r
+\r
+       zoomIn: function (delta, options) {\r
+               return this.setZoom(this._zoom + (delta || 1), options);\r
+       },\r
+\r
+       zoomOut: function (delta, options) {\r
+               return this.setZoom(this._zoom - (delta || 1), options);\r
+       },\r
+\r
+       setZoomAround: function (latlng, zoom, options) {\r
+               var scale = this.getZoomScale(zoom),\r
+                   viewHalf = this.getSize().divideBy(2),\r
+                   containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),\r
+\r
+                   centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),\r
+                   newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));\r
+\r
+               return this.setView(newCenter, zoom, {zoom: options});\r
+       },\r
+\r
+       fitBounds: function (bounds, options) {\r
+\r
+               options = options || {};\r
+               bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);\r
+\r
+               var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),\r
+                   paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),\r
+\r
+                   zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)),\r
+                   paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),\r
+\r
+                   swPoint = this.project(bounds.getSouthWest(), zoom),\r
+                   nePoint = this.project(bounds.getNorthEast(), zoom),\r
+                   center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);\r
+\r
+               zoom = options && options.maxZoom ? Math.min(options.maxZoom, zoom) : zoom;\r
+\r
+               return this.setView(center, zoom, options);\r
+       },\r
+\r
+       fitWorld: function (options) {\r
+               return this.fitBounds([[-90, -180], [90, 180]], options);\r
+       },\r
+\r
+       panTo: function (center, options) { // (LatLng)\r
+               return this.setView(center, this._zoom, {pan: options});\r
+       },\r
+\r
+       panBy: function (offset) { // (Point)\r
+               // replaced with animated panBy in Map.PanAnimation.js\r
+               this.fire('movestart');\r
+\r
+               this._rawPanBy(L.point(offset));\r
+\r
+               this.fire('move');\r
+               return this.fire('moveend');\r
+       },\r
+\r
+       setMaxBounds: function (bounds) {\r
+               bounds = L.latLngBounds(bounds);\r
+\r
+               this.options.maxBounds = bounds;\r
+\r
+               if (!bounds) {\r
+                       return this.off('moveend', this._panInsideMaxBounds, this);\r
+               }\r
+\r
+               if (this._loaded) {\r
+                       this._panInsideMaxBounds();\r
+               }\r
+\r
+               return this.on('moveend', this._panInsideMaxBounds, this);\r
+       },\r
+\r
+       panInsideBounds: function (bounds, options) {\r
+               var center = this.getCenter(),\r
+                       newCenter = this._limitCenter(center, this._zoom, bounds);\r
+\r
+               if (center.equals(newCenter)) { return this; }\r
+\r
+               return this.panTo(newCenter, options);\r
+       },\r
+\r
+       addLayer: function (layer) {\r
+               // TODO method is too big, refactor\r
+\r
+               var id = L.stamp(layer);\r
+\r
+               if (this._layers[id]) { return this; }\r
+\r
+               this._layers[id] = layer;\r
+\r
+               // TODO getMaxZoom, getMinZoom in ILayer (instead of options)\r
+               if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) {\r
+                       this._zoomBoundLayers[id] = layer;\r
+                       this._updateZoomLevels();\r
+               }\r
+\r
+               // TODO looks ugly, refactor!!!\r
+               if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {\r
+                       this._tileLayersNum++;\r
+                       this._tileLayersToLoad++;\r
+                       layer.on('load', this._onTileLayerLoad, this);\r
+               }\r
+\r
+               if (this._loaded) {\r
+                       this._layerAdd(layer);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       removeLayer: function (layer) {\r
+               var id = L.stamp(layer);\r
+\r
+               if (!this._layers[id]) { return this; }\r
+\r
+               if (this._loaded) {\r
+                       layer.onRemove(this);\r
+               }\r
+\r
+               delete this._layers[id];\r
+\r
+               if (this._loaded) {\r
+                       this.fire('layerremove', {layer: layer});\r
+               }\r
+\r
+               if (this._zoomBoundLayers[id]) {\r
+                       delete this._zoomBoundLayers[id];\r
+                       this._updateZoomLevels();\r
+               }\r
+\r
+               // TODO looks ugly, refactor\r
+               if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {\r
+                       this._tileLayersNum--;\r
+                       this._tileLayersToLoad--;\r
+                       layer.off('load', this._onTileLayerLoad, this);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       hasLayer: function (layer) {\r
+               if (!layer) { return false; }\r
+\r
+               return (L.stamp(layer) in this._layers);\r
+       },\r
+\r
+       eachLayer: function (method, context) {\r
+               for (var i in this._layers) {\r
+                       method.call(context, this._layers[i]);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       invalidateSize: function (options) {\r
+               if (!this._loaded) { return this; }\r
+\r
+               options = L.extend({\r
+                       animate: false,\r
+                       pan: true\r
+               }, options === true ? {animate: true} : options);\r
+\r
+               var oldSize = this.getSize();\r
+               this._sizeChanged = true;\r
+               this._initialCenter = null;\r
+\r
+               var newSize = this.getSize(),\r
+                   oldCenter = oldSize.divideBy(2).round(),\r
+                   newCenter = newSize.divideBy(2).round(),\r
+                   offset = oldCenter.subtract(newCenter);\r
+\r
+               if (!offset.x && !offset.y) { return this; }\r
+\r
+               if (options.animate && options.pan) {\r
+                       this.panBy(offset);\r
+\r
+               } else {\r
+                       if (options.pan) {\r
+                               this._rawPanBy(offset);\r
+                       }\r
+\r
+                       this.fire('move');\r
+\r
+                       if (options.debounceMoveend) {\r
+                               clearTimeout(this._sizeTimer);\r
+                               this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);\r
+                       } else {\r
+                               this.fire('moveend');\r
+                       }\r
+               }\r
+\r
+               return this.fire('resize', {\r
+                       oldSize: oldSize,\r
+                       newSize: newSize\r
+               });\r
+       },\r
+\r
+       // TODO handler.addTo\r
+       addHandler: function (name, HandlerClass) {\r
+               if (!HandlerClass) { return this; }\r
+\r
+               var handler = this[name] = new HandlerClass(this);\r
+\r
+               this._handlers.push(handler);\r
+\r
+               if (this.options[name]) {\r
+                       handler.enable();\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       remove: function () {\r
+               if (this._loaded) {\r
+                       this.fire('unload');\r
+               }\r
+\r
+               this._initEvents('off');\r
+\r
+               try {\r
+                       // throws error in IE6-8\r
+                       delete this._container._leaflet;\r
+               } catch (e) {\r
+                       this._container._leaflet = undefined;\r
+               }\r
+\r
+               this._clearPanes();\r
+               if (this._clearControlPos) {\r
+                       this._clearControlPos();\r
+               }\r
+\r
+               this._clearHandlers();\r
+\r
+               return this;\r
+       },\r
+\r
+\r
+       // public methods for getting map state\r
+\r
+       getCenter: function () { // (Boolean) -> LatLng\r
+               this._checkIfLoaded();\r
+\r
+               if (this._initialCenter && !this._moved()) {\r
+                       return this._initialCenter;\r
+               }\r
+               return this.layerPointToLatLng(this._getCenterLayerPoint());\r
+       },\r
+\r
+       getZoom: function () {\r
+               return this._zoom;\r
+       },\r
+\r
+       getBounds: function () {\r
+               var bounds = this.getPixelBounds(),\r
+                   sw = this.unproject(bounds.getBottomLeft()),\r
+                   ne = this.unproject(bounds.getTopRight());\r
+\r
+               return new L.LatLngBounds(sw, ne);\r
+       },\r
+\r
+       getMinZoom: function () {\r
+               return this.options.minZoom === undefined ?\r
+                       (this._layersMinZoom === undefined ? 0 : this._layersMinZoom) :\r
+                       this.options.minZoom;\r
+       },\r
+\r
+       getMaxZoom: function () {\r
+               return this.options.maxZoom === undefined ?\r
+                       (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :\r
+                       this.options.maxZoom;\r
+       },\r
+\r
+       getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number\r
+               bounds = L.latLngBounds(bounds);\r
+\r
+               var zoom = this.getMinZoom() - (inside ? 1 : 0),\r
+                   maxZoom = this.getMaxZoom(),\r
+                   size = this.getSize(),\r
+\r
+                   nw = bounds.getNorthWest(),\r
+                   se = bounds.getSouthEast(),\r
+\r
+                   zoomNotFound = true,\r
+                   boundsSize;\r
+\r
+               padding = L.point(padding || [0, 0]);\r
+\r
+               do {\r
+                       zoom++;\r
+                       boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)).add(padding);\r
+                       zoomNotFound = !inside ? size.contains(boundsSize) : boundsSize.x < size.x || boundsSize.y < size.y;\r
+\r
+               } while (zoomNotFound && zoom <= maxZoom);\r
+\r
+               if (zoomNotFound && inside) {\r
+                       return null;\r
+               }\r
+\r
+               return inside ? zoom : zoom - 1;\r
+       },\r
+\r
+       getSize: function () {\r
+               if (!this._size || this._sizeChanged) {\r
+                       this._size = new L.Point(\r
+                               this._container.clientWidth,\r
+                               this._container.clientHeight);\r
+\r
+                       this._sizeChanged = false;\r
+               }\r
+               return this._size.clone();\r
+       },\r
+\r
+       getPixelBounds: function () {\r
+               var topLeftPoint = this._getTopLeftPoint();\r
+               return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));\r
+       },\r
+\r
+       getPixelOrigin: function () {\r
+               this._checkIfLoaded();\r
+               return this._initialTopLeftPoint;\r
+       },\r
+\r
+       getPanes: function () {\r
+               return this._panes;\r
+       },\r
+\r
+       getContainer: function () {\r
+               return this._container;\r
+       },\r
+\r
+\r
+       // TODO replace with universal implementation after refactoring projections\r
+\r
+       getZoomScale: function (toZoom) {\r
+               var crs = this.options.crs;\r
+               return crs.scale(toZoom) / crs.scale(this._zoom);\r
+       },\r
+\r
+       getScaleZoom: function (scale) {\r
+               return this._zoom + (Math.log(scale) / Math.LN2);\r
+       },\r
+\r
+\r
+       // conversion methods\r
+\r
+       project: function (latlng, zoom) { // (LatLng[, Number]) -> Point\r
+               zoom = zoom === undefined ? this._zoom : zoom;\r
+               return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);\r
+       },\r
+\r
+       unproject: function (point, zoom) { // (Point[, Number]) -> LatLng\r
+               zoom = zoom === undefined ? this._zoom : zoom;\r
+               return this.options.crs.pointToLatLng(L.point(point), zoom);\r
+       },\r
+\r
+       layerPointToLatLng: function (point) { // (Point)\r
+               var projectedPoint = L.point(point).add(this.getPixelOrigin());\r
+               return this.unproject(projectedPoint);\r
+       },\r
+\r
+       latLngToLayerPoint: function (latlng) { // (LatLng)\r
+               var projectedPoint = this.project(L.latLng(latlng))._round();\r
+               return projectedPoint._subtract(this.getPixelOrigin());\r
+       },\r
+\r
+       containerPointToLayerPoint: function (point) { // (Point)\r
+               return L.point(point).subtract(this._getMapPanePos());\r
+       },\r
+\r
+       layerPointToContainerPoint: function (point) { // (Point)\r
+               return L.point(point).add(this._getMapPanePos());\r
+       },\r
+\r
+       containerPointToLatLng: function (point) {\r
+               var layerPoint = this.containerPointToLayerPoint(L.point(point));\r
+               return this.layerPointToLatLng(layerPoint);\r
+       },\r
+\r
+       latLngToContainerPoint: function (latlng) {\r
+               return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));\r
+       },\r
+\r
+       mouseEventToContainerPoint: function (e) { // (MouseEvent)\r
+               return L.DomEvent.getMousePosition(e, this._container);\r
+       },\r
+\r
+       mouseEventToLayerPoint: function (e) { // (MouseEvent)\r
+               return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));\r
+       },\r
+\r
+       mouseEventToLatLng: function (e) { // (MouseEvent)\r
+               return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));\r
+       },\r
+\r
+\r
+       // map initialization methods\r
+\r
+       _initContainer: function (id) {\r
+               var container = this._container = L.DomUtil.get(id);\r
+\r
+               if (!container) {\r
+                       throw new Error('Map container not found.');\r
+               } else if (container._leaflet) {\r
+                       throw new Error('Map container is already initialized.');\r
+               }\r
+\r
+               container._leaflet = true;\r
+       },\r
+\r
+       _initLayout: function () {\r
+               var container = this._container;\r
+\r
+               L.DomUtil.addClass(container, 'leaflet-container' +\r
+                       (L.Browser.touch ? ' leaflet-touch' : '') +\r
+                       (L.Browser.retina ? ' leaflet-retina' : '') +\r
+                       (L.Browser.ielt9 ? ' leaflet-oldie' : '') +\r
+                       (this.options.fadeAnimation ? ' leaflet-fade-anim' : ''));\r
+\r
+               var position = L.DomUtil.getStyle(container, 'position');\r
+\r
+               if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {\r
+                       container.style.position = 'relative';\r
+               }\r
+\r
+               this._initPanes();\r
+\r
+               if (this._initControlPos) {\r
+                       this._initControlPos();\r
+               }\r
+       },\r
+\r
+       _initPanes: function () {\r
+               var panes = this._panes = {};\r
+\r
+               this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container);\r
+\r
+               this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane);\r
+               panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane);\r
+               panes.shadowPane = this._createPane('leaflet-shadow-pane');\r
+               panes.overlayPane = this._createPane('leaflet-overlay-pane');\r
+               panes.markerPane = this._createPane('leaflet-marker-pane');\r
+               panes.popupPane = this._createPane('leaflet-popup-pane');\r
+\r
+               var zoomHide = ' leaflet-zoom-hide';\r
+\r
+               if (!this.options.markerZoomAnimation) {\r
+                       L.DomUtil.addClass(panes.markerPane, zoomHide);\r
+                       L.DomUtil.addClass(panes.shadowPane, zoomHide);\r
+                       L.DomUtil.addClass(panes.popupPane, zoomHide);\r
+               }\r
+       },\r
+\r
+       _createPane: function (className, container) {\r
+               return L.DomUtil.create('div', className, container || this._panes.objectsPane);\r
+       },\r
+\r
+       _clearPanes: function () {\r
+               this._container.removeChild(this._mapPane);\r
+       },\r
+\r
+       _addLayers: function (layers) {\r
+               layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];\r
+\r
+               for (var i = 0, len = layers.length; i < len; i++) {\r
+                       this.addLayer(layers[i]);\r
+               }\r
+       },\r
+\r
+\r
+       // private methods that modify map state\r
+\r
+       _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) {\r
+\r
+               var zoomChanged = (this._zoom !== zoom);\r
+\r
+               if (!afterZoomAnim) {\r
+                       this.fire('movestart');\r
+\r
+                       if (zoomChanged) {\r
+                               this.fire('zoomstart');\r
+                       }\r
+               }\r
+\r
+               this._zoom = zoom;\r
+               this._initialCenter = center;\r
+\r
+               this._initialTopLeftPoint = this._getNewTopLeftPoint(center);\r
+\r
+               if (!preserveMapOffset) {\r
+                       L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));\r
+               } else {\r
+                       this._initialTopLeftPoint._add(this._getMapPanePos());\r
+               }\r
+\r
+               this._tileLayersToLoad = this._tileLayersNum;\r
+\r
+               var loading = !this._loaded;\r
+               this._loaded = true;\r
+\r
+               this.fire('viewreset', {hard: !preserveMapOffset});\r
+\r
+               if (loading) {\r
+                       this.fire('load');\r
+                       this.eachLayer(this._layerAdd, this);\r
+               }\r
+\r
+               this.fire('move');\r
+\r
+               if (zoomChanged || afterZoomAnim) {\r
+                       this.fire('zoomend');\r
+               }\r
+\r
+               this.fire('moveend', {hard: !preserveMapOffset});\r
+       },\r
+\r
+       _rawPanBy: function (offset) {\r
+               L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));\r
+       },\r
+\r
+       _getZoomSpan: function () {\r
+               return this.getMaxZoom() - this.getMinZoom();\r
+       },\r
+\r
+       _updateZoomLevels: function () {\r
+               var i,\r
+                       minZoom = Infinity,\r
+                       maxZoom = -Infinity,\r
+                       oldZoomSpan = this._getZoomSpan();\r
+\r
+               for (i in this._zoomBoundLayers) {\r
+                       var layer = this._zoomBoundLayers[i];\r
+                       if (!isNaN(layer.options.minZoom)) {\r
+                               minZoom = Math.min(minZoom, layer.options.minZoom);\r
+                       }\r
+                       if (!isNaN(layer.options.maxZoom)) {\r
+                               maxZoom = Math.max(maxZoom, layer.options.maxZoom);\r
+                       }\r
+               }\r
+\r
+               if (i === undefined) { // we have no tilelayers\r
+                       this._layersMaxZoom = this._layersMinZoom = undefined;\r
+               } else {\r
+                       this._layersMaxZoom = maxZoom;\r
+                       this._layersMinZoom = minZoom;\r
+               }\r
+\r
+               if (oldZoomSpan !== this._getZoomSpan()) {\r
+                       this.fire('zoomlevelschange');\r
+               }\r
+       },\r
+\r
+       _panInsideMaxBounds: function () {\r
+               this.panInsideBounds(this.options.maxBounds);\r
+       },\r
+\r
+       _checkIfLoaded: function () {\r
+               if (!this._loaded) {\r
+                       throw new Error('Set map center and zoom first.');\r
+               }\r
+       },\r
+\r
+       // map events\r
+\r
+       _initEvents: function (onOff) {\r
+               if (!L.DomEvent) { return; }\r
+\r
+               onOff = onOff || 'on';\r
+\r
+               L.DomEvent[onOff](this._container, 'click', this._onMouseClick, this);\r
+\r
+               var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter',\r
+                             'mouseleave', 'mousemove', 'contextmenu'],\r
+                   i, len;\r
+\r
+               for (i = 0, len = events.length; i < len; i++) {\r
+                       L.DomEvent[onOff](this._container, events[i], this._fireMouseEvent, this);\r
+               }\r
+\r
+               if (this.options.trackResize) {\r
+                       L.DomEvent[onOff](window, 'resize', this._onResize, this);\r
+               }\r
+       },\r
+\r
+       _onResize: function () {\r
+               L.Util.cancelAnimFrame(this._resizeRequest);\r
+               this._resizeRequest = L.Util.requestAnimFrame(\r
+                       function () { this.invalidateSize({debounceMoveend: true}); }, this, false, this._container);\r
+       },\r
+\r
+       _onMouseClick: function (e) {\r
+               if (!this._loaded || (!e._simulated &&\r
+                       ((this.dragging && this.dragging.moved()) ||\r
+                        (this.boxZoom  && this.boxZoom.moved()))) ||\r
+                           L.DomEvent._skipped(e)) { return; }\r
+\r
+               this.fire('preclick');\r
+               this._fireMouseEvent(e);\r
+       },\r
+\r
+       _fireMouseEvent: function (e) {\r
+               if (!this._loaded || L.DomEvent._skipped(e)) { return; }\r
+\r
+               var type = e.type;\r
+\r
+               type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));\r
+\r
+               if (!this.hasEventListeners(type)) { return; }\r
+\r
+               if (type === 'contextmenu') {\r
+                       L.DomEvent.preventDefault(e);\r
+               }\r
+\r
+               var containerPoint = this.mouseEventToContainerPoint(e),\r
+                   layerPoint = this.containerPointToLayerPoint(containerPoint),\r
+                   latlng = this.layerPointToLatLng(layerPoint);\r
+\r
+               this.fire(type, {\r
+                       latlng: latlng,\r
+                       layerPoint: layerPoint,\r
+                       containerPoint: containerPoint,\r
+                       originalEvent: e\r
+               });\r
+       },\r
+\r
+       _onTileLayerLoad: function () {\r
+               this._tileLayersToLoad--;\r
+               if (this._tileLayersNum && !this._tileLayersToLoad) {\r
+                       this.fire('tilelayersload');\r
+               }\r
+       },\r
+\r
+       _clearHandlers: function () {\r
+               for (var i = 0, len = this._handlers.length; i < len; i++) {\r
+                       this._handlers[i].disable();\r
+               }\r
+       },\r
+\r
+       whenReady: function (callback, context) {\r
+               if (this._loaded) {\r
+                       callback.call(context || this, this);\r
+               } else {\r
+                       this.on('load', callback, context);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       _layerAdd: function (layer) {\r
+               layer.onAdd(this);\r
+               this.fire('layeradd', {layer: layer});\r
+       },\r
+\r
+\r
+       // private methods for getting map state\r
+\r
+       _getMapPanePos: function () {\r
+               return L.DomUtil.getPosition(this._mapPane);\r
+       },\r
+\r
+       _moved: function () {\r
+               var pos = this._getMapPanePos();\r
+               return pos && !pos.equals([0, 0]);\r
+       },\r
+\r
+       _getTopLeftPoint: function () {\r
+               return this.getPixelOrigin().subtract(this._getMapPanePos());\r
+       },\r
+\r
+       _getNewTopLeftPoint: function (center, zoom) {\r
+               var viewHalf = this.getSize()._divideBy(2);\r
+               // TODO round on display, not calculation to increase precision?\r
+               return this.project(center, zoom)._subtract(viewHalf)._round();\r
+       },\r
+\r
+       _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) {\r
+               var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos());\r
+               return this.project(latlng, newZoom)._subtract(topLeft);\r
+       },\r
+\r
+       // layer point of the current center\r
+       _getCenterLayerPoint: function () {\r
+               return this.containerPointToLayerPoint(this.getSize()._divideBy(2));\r
+       },\r
+\r
+       // offset of the specified place to the current center in pixels\r
+       _getCenterOffset: function (latlng) {\r
+               return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());\r
+       },\r
+\r
+       // adjust center for view to get inside bounds\r
+       _limitCenter: function (center, zoom, bounds) {\r
+\r
+               if (!bounds) { return center; }\r
+\r
+               var centerPoint = this.project(center, zoom),\r
+                   viewHalf = this.getSize().divideBy(2),\r
+                   viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),\r
+                   offset = this._getBoundsOffset(viewBounds, bounds, zoom);\r
+\r
+               return this.unproject(centerPoint.add(offset), zoom);\r
+       },\r
+\r
+       // adjust offset for view to get inside bounds\r
+       _limitOffset: function (offset, bounds) {\r
+               if (!bounds) { return offset; }\r
+\r
+               var viewBounds = this.getPixelBounds(),\r
+                   newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));\r
+\r
+               return offset.add(this._getBoundsOffset(newBounds, bounds));\r
+       },\r
+\r
+       // returns offset needed for pxBounds to get inside maxBounds at a specified zoom\r
+       _getBoundsOffset: function (pxBounds, maxBounds, zoom) {\r
+               var nwOffset = this.project(maxBounds.getNorthWest(), zoom).subtract(pxBounds.min),\r
+                   seOffset = this.project(maxBounds.getSouthEast(), zoom).subtract(pxBounds.max),\r
+\r
+                   dx = this._rebound(nwOffset.x, -seOffset.x),\r
+                   dy = this._rebound(nwOffset.y, -seOffset.y);\r
+\r
+               return new L.Point(dx, dy);\r
+       },\r
+\r
+       _rebound: function (left, right) {\r
+               return left + right > 0 ?\r
+                       Math.round(left - right) / 2 :\r
+                       Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));\r
+       },\r
+\r
+       _limitZoom: function (zoom) {\r
+               var min = this.getMinZoom(),\r
+                   max = this.getMaxZoom();\r
+\r
+               return Math.max(min, Math.min(max, zoom));\r
+       }\r
+});\r
+\r
+L.map = function (id, options) {\r
+       return new L.Map(id, options);\r
+};\r
+
+
+/*\r
+ * Mercator projection that takes into account that the Earth is not a perfect sphere.\r
+ * Less popular than spherical mercator; used by projections like EPSG:3395.\r
+ */\r
+\r
+L.Projection.Mercator = {\r
+       MAX_LATITUDE: 85.0840591556,\r
+\r
+       R_MINOR: 6356752.314245179,\r
+       R_MAJOR: 6378137,\r
+\r
+       project: function (latlng) { // (LatLng) -> Point\r
+               var d = L.LatLng.DEG_TO_RAD,\r
+                   max = this.MAX_LATITUDE,\r
+                   lat = Math.max(Math.min(max, latlng.lat), -max),\r
+                   r = this.R_MAJOR,\r
+                   r2 = this.R_MINOR,\r
+                   x = latlng.lng * d * r,\r
+                   y = lat * d,\r
+                   tmp = r2 / r,\r
+                   eccent = Math.sqrt(1.0 - tmp * tmp),\r
+                   con = eccent * Math.sin(y);\r
+\r
+               con = Math.pow((1 - con) / (1 + con), eccent * 0.5);\r
+\r
+               var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con;\r
+               y = -r * Math.log(ts);\r
+\r
+               return new L.Point(x, y);\r
+       },\r
+\r
+       unproject: function (point) { // (Point, Boolean) -> LatLng\r
+               var d = L.LatLng.RAD_TO_DEG,\r
+                   r = this.R_MAJOR,\r
+                   r2 = this.R_MINOR,\r
+                   lng = point.x * d / r,\r
+                   tmp = r2 / r,\r
+                   eccent = Math.sqrt(1 - (tmp * tmp)),\r
+                   ts = Math.exp(- point.y / r),\r
+                   phi = (Math.PI / 2) - 2 * Math.atan(ts),\r
+                   numIter = 15,\r
+                   tol = 1e-7,\r
+                   i = numIter,\r
+                   dphi = 0.1,\r
+                   con;\r
+\r
+               while ((Math.abs(dphi) > tol) && (--i > 0)) {\r
+                       con = eccent * Math.sin(phi);\r
+                       dphi = (Math.PI / 2) - 2 * Math.atan(ts *\r
+                                   Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi;\r
+                       phi += dphi;\r
+               }\r
+\r
+               return new L.LatLng(phi * d, lng);\r
+       }\r
+};\r
+
+
+\r
+L.CRS.EPSG3395 = L.extend({}, L.CRS, {\r
+       code: 'EPSG:3395',\r
+\r
+       projection: L.Projection.Mercator,\r
+\r
+       transformation: (function () {\r
+               var m = L.Projection.Mercator,\r
+                   r = m.R_MAJOR,\r
+                   scale = 0.5 / (Math.PI * r);\r
+\r
+               return new L.Transformation(scale, 0.5, -scale, 0.5);\r
+       }())\r
+});\r
+
+
+/*\r
+ * L.TileLayer is used for standard xyz-numbered tile layers.\r
+ */\r
+\r
+L.TileLayer = L.Class.extend({\r
+       includes: L.Mixin.Events,\r
+\r
+       options: {\r
+               minZoom: 0,\r
+               maxZoom: 18,\r
+               tileSize: 256,\r
+               subdomains: 'abc',\r
+               errorTileUrl: '',\r
+               attribution: '',\r
+               zoomOffset: 0,\r
+               opacity: 1,\r
+               /*\r
+               maxNativeZoom: null,\r
+               zIndex: null,\r
+               tms: false,\r
+               continuousWorld: false,\r
+               noWrap: false,\r
+               zoomReverse: false,\r
+               detectRetina: false,\r
+               reuseTiles: false,\r
+               bounds: false,\r
+               */\r
+               unloadInvisibleTiles: L.Browser.mobile,\r
+               updateWhenIdle: L.Browser.mobile\r
+       },\r
+\r
+       initialize: function (url, options) {\r
+               options = L.setOptions(this, options);\r
+\r
+               // detecting retina displays, adjusting tileSize and zoom levels\r
+               if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {\r
+\r
+                       options.tileSize = Math.floor(options.tileSize / 2);\r
+                       options.zoomOffset++;\r
+\r
+                       if (options.minZoom > 0) {\r
+                               options.minZoom--;\r
+                       }\r
+                       this.options.maxZoom--;\r
+               }\r
+\r
+               if (options.bounds) {\r
+                       options.bounds = L.latLngBounds(options.bounds);\r
+               }\r
+\r
+               this._url = url;\r
+\r
+               var subdomains = this.options.subdomains;\r
+\r
+               if (typeof subdomains === 'string') {\r
+                       this.options.subdomains = subdomains.split('');\r
+               }\r
+       },\r
+\r
+       onAdd: function (map) {\r
+               this._map = map;\r
+               this._animated = map._zoomAnimated;\r
+\r
+               // create a container div for tiles\r
+               this._initContainer();\r
+\r
+               // set up events\r
+               map.on({\r
+                       'viewreset': this._reset,\r
+                       'moveend': this._update\r
+               }, this);\r
+\r
+               if (this._animated) {\r
+                       map.on({\r
+                               'zoomanim': this._animateZoom,\r
+                               'zoomend': this._endZoomAnim\r
+                       }, this);\r
+               }\r
+\r
+               if (!this.options.updateWhenIdle) {\r
+                       this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);\r
+                       map.on('move', this._limitedUpdate, this);\r
+               }\r
+\r
+               this._reset();\r
+               this._update();\r
+       },\r
+\r
+       addTo: function (map) {\r
+               map.addLayer(this);\r
+               return this;\r
+       },\r
+\r
+       onRemove: function (map) {\r
+               this._container.parentNode.removeChild(this._container);\r
+\r
+               map.off({\r
+                       'viewreset': this._reset,\r
+                       'moveend': this._update\r
+               }, this);\r
+\r
+               if (this._animated) {\r
+                       map.off({\r
+                               'zoomanim': this._animateZoom,\r
+                               'zoomend': this._endZoomAnim\r
+                       }, this);\r
+               }\r
+\r
+               if (!this.options.updateWhenIdle) {\r
+                       map.off('move', this._limitedUpdate, this);\r
+               }\r
+\r
+               this._container = null;\r
+               this._map = null;\r
+       },\r
+\r
+       bringToFront: function () {\r
+               var pane = this._map._panes.tilePane;\r
+\r
+               if (this._container) {\r
+                       pane.appendChild(this._container);\r
+                       this._setAutoZIndex(pane, Math.max);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       bringToBack: function () {\r
+               var pane = this._map._panes.tilePane;\r
+\r
+               if (this._container) {\r
+                       pane.insertBefore(this._container, pane.firstChild);\r
+                       this._setAutoZIndex(pane, Math.min);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       getAttribution: function () {\r
+               return this.options.attribution;\r
+       },\r
+\r
+       getContainer: function () {\r
+               return this._container;\r
+       },\r
+\r
+       setOpacity: function (opacity) {\r
+               this.options.opacity = opacity;\r
+\r
+               if (this._map) {\r
+                       this._updateOpacity();\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       setZIndex: function (zIndex) {\r
+               this.options.zIndex = zIndex;\r
+               this._updateZIndex();\r
+\r
+               return this;\r
+       },\r
+\r
+       setUrl: function (url, noRedraw) {\r
+               this._url = url;\r
+\r
+               if (!noRedraw) {\r
+                       this.redraw();\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       redraw: function () {\r
+               if (this._map) {\r
+                       this._reset({hard: true});\r
+                       this._update();\r
+               }\r
+               return this;\r
+       },\r
+\r
+       _updateZIndex: function () {\r
+               if (this._container && this.options.zIndex !== undefined) {\r
+                       this._container.style.zIndex = this.options.zIndex;\r
+               }\r
+       },\r
+\r
+       _setAutoZIndex: function (pane, compare) {\r
+\r
+               var layers = pane.children,\r
+                   edgeZIndex = -compare(Infinity, -Infinity), // -Infinity for max, Infinity for min\r
+                   zIndex, i, len;\r
+\r
+               for (i = 0, len = layers.length; i < len; i++) {\r
+\r
+                       if (layers[i] !== this._container) {\r
+                               zIndex = parseInt(layers[i].style.zIndex, 10);\r
+\r
+                               if (!isNaN(zIndex)) {\r
+                                       edgeZIndex = compare(edgeZIndex, zIndex);\r
+                               }\r
+                       }\r
+               }\r
+\r
+               this.options.zIndex = this._container.style.zIndex =\r
+                       (isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1);\r
+       },\r
+\r
+       _updateOpacity: function () {\r
+               var i,\r
+                   tiles = this._tiles;\r
+\r
+               if (L.Browser.ielt9) {\r
+                       for (i in tiles) {\r
+                               L.DomUtil.setOpacity(tiles[i], this.options.opacity);\r
+                       }\r
+               } else {\r
+                       L.DomUtil.setOpacity(this._container, this.options.opacity);\r
+               }\r
+       },\r
+\r
+       _initContainer: function () {\r
+               var tilePane = this._map._panes.tilePane;\r
+\r
+               if (!this._container) {\r
+                       this._container = L.DomUtil.create('div', 'leaflet-layer');\r
+\r
+                       this._updateZIndex();\r
+\r
+                       if (this._animated) {\r
+                               var className = 'leaflet-tile-container';\r
+\r
+                               this._bgBuffer = L.DomUtil.create('div', className, this._container);\r
+                               this._tileContainer = L.DomUtil.create('div', className, this._container);\r
+\r
+                       } else {\r
+                               this._tileContainer = this._container;\r
+                       }\r
+\r
+                       tilePane.appendChild(this._container);\r
+\r
+                       if (this.options.opacity < 1) {\r
+                               this._updateOpacity();\r
+                       }\r
+               }\r
+       },\r
+\r
+       _reset: function (e) {\r
+               for (var key in this._tiles) {\r
+                       this.fire('tileunload', {tile: this._tiles[key]});\r
+               }\r
+\r
+               this._tiles = {};\r
+               this._tilesToLoad = 0;\r
+\r
+               if (this.options.reuseTiles) {\r
+                       this._unusedTiles = [];\r
+               }\r
+\r
+               this._tileContainer.innerHTML = '';\r
+\r
+               if (this._animated && e && e.hard) {\r
+                       this._clearBgBuffer();\r
+               }\r
+\r
+               this._initContainer();\r
+       },\r
+\r
+       _getTileSize: function () {\r
+               var map = this._map,\r
+                   zoom = map.getZoom() + this.options.zoomOffset,\r
+                   zoomN = this.options.maxNativeZoom,\r
+                   tileSize = this.options.tileSize;\r
+\r
+               if (zoomN && zoom > zoomN) {\r
+                       tileSize = Math.round(map.getZoomScale(zoom) / map.getZoomScale(zoomN) * tileSize);\r
+               }\r
+\r
+               return tileSize;\r
+       },\r
+\r
+       _update: function () {\r
+\r
+               if (!this._map) { return; }\r
+\r
+               var map = this._map,\r
+                   bounds = map.getPixelBounds(),\r
+                   zoom = map.getZoom(),\r
+                   tileSize = this._getTileSize();\r
+\r
+               if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {\r
+                       return;\r
+               }\r
+\r
+               var tileBounds = L.bounds(\r
+                       bounds.min.divideBy(tileSize)._floor(),\r
+                       bounds.max.divideBy(tileSize)._floor());\r
+\r
+               this._addTilesFromCenterOut(tileBounds);\r
+\r
+               if (this.options.unloadInvisibleTiles || this.options.reuseTiles) {\r
+                       this._removeOtherTiles(tileBounds);\r
+               }\r
+       },\r
+\r
+       _addTilesFromCenterOut: function (bounds) {\r
+               var queue = [],\r
+                   center = bounds.getCenter();\r
+\r
+               var j, i, point;\r
+\r
+               for (j = bounds.min.y; j <= bounds.max.y; j++) {\r
+                       for (i = bounds.min.x; i <= bounds.max.x; i++) {\r
+                               point = new L.Point(i, j);\r
+\r
+                               if (this._tileShouldBeLoaded(point)) {\r
+                                       queue.push(point);\r
+                               }\r
+                       }\r
+               }\r
+\r
+               var tilesToLoad = queue.length;\r
+\r
+               if (tilesToLoad === 0) { return; }\r
+\r
+               // load tiles in order of their distance to center\r
+               queue.sort(function (a, b) {\r
+                       return a.distanceTo(center) - b.distanceTo(center);\r
+               });\r
+\r
+               var fragment = document.createDocumentFragment();\r
+\r
+               // if its the first batch of tiles to load\r
+               if (!this._tilesToLoad) {\r
+                       this.fire('loading');\r
+               }\r
+\r
+               this._tilesToLoad += tilesToLoad;\r
+\r
+               for (i = 0; i < tilesToLoad; i++) {\r
+                       this._addTile(queue[i], fragment);\r
+               }\r
+\r
+               this._tileContainer.appendChild(fragment);\r
+       },\r
+\r
+       _tileShouldBeLoaded: function (tilePoint) {\r
+               if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) {\r
+                       return false; // already loaded\r
+               }\r
+\r
+               var options = this.options;\r
+\r
+               if (!options.continuousWorld) {\r
+                       var limit = this._getWrapTileNum();\r
+\r
+                       // don't load if exceeds world bounds\r
+                       if ((options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit.x)) ||\r
+                               tilePoint.y < 0 || tilePoint.y >= limit.y) { return false; }\r
+               }\r
+\r
+               if (options.bounds) {\r
+                       var tileSize = options.tileSize,\r
+                           nwPoint = tilePoint.multiplyBy(tileSize),\r
+                           sePoint = nwPoint.add([tileSize, tileSize]),\r
+                           nw = this._map.unproject(nwPoint),\r
+                           se = this._map.unproject(sePoint);\r
+\r
+                       // TODO temporary hack, will be removed after refactoring projections\r
+                       // https://github.com/Leaflet/Leaflet/issues/1618\r
+                       if (!options.continuousWorld && !options.noWrap) {\r
+                               nw = nw.wrap();\r
+                               se = se.wrap();\r
+                       }\r
+\r
+                       if (!options.bounds.intersects([nw, se])) { return false; }\r
+               }\r
+\r
+               return true;\r
+       },\r
+\r
+       _removeOtherTiles: function (bounds) {\r
+               var kArr, x, y, key;\r
+\r
+               for (key in this._tiles) {\r
+                       kArr = key.split(':');\r
+                       x = parseInt(kArr[0], 10);\r
+                       y = parseInt(kArr[1], 10);\r
+\r
+                       // remove tile if it's out of bounds\r
+                       if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {\r
+                               this._removeTile(key);\r
+                       }\r
+               }\r
+       },\r
+\r
+       _removeTile: function (key) {\r
+               var tile = this._tiles[key];\r
+\r
+               this.fire('tileunload', {tile: tile, url: tile.src});\r
+\r
+               if (this.options.reuseTiles) {\r
+                       L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');\r
+                       this._unusedTiles.push(tile);\r
+\r
+               } else if (tile.parentNode === this._tileContainer) {\r
+                       this._tileContainer.removeChild(tile);\r
+               }\r
+\r
+               // for https://github.com/CloudMade/Leaflet/issues/137\r
+               if (!L.Browser.android) {\r
+                       tile.onload = null;\r
+                       tile.src = L.Util.emptyImageUrl;\r
+               }\r
+\r
+               delete this._tiles[key];\r
+       },\r
+\r
+       _addTile: function (tilePoint, container) {\r
+               var tilePos = this._getTilePos(tilePoint);\r
+\r
+               // get unused tile - or create a new tile\r
+               var tile = this._getTile();\r
+\r
+               /*\r
+               Chrome 20 layouts much faster with top/left (verify with timeline, frames)\r
+               Android 4 browser has display issues with top/left and requires transform instead\r
+               (other browsers don't currently care) - see debug/hacks/jitter.html for an example\r
+               */\r
+               L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome);\r
+\r
+               this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;\r
+\r
+               this._loadTile(tile, tilePoint);\r
+\r
+               if (tile.parentNode !== this._tileContainer) {\r
+                       container.appendChild(tile);\r
+               }\r
+       },\r
+\r
+       _getZoomForUrl: function () {\r
+\r
+               var options = this.options,\r
+                   zoom = this._map.getZoom();\r
+\r
+               if (options.zoomReverse) {\r
+                       zoom = options.maxZoom - zoom;\r
+               }\r
+\r
+               zoom += options.zoomOffset;\r
+\r
+               return options.maxNativeZoom ? Math.min(zoom, options.maxNativeZoom) : zoom;\r
+       },\r
+\r
+       _getTilePos: function (tilePoint) {\r
+               var origin = this._map.getPixelOrigin(),\r
+                   tileSize = this._getTileSize();\r
+\r
+               return tilePoint.multiplyBy(tileSize).subtract(origin);\r
+       },\r
+\r
+       // image-specific code (override to implement e.g. Canvas or SVG tile layer)\r
+\r
+       getTileUrl: function (tilePoint) {\r
+               return L.Util.template(this._url, L.extend({\r
+                       s: this._getSubdomain(tilePoint),\r
+                       z: tilePoint.z,\r
+                       x: tilePoint.x,\r
+                       y: tilePoint.y\r
+               }, this.options));\r
+       },\r
+\r
+       _getWrapTileNum: function () {\r
+               var crs = this._map.options.crs,\r
+                   size = crs.getSize(this._map.getZoom());\r
+               return size.divideBy(this._getTileSize())._floor();\r
+       },\r
+\r
+       _adjustTilePoint: function (tilePoint) {\r
+\r
+               var limit = this._getWrapTileNum();\r
+\r
+               // wrap tile coordinates\r
+               if (!this.options.continuousWorld && !this.options.noWrap) {\r
+                       tilePoint.x = ((tilePoint.x % limit.x) + limit.x) % limit.x;\r
+               }\r
+\r
+               if (this.options.tms) {\r
+                       tilePoint.y = limit.y - tilePoint.y - 1;\r
+               }\r
+\r
+               tilePoint.z = this._getZoomForUrl();\r
+       },\r
+\r
+       _getSubdomain: function (tilePoint) {\r
+               var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;\r
+               return this.options.subdomains[index];\r
+       },\r
+\r
+       _getTile: function () {\r
+               if (this.options.reuseTiles && this._unusedTiles.length > 0) {\r
+                       var tile = this._unusedTiles.pop();\r
+                       this._resetTile(tile);\r
+                       return tile;\r
+               }\r
+               return this._createTile();\r
+       },\r
+\r
+       // Override if data stored on a tile needs to be cleaned up before reuse\r
+       _resetTile: function (/*tile*/) {},\r
+\r
+       _createTile: function () {\r
+               var tile = L.DomUtil.create('img', 'leaflet-tile');\r
+               tile.style.width = tile.style.height = this._getTileSize() + 'px';\r
+               tile.galleryimg = 'no';\r
+\r
+               tile.onselectstart = tile.onmousemove = L.Util.falseFn;\r
+\r
+               if (L.Browser.ielt9 && this.options.opacity !== undefined) {\r
+                       L.DomUtil.setOpacity(tile, this.options.opacity);\r
+               }\r
+               // without this hack, tiles disappear after zoom on Chrome for Android\r
+               // https://github.com/Leaflet/Leaflet/issues/2078\r
+               if (L.Browser.mobileWebkit3d) {\r
+                       tile.style.WebkitBackfaceVisibility = 'hidden';\r
+               }\r
+               return tile;\r
+       },\r
+\r
+       _loadTile: function (tile, tilePoint) {\r
+               tile._layer  = this;\r
+               tile.onload  = this._tileOnLoad;\r
+               tile.onerror = this._tileOnError;\r
+\r
+               this._adjustTilePoint(tilePoint);\r
+               tile.src     = this.getTileUrl(tilePoint);\r
+\r
+               this.fire('tileloadstart', {\r
+                       tile: tile,\r
+                       url: tile.src\r
+               });\r
+       },\r
+\r
+       _tileLoaded: function () {\r
+               this._tilesToLoad--;\r
+\r
+               if (this._animated) {\r
+                       L.DomUtil.addClass(this._tileContainer, 'leaflet-zoom-animated');\r
+               }\r
+\r
+               if (!this._tilesToLoad) {\r
+                       this.fire('load');\r
+\r
+                       if (this._animated) {\r
+                               // clear scaled tiles after all new tiles are loaded (for performance)\r
+                               clearTimeout(this._clearBgBufferTimer);\r
+                               this._clearBgBufferTimer = setTimeout(L.bind(this._clearBgBuffer, this), 500);\r
+                       }\r
+               }\r
+       },\r
+\r
+       _tileOnLoad: function () {\r
+               var layer = this._layer;\r
+\r
+               //Only if we are loading an actual image\r
+               if (this.src !== L.Util.emptyImageUrl) {\r
+                       L.DomUtil.addClass(this, 'leaflet-tile-loaded');\r
+\r
+                       layer.fire('tileload', {\r
+                               tile: this,\r
+                               url: this.src\r
+                       });\r
+               }\r
+\r
+               layer._tileLoaded();\r
+       },\r
+\r
+       _tileOnError: function () {\r
+               var layer = this._layer;\r
+\r
+               layer.fire('tileerror', {\r
+                       tile: this,\r
+                       url: this.src\r
+               });\r
+\r
+               var newUrl = layer.options.errorTileUrl;\r
+               if (newUrl) {\r
+                       this.src = newUrl;\r
+               }\r
+\r
+               layer._tileLoaded();\r
+       }\r
+});\r
+\r
+L.tileLayer = function (url, options) {\r
+       return new L.TileLayer(url, options);\r
+};\r
+
+
+/*\r
+ * L.TileLayer.WMS is used for putting WMS tile layers on the map.\r
+ */\r
+\r
+L.TileLayer.WMS = L.TileLayer.extend({\r
+\r
+       defaultWmsParams: {\r
+               service: 'WMS',\r
+               request: 'GetMap',\r
+               version: '1.1.1',\r
+               layers: '',\r
+               styles: '',\r
+               format: 'image/jpeg',\r
+               transparent: false\r
+       },\r
+\r
+       initialize: function (url, options) { // (String, Object)\r
+\r
+               this._url = url;\r
+\r
+               var wmsParams = L.extend({}, this.defaultWmsParams),\r
+                   tileSize = options.tileSize || this.options.tileSize;\r
+\r
+               if (options.detectRetina && L.Browser.retina) {\r
+                       wmsParams.width = wmsParams.height = tileSize * 2;\r
+               } else {\r
+                       wmsParams.width = wmsParams.height = tileSize;\r
+               }\r
+\r
+               for (var i in options) {\r
+                       // all keys that are not TileLayer options go to WMS params\r
+                       if (!this.options.hasOwnProperty(i) && i !== 'crs') {\r
+                               wmsParams[i] = options[i];\r
+                       }\r
+               }\r
+\r
+               this.wmsParams = wmsParams;\r
+\r
+               L.setOptions(this, options);\r
+       },\r
+\r
+       onAdd: function (map) {\r
+\r
+               this._crs = this.options.crs || map.options.crs;\r
+\r
+               this._wmsVersion = parseFloat(this.wmsParams.version);\r
+\r
+               var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';\r
+               this.wmsParams[projectionKey] = this._crs.code;\r
+\r
+               L.TileLayer.prototype.onAdd.call(this, map);\r
+       },\r
+\r
+       getTileUrl: function (tilePoint) { // (Point, Number) -> String\r
+\r
+               var map = this._map,\r
+                   tileSize = this.options.tileSize,\r
+\r
+                   nwPoint = tilePoint.multiplyBy(tileSize),\r
+                   sePoint = nwPoint.add([tileSize, tileSize]),\r
+\r
+                   nw = this._crs.project(map.unproject(nwPoint, tilePoint.z)),\r
+                   se = this._crs.project(map.unproject(sePoint, tilePoint.z)),\r
+                   bbox = this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ?\r
+                       [se.y, nw.x, nw.y, se.x].join(',') :\r
+                       [nw.x, se.y, se.x, nw.y].join(','),\r
+\r
+                   url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});\r
+\r
+               return url + L.Util.getParamString(this.wmsParams, url, true) + '&BBOX=' + bbox;\r
+       },\r
+\r
+       setParams: function (params, noRedraw) {\r
+\r
+               L.extend(this.wmsParams, params);\r
+\r
+               if (!noRedraw) {\r
+                       this.redraw();\r
+               }\r
+\r
+               return this;\r
+       }\r
+});\r
+\r
+L.tileLayer.wms = function (url, options) {\r
+       return new L.TileLayer.WMS(url, options);\r
+};\r
+
+
+/*\r
+ * L.TileLayer.Canvas is a class that you can use as a base for creating\r
+ * dynamically drawn Canvas-based tile layers.\r
+ */\r
+\r
+L.TileLayer.Canvas = L.TileLayer.extend({\r
+       options: {\r
+               async: false\r
+       },\r
+\r
+       initialize: function (options) {\r
+               L.setOptions(this, options);\r
+       },\r
+\r
+       redraw: function () {\r
+               if (this._map) {\r
+                       this._reset({hard: true});\r
+                       this._update();\r
+               }\r
+\r
+               for (var i in this._tiles) {\r
+                       this._redrawTile(this._tiles[i]);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       _redrawTile: function (tile) {\r
+               this.drawTile(tile, tile._tilePoint, this._map._zoom);\r
+       },\r
+\r
+       _createTile: function () {\r
+               var tile = L.DomUtil.create('canvas', 'leaflet-tile');\r
+               tile.width = tile.height = this.options.tileSize;\r
+               tile.onselectstart = tile.onmousemove = L.Util.falseFn;\r
+               return tile;\r
+       },\r
+\r
+       _loadTile: function (tile, tilePoint) {\r
+               tile._layer = this;\r
+               tile._tilePoint = tilePoint;\r
+\r
+               this._redrawTile(tile);\r
+\r
+               if (!this.options.async) {\r
+                       this.tileDrawn(tile);\r
+               }\r
+       },\r
+\r
+       drawTile: function (/*tile, tilePoint*/) {\r
+               // override with rendering code\r
+       },\r
+\r
+       tileDrawn: function (tile) {\r
+               this._tileOnLoad.call(tile);\r
+       }\r
+});\r
+\r
+\r
+L.tileLayer.canvas = function (options) {\r
+       return new L.TileLayer.Canvas(options);\r
+};\r
+
+
+/*\r
+ * L.ImageOverlay is used to overlay images over the map (to specific geographical bounds).\r
+ */\r
+\r
+L.ImageOverlay = L.Class.extend({\r
+       includes: L.Mixin.Events,\r
+\r
+       options: {\r
+               opacity: 1\r
+       },\r
+\r
+       initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)\r
+               this._url = url;\r
+               this._bounds = L.latLngBounds(bounds);\r
+\r
+               L.setOptions(this, options);\r
+       },\r
+\r
+       onAdd: function (map) {\r
+               this._map = map;\r
+\r
+               if (!this._image) {\r
+                       this._initImage();\r
+               }\r
+\r
+               map._panes.overlayPane.appendChild(this._image);\r
+\r
+               map.on('viewreset', this._reset, this);\r
+\r
+               if (map.options.zoomAnimation && L.Browser.any3d) {\r
+                       map.on('zoomanim', this._animateZoom, this);\r
+               }\r
+\r
+               this._reset();\r
+       },\r
+\r
+       onRemove: function (map) {\r
+               map.getPanes().overlayPane.removeChild(this._image);\r
+\r
+               map.off('viewreset', this._reset, this);\r
+\r
+               if (map.options.zoomAnimation) {\r
+                       map.off('zoomanim', this._animateZoom, this);\r
+               }\r
+       },\r
+\r
+       addTo: function (map) {\r
+               map.addLayer(this);\r
+               return this;\r
+       },\r
+\r
+       setOpacity: function (opacity) {\r
+               this.options.opacity = opacity;\r
+               this._updateOpacity();\r
+               return this;\r
+       },\r
+\r
+       // TODO remove bringToFront/bringToBack duplication from TileLayer/Path\r
+       bringToFront: function () {\r
+               if (this._image) {\r
+                       this._map._panes.overlayPane.appendChild(this._image);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       bringToBack: function () {\r
+               var pane = this._map._panes.overlayPane;\r
+               if (this._image) {\r
+                       pane.insertBefore(this._image, pane.firstChild);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       setUrl: function (url) {\r
+               this._url = url;\r
+               this._image.src = this._url;\r
+       },\r
+\r
+       getAttribution: function () {\r
+               return this.options.attribution;\r
+       },\r
+\r
+       _initImage: function () {\r
+               this._image = L.DomUtil.create('img', 'leaflet-image-layer');\r
+\r
+               if (this._map.options.zoomAnimation && L.Browser.any3d) {\r
+                       L.DomUtil.addClass(this._image, 'leaflet-zoom-animated');\r
+               } else {\r
+                       L.DomUtil.addClass(this._image, 'leaflet-zoom-hide');\r
+               }\r
+\r
+               this._updateOpacity();\r
+\r
+               //TODO createImage util method to remove duplication\r
+               L.extend(this._image, {\r
+                       galleryimg: 'no',\r
+                       onselectstart: L.Util.falseFn,\r
+                       onmousemove: L.Util.falseFn,\r
+                       onload: L.bind(this._onImageLoad, this),\r
+                       src: this._url\r
+               });\r
+       },\r
+\r
+       _animateZoom: function (e) {\r
+               var map = this._map,\r
+                   image = this._image,\r
+                   scale = map.getZoomScale(e.zoom),\r
+                   nw = this._bounds.getNorthWest(),\r
+                   se = this._bounds.getSouthEast(),\r
+\r
+                   topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),\r
+                   size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft),\r
+                   origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale)));\r
+\r
+               image.style[L.DomUtil.TRANSFORM] =\r
+                       L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';\r
+       },\r
+\r
+       _reset: function () {\r
+               var image   = this._image,\r
+                   topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),\r
+                   size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft);\r
+\r
+               L.DomUtil.setPosition(image, topLeft);\r
+\r
+               image.style.width  = size.x + 'px';\r
+               image.style.height = size.y + 'px';\r
+       },\r
+\r
+       _onImageLoad: function () {\r
+               this.fire('load');\r
+       },\r
+\r
+       _updateOpacity: function () {\r
+               L.DomUtil.setOpacity(this._image, this.options.opacity);\r
+       }\r
+});\r
+\r
+L.imageOverlay = function (url, bounds, options) {\r
+       return new L.ImageOverlay(url, bounds, options);\r
+};\r
+
+
+/*\r
+ * L.Icon is an image-based icon class that you can use with L.Marker for custom markers.\r
+ */\r
+\r
+L.Icon = L.Class.extend({\r
+       options: {\r
+               /*\r
+               iconUrl: (String) (required)\r
+               iconRetinaUrl: (String) (optional, used for retina devices if detected)\r
+               iconSize: (Point) (can be set through CSS)\r
+               iconAnchor: (Point) (centered by default, can be set in CSS with negative margins)\r
+               popupAnchor: (Point) (if not specified, popup opens in the anchor point)\r
+               shadowUrl: (String) (no shadow by default)\r
+               shadowRetinaUrl: (String) (optional, used for retina devices if detected)\r
+               shadowSize: (Point)\r
+               shadowAnchor: (Point)\r
+               */\r
+               className: ''\r
+       },\r
+\r
+       initialize: function (options) {\r
+               L.setOptions(this, options);\r
+       },\r
+\r
+       createIcon: function (oldIcon) {\r
+               return this._createIcon('icon', oldIcon);\r
+       },\r
+\r
+       createShadow: function (oldIcon) {\r
+               return this._createIcon('shadow', oldIcon);\r
+       },\r
+\r
+       _createIcon: function (name, oldIcon) {\r
+               var src = this._getIconUrl(name);\r
+\r
+               if (!src) {\r
+                       if (name === 'icon') {\r
+                               throw new Error('iconUrl not set in Icon options (see the docs).');\r
+                       }\r
+                       return null;\r
+               }\r
+\r
+               var img;\r
+               if (!oldIcon || oldIcon.tagName !== 'IMG') {\r
+                       img = this._createImg(src);\r
+               } else {\r
+                       img = this._createImg(src, oldIcon);\r
+               }\r
+               this._setIconStyles(img, name);\r
+\r
+               return img;\r
+       },\r
+\r
+       _setIconStyles: function (img, name) {\r
+               var options = this.options,\r
+                   size = L.point(options[name + 'Size']),\r
+                   anchor;\r
+\r
+               if (name === 'shadow') {\r
+                       anchor = L.point(options.shadowAnchor || options.iconAnchor);\r
+               } else {\r
+                       anchor = L.point(options.iconAnchor);\r
+               }\r
+\r
+               if (!anchor && size) {\r
+                       anchor = size.divideBy(2, true);\r
+               }\r
+\r
+               img.className = 'leaflet-marker-' + name + ' ' + options.className;\r
+\r
+               if (anchor) {\r
+                       img.style.marginLeft = (-anchor.x) + 'px';\r
+                       img.style.marginTop  = (-anchor.y) + 'px';\r
+               }\r
+\r
+               if (size) {\r
+                       img.style.width  = size.x + 'px';\r
+                       img.style.height = size.y + 'px';\r
+               }\r
+       },\r
+\r
+       _createImg: function (src, el) {\r
+               el = el || document.createElement('img');\r
+               el.src = src;\r
+               return el;\r
+       },\r
+\r
+       _getIconUrl: function (name) {\r
+               if (L.Browser.retina && this.options[name + 'RetinaUrl']) {\r
+                       return this.options[name + 'RetinaUrl'];\r
+               }\r
+               return this.options[name + 'Url'];\r
+       }\r
+});\r
+\r
+L.icon = function (options) {\r
+       return new L.Icon(options);\r
+};\r
+
+
+/*
+ * L.Icon.Default is the blue marker icon used by default in Leaflet.
+ */
+
+L.Icon.Default = L.Icon.extend({
+
+       options: {
+               iconSize: [25, 41],
+               iconAnchor: [12, 41],
+               popupAnchor: [1, -34],
+
+               shadowSize: [41, 41]
+       },
+
+       _getIconUrl: function (name) {
+               var key = name + 'Url';
+
+               if (this.options[key]) {
+                       return this.options[key];
+               }
+
+               if (L.Browser.retina && name === 'icon') {
+                       name += '-2x';
+               }
+
+               var path = L.Icon.Default.imagePath;
+
+               if (!path) {
+                       throw new Error('Couldn\'t autodetect L.Icon.Default.imagePath, set it manually.');
+               }
+
+               return path + '/marker-' + name + '.png';
+       }
+});
+
+L.Icon.Default.imagePath = (function () {
+       var scripts = document.getElementsByTagName('script'),
+           leafletRe = /[\/^]leaflet[\-\._]?([\w\-\._]*)\.js\??/;
+
+       var i, len, src, matches, path;
+
+       for (i = 0, len = scripts.length; i < len; i++) {
+               src = scripts[i].src;
+               matches = src.match(leafletRe);
+
+               if (matches) {
+                       path = src.split(leafletRe)[0];
+                       return (path ? path + '/' : '') + 'images';
+               }
+       }
+}());
+
+
+/*\r
+ * L.Marker is used to display clickable/draggable icons on the map.\r
+ */\r
+\r
+L.Marker = L.Class.extend({\r
+\r
+       includes: L.Mixin.Events,\r
+\r
+       options: {\r
+               icon: new L.Icon.Default(),\r
+               title: '',\r
+               alt: '',\r
+               clickable: true,\r
+               draggable: false,\r
+               keyboard: true,\r
+               zIndexOffset: 0,\r
+               opacity: 1,\r
+               riseOnHover: false,\r
+               riseOffset: 250\r
+       },\r
+\r
+       initialize: function (latlng, options) {\r
+               L.setOptions(this, options);\r
+               this._latlng = L.latLng(latlng);\r
+       },\r
+\r
+       onAdd: function (map) {\r
+               this._map = map;\r
+\r
+               map.on('viewreset', this.update, this);\r
+\r
+               this._initIcon();\r
+               this.update();\r
+               this.fire('add');\r
+\r
+               if (map.options.zoomAnimation && map.options.markerZoomAnimation) {\r
+                       map.on('zoomanim', this._animateZoom, this);\r
+               }\r
+       },\r
+\r
+       addTo: function (map) {\r
+               map.addLayer(this);\r
+               return this;\r
+       },\r
+\r
+       onRemove: function (map) {\r
+               if (this.dragging) {\r
+                       this.dragging.disable();\r
+               }\r
+\r
+               this._removeIcon();\r
+               this._removeShadow();\r
+\r
+               this.fire('remove');\r
+\r
+               map.off({\r
+                       'viewreset': this.update,\r
+                       'zoomanim': this._animateZoom\r
+               }, this);\r
+\r
+               this._map = null;\r
+       },\r
+\r
+       getLatLng: function () {\r
+               return this._latlng;\r
+       },\r
+\r
+       setLatLng: function (latlng) {\r
+               this._latlng = L.latLng(latlng);\r
+\r
+               this.update();\r
+\r
+               return this.fire('move', { latlng: this._latlng });\r
+       },\r
+\r
+       setZIndexOffset: function (offset) {\r
+               this.options.zIndexOffset = offset;\r
+               this.update();\r
+\r
+               return this;\r
+       },\r
+\r
+       setIcon: function (icon) {\r
+\r
+               this.options.icon = icon;\r
+\r
+               if (this._map) {\r
+                       this._initIcon();\r
+                       this.update();\r
+               }\r
+\r
+               if (this._popup) {\r
+                       this.bindPopup(this._popup);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       update: function () {\r
+               if (this._icon) {\r
+                       var pos = this._map.latLngToLayerPoint(this._latlng).round();\r
+                       this._setPos(pos);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       _initIcon: function () {\r
+               var options = this.options,\r
+                   map = this._map,\r
+                   animation = (map.options.zoomAnimation && map.options.markerZoomAnimation),\r
+                   classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide';\r
+\r
+               var icon = options.icon.createIcon(this._icon),\r
+                       addIcon = false;\r
+\r
+               // if we're not reusing the icon, remove the old one and init new one\r
+               if (icon !== this._icon) {\r
+                       if (this._icon) {\r
+                               this._removeIcon();\r
+                       }\r
+                       addIcon = true;\r
+\r
+                       if (options.title) {\r
+                               icon.title = options.title;\r
+                       }\r
+                       \r
+                       if (options.alt) {\r
+                               icon.alt = options.alt;\r
+                       }\r
+               }\r
+\r
+               L.DomUtil.addClass(icon, classToAdd);\r
+\r
+               if (options.keyboard) {\r
+                       icon.tabIndex = '0';\r
+               }\r
+\r
+               this._icon = icon;\r
+\r
+               this._initInteraction();\r
+\r
+               if (options.riseOnHover) {\r
+                       L.DomEvent\r
+                               .on(icon, 'mouseover', this._bringToFront, this)\r
+                               .on(icon, 'mouseout', this._resetZIndex, this);\r
+               }\r
+\r
+               var newShadow = options.icon.createShadow(this._shadow),\r
+                       addShadow = false;\r
+\r
+               if (newShadow !== this._shadow) {\r
+                       this._removeShadow();\r
+                       addShadow = true;\r
+               }\r
+\r
+               if (newShadow) {\r
+                       L.DomUtil.addClass(newShadow, classToAdd);\r
+               }\r
+               this._shadow = newShadow;\r
+\r
+\r
+               if (options.opacity < 1) {\r
+                       this._updateOpacity();\r
+               }\r
+\r
+\r
+               var panes = this._map._panes;\r
+\r
+               if (addIcon) {\r
+                       panes.markerPane.appendChild(this._icon);\r
+               }\r
+\r
+               if (newShadow && addShadow) {\r
+                       panes.shadowPane.appendChild(this._shadow);\r
+               }\r
+       },\r
+\r
+       _removeIcon: function () {\r
+               if (this.options.riseOnHover) {\r
+                       L.DomEvent\r
+                           .off(this._icon, 'mouseover', this._bringToFront)\r
+                           .off(this._icon, 'mouseout', this._resetZIndex);\r
+               }\r
+\r
+               this._map._panes.markerPane.removeChild(this._icon);\r
+\r
+               this._icon = null;\r
+       },\r
+\r
+       _removeShadow: function () {\r
+               if (this._shadow) {\r
+                       this._map._panes.shadowPane.removeChild(this._shadow);\r
+               }\r
+               this._shadow = null;\r
+       },\r
+\r
+       _setPos: function (pos) {\r
+               L.DomUtil.setPosition(this._icon, pos);\r
+\r
+               if (this._shadow) {\r
+                       L.DomUtil.setPosition(this._shadow, pos);\r
+               }\r
+\r
+               this._zIndex = pos.y + this.options.zIndexOffset;\r
+\r
+               this._resetZIndex();\r
+       },\r
+\r
+       _updateZIndex: function (offset) {\r
+               this._icon.style.zIndex = this._zIndex + offset;\r
+       },\r
+\r
+       _animateZoom: function (opt) {\r
+               var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();\r
+\r
+               this._setPos(pos);\r
+       },\r
+\r
+       _initInteraction: function () {\r
+\r
+               if (!this.options.clickable) { return; }\r
+\r
+               // TODO refactor into something shared with Map/Path/etc. to DRY it up\r
+\r
+               var icon = this._icon,\r
+                   events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu'];\r
+\r
+               L.DomUtil.addClass(icon, 'leaflet-clickable');\r
+               L.DomEvent.on(icon, 'click', this._onMouseClick, this);\r
+               L.DomEvent.on(icon, 'keypress', this._onKeyPress, this);\r
+\r
+               for (var i = 0; i < events.length; i++) {\r
+                       L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);\r
+               }\r
+\r
+               if (L.Handler.MarkerDrag) {\r
+                       this.dragging = new L.Handler.MarkerDrag(this);\r
+\r
+                       if (this.options.draggable) {\r
+                               this.dragging.enable();\r
+                       }\r
+               }\r
+       },\r
+\r
+       _onMouseClick: function (e) {\r
+               var wasDragged = this.dragging && this.dragging.moved();\r
+\r
+               if (this.hasEventListeners(e.type) || wasDragged) {\r
+                       L.DomEvent.stopPropagation(e);\r
+               }\r
+\r
+               if (wasDragged) { return; }\r
+\r
+               if ((!this.dragging || !this.dragging._enabled) && this._map.dragging && this._map.dragging.moved()) { return; }\r
+\r
+               this.fire(e.type, {\r
+                       originalEvent: e,\r
+                       latlng: this._latlng\r
+               });\r
+       },\r
+\r
+       _onKeyPress: function (e) {\r
+               if (e.keyCode === 13) {\r
+                       this.fire('click', {\r
+                               originalEvent: e,\r
+                               latlng: this._latlng\r
+                       });\r
+               }\r
+       },\r
+\r
+       _fireMouseEvent: function (e) {\r
+\r
+               this.fire(e.type, {\r
+                       originalEvent: e,\r
+                       latlng: this._latlng\r
+               });\r
+\r
+               // TODO proper custom event propagation\r
+               // this line will always be called if marker is in a FeatureGroup\r
+               if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) {\r
+                       L.DomEvent.preventDefault(e);\r
+               }\r
+               if (e.type !== 'mousedown') {\r
+                       L.DomEvent.stopPropagation(e);\r
+               } else {\r
+                       L.DomEvent.preventDefault(e);\r
+               }\r
+       },\r
+\r
+       setOpacity: function (opacity) {\r
+               this.options.opacity = opacity;\r
+               if (this._map) {\r
+                       this._updateOpacity();\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       _updateOpacity: function () {\r
+               L.DomUtil.setOpacity(this._icon, this.options.opacity);\r
+               if (this._shadow) {\r
+                       L.DomUtil.setOpacity(this._shadow, this.options.opacity);\r
+               }\r
+       },\r
+\r
+       _bringToFront: function () {\r
+               this._updateZIndex(this.options.riseOffset);\r
+       },\r
+\r
+       _resetZIndex: function () {\r
+               this._updateZIndex(0);\r
+       }\r
+});\r
+\r
+L.marker = function (latlng, options) {\r
+       return new L.Marker(latlng, options);\r
+};\r
+
+
+/*
+ * L.DivIcon is a lightweight HTML-based icon class (as opposed to the image-based L.Icon)
+ * to use with L.Marker.
+ */
+
+L.DivIcon = L.Icon.extend({
+       options: {
+               iconSize: [12, 12], // also can be set through CSS
+               /*
+               iconAnchor: (Point)
+               popupAnchor: (Point)
+               html: (String)
+               bgPos: (Point)
+               */
+               className: 'leaflet-div-icon',
+               html: false
+       },
+
+       createIcon: function (oldIcon) {
+               var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
+                   options = this.options;
+
+               if (options.html !== false) {
+                       div.innerHTML = options.html;
+               } else {
+                       div.innerHTML = '';
+               }
+
+               if (options.bgPos) {
+                       div.style.backgroundPosition =
+                               (-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px';
+               }
+
+               this._setIconStyles(div, 'icon');
+               return div;
+       },
+
+       createShadow: function () {
+               return null;
+       }
+});
+
+L.divIcon = function (options) {
+       return new L.DivIcon(options);
+};
+
+
+/*\r
+ * L.Popup is used for displaying popups on the map.\r
+ */\r
+\r
+L.Map.mergeOptions({\r
+       closePopupOnClick: true\r
+});\r
+\r
+L.Popup = L.Class.extend({\r
+       includes: L.Mixin.Events,\r
+\r
+       options: {\r
+               minWidth: 50,\r
+               maxWidth: 300,\r
+               // maxHeight: null,\r
+               autoPan: true,\r
+               closeButton: true,\r
+               offset: [0, 7],\r
+               autoPanPadding: [5, 5],\r
+               // autoPanPaddingTopLeft: null,\r
+               // autoPanPaddingBottomRight: null,\r
+               keepInView: false,\r
+               className: '',\r
+               zoomAnimation: true\r
+       },\r
+\r
+       initialize: function (options, source) {\r
+               L.setOptions(this, options);\r
+\r
+               this._source = source;\r
+               this._animated = L.Browser.any3d && this.options.zoomAnimation;\r
+               this._isOpen = false;\r
+       },\r
+\r
+       onAdd: function (map) {\r
+               this._map = map;\r
+\r
+               if (!this._container) {\r
+                       this._initLayout();\r
+               }\r
+\r
+               var animFade = map.options.fadeAnimation;\r
+\r
+               if (animFade) {\r
+                       L.DomUtil.setOpacity(this._container, 0);\r
+               }\r
+               map._panes.popupPane.appendChild(this._container);\r
+\r
+               map.on(this._getEvents(), this);\r
+\r
+               this.update();\r
+\r
+               if (animFade) {\r
+                       L.DomUtil.setOpacity(this._container, 1);\r
+               }\r
+\r
+               this.fire('open');\r
+\r
+               map.fire('popupopen', {popup: this});\r
+\r
+               if (this._source) {\r
+                       this._source.fire('popupopen', {popup: this});\r
+               }\r
+       },\r
+\r
+       addTo: function (map) {\r
+               map.addLayer(this);\r
+               return this;\r
+       },\r
+\r
+       openOn: function (map) {\r
+               map.openPopup(this);\r
+               return this;\r
+       },\r
+\r
+       onRemove: function (map) {\r
+               map._panes.popupPane.removeChild(this._container);\r
+\r
+               L.Util.falseFn(this._container.offsetWidth); // force reflow\r
+\r
+               map.off(this._getEvents(), this);\r
+\r
+               if (map.options.fadeAnimation) {\r
+                       L.DomUtil.setOpacity(this._container, 0);\r
+               }\r
+\r
+               this._map = null;\r
+\r
+               this.fire('close');\r
+\r
+               map.fire('popupclose', {popup: this});\r
+\r
+               if (this._source) {\r
+                       this._source.fire('popupclose', {popup: this});\r
+               }\r
+       },\r
+\r
+       getLatLng: function () {\r
+               return this._latlng;\r
+       },\r
+\r
+       setLatLng: function (latlng) {\r
+               this._latlng = L.latLng(latlng);\r
+               if (this._map) {\r
+                       this._updatePosition();\r
+                       this._adjustPan();\r
+               }\r
+               return this;\r
+       },\r
+\r
+       getContent: function () {\r
+               return this._content;\r
+       },\r
+\r
+       setContent: function (content) {\r
+               this._content = content;\r
+               this.update();\r
+               return this;\r
+       },\r
+\r
+       update: function () {\r
+               if (!this._map) { return; }\r
+\r
+               this._container.style.visibility = 'hidden';\r
+\r
+               this._updateContent();\r
+               this._updateLayout();\r
+               this._updatePosition();\r
+\r
+               this._container.style.visibility = '';\r
+\r
+               this._adjustPan();\r
+       },\r
+\r
+       _getEvents: function () {\r
+               var events = {\r
+                       viewreset: this._updatePosition\r
+               };\r
+\r
+               if (this._animated) {\r
+                       events.zoomanim = this._zoomAnimation;\r
+               }\r
+               if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {\r
+                       events.preclick = this._close;\r
+               }\r
+               if (this.options.keepInView) {\r
+                       events.moveend = this._adjustPan;\r
+               }\r
+\r
+               return events;\r
+       },\r
+\r
+       _close: function () {\r
+               if (this._map) {\r
+                       this._map.closePopup(this);\r
+               }\r
+       },\r
+\r
+       _initLayout: function () {\r
+               var prefix = 'leaflet-popup',\r
+                       containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-' +\r
+                               (this._animated ? 'animated' : 'hide'),\r
+                       container = this._container = L.DomUtil.create('div', containerClass),\r
+                       closeButton;\r
+\r
+               if (this.options.closeButton) {\r
+                       closeButton = this._closeButton =\r
+                               L.DomUtil.create('a', prefix + '-close-button', container);\r
+                       closeButton.href = '#close';\r
+                       closeButton.innerHTML = '&#215;';\r
+                       L.DomEvent.disableClickPropagation(closeButton);\r
+\r
+                       L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);\r
+               }\r
+\r
+               var wrapper = this._wrapper =\r
+                       L.DomUtil.create('div', prefix + '-content-wrapper', container);\r
+               L.DomEvent.disableClickPropagation(wrapper);\r
+\r
+               this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);\r
+\r
+               L.DomEvent.disableScrollPropagation(this._contentNode);\r
+               L.DomEvent.on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);\r
+\r
+               this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);\r
+               this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);\r
+       },\r
+\r
+       _updateContent: function () {\r
+               if (!this._content) { return; }\r
+\r
+               if (typeof this._content === 'string') {\r
+                       this._contentNode.innerHTML = this._content;\r
+               } else {\r
+                       while (this._contentNode.hasChildNodes()) {\r
+                               this._contentNode.removeChild(this._contentNode.firstChild);\r
+                       }\r
+                       this._contentNode.appendChild(this._content);\r
+               }\r
+               this.fire('contentupdate');\r
+       },\r
+\r
+       _updateLayout: function () {\r
+               var container = this._contentNode,\r
+                   style = container.style;\r
+\r
+               style.width = '';\r
+               style.whiteSpace = 'nowrap';\r
+\r
+               var width = container.offsetWidth;\r
+               width = Math.min(width, this.options.maxWidth);\r
+               width = Math.max(width, this.options.minWidth);\r
+\r
+               style.width = (width + 1) + 'px';\r
+               style.whiteSpace = '';\r
+\r
+               style.height = '';\r
+\r
+               var height = container.offsetHeight,\r
+                   maxHeight = this.options.maxHeight,\r
+                   scrolledClass = 'leaflet-popup-scrolled';\r
+\r
+               if (maxHeight && height > maxHeight) {\r
+                       style.height = maxHeight + 'px';\r
+                       L.DomUtil.addClass(container, scrolledClass);\r
+               } else {\r
+                       L.DomUtil.removeClass(container, scrolledClass);\r
+               }\r
+\r
+               this._containerWidth = this._container.offsetWidth;\r
+       },\r
+\r
+       _updatePosition: function () {\r
+               if (!this._map) { return; }\r
+\r
+               var pos = this._map.latLngToLayerPoint(this._latlng),\r
+                   animated = this._animated,\r
+                   offset = L.point(this.options.offset);\r
+\r
+               if (animated) {\r
+                       L.DomUtil.setPosition(this._container, pos);\r
+               }\r
+\r
+               this._containerBottom = -offset.y - (animated ? 0 : pos.y);\r
+               this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (animated ? 0 : pos.x);\r
+\r
+               // bottom position the popup in case the height of the popup changes (images loading etc)\r
+               this._container.style.bottom = this._containerBottom + 'px';\r
+               this._container.style.left = this._containerLeft + 'px';\r
+       },\r
+\r
+       _zoomAnimation: function (opt) {\r
+               var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);\r
+\r
+               L.DomUtil.setPosition(this._container, pos);\r
+       },\r
+\r
+       _adjustPan: function () {\r
+               if (!this.options.autoPan) { return; }\r
+\r
+               var map = this._map,\r
+                   containerHeight = this._container.offsetHeight,\r
+                   containerWidth = this._containerWidth,\r
+\r
+                   layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);\r
+\r
+               if (this._animated) {\r
+                       layerPos._add(L.DomUtil.getPosition(this._container));\r
+               }\r
+\r
+               var containerPos = map.layerPointToContainerPoint(layerPos),\r
+                   padding = L.point(this.options.autoPanPadding),\r
+                   paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding),\r
+                   paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding),\r
+                   size = map.getSize(),\r
+                   dx = 0,\r
+                   dy = 0;\r
+\r
+               if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right\r
+                       dx = containerPos.x + containerWidth - size.x + paddingBR.x;\r
+               }\r
+               if (containerPos.x - dx - paddingTL.x < 0) { // left\r
+                       dx = containerPos.x - paddingTL.x;\r
+               }\r
+               if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom\r
+                       dy = containerPos.y + containerHeight - size.y + paddingBR.y;\r
+               }\r
+               if (containerPos.y - dy - paddingTL.y < 0) { // top\r
+                       dy = containerPos.y - paddingTL.y;\r
+               }\r
+\r
+               if (dx || dy) {\r
+                       map\r
+                           .fire('autopanstart')\r
+                           .panBy([dx, dy]);\r
+               }\r
+       },\r
+\r
+       _onCloseButtonClick: function (e) {\r
+               this._close();\r
+               L.DomEvent.stop(e);\r
+       }\r
+});\r
+\r
+L.popup = function (options, source) {\r
+       return new L.Popup(options, source);\r
+};\r
+\r
+\r
+L.Map.include({\r
+       openPopup: function (popup, latlng, options) { // (Popup) or (String || HTMLElement, LatLng[, Object])\r
+               this.closePopup();\r
+\r
+               if (!(popup instanceof L.Popup)) {\r
+                       var content = popup;\r
+\r
+                       popup = new L.Popup(options)\r
+                           .setLatLng(latlng)\r
+                           .setContent(content);\r
+               }\r
+               popup._isOpen = true;\r
+\r
+               this._popup = popup;\r
+               return this.addLayer(popup);\r
+       },\r
+\r
+       closePopup: function (popup) {\r
+               if (!popup || popup === this._popup) {\r
+                       popup = this._popup;\r
+                       this._popup = null;\r
+               }\r
+               if (popup) {\r
+                       this.removeLayer(popup);\r
+                       popup._isOpen = false;\r
+               }\r
+               return this;\r
+       }\r
+});\r
+
+
+/*\r
+ * Popup extension to L.Marker, adding popup-related methods.\r
+ */\r
+\r
+L.Marker.include({\r
+       openPopup: function () {\r
+               if (this._popup && this._map && !this._map.hasLayer(this._popup)) {\r
+                       this._popup.setLatLng(this._latlng);\r
+                       this._map.openPopup(this._popup);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       closePopup: function () {\r
+               if (this._popup) {\r
+                       this._popup._close();\r
+               }\r
+               return this;\r
+       },\r
+\r
+       togglePopup: function () {\r
+               if (this._popup) {\r
+                       if (this._popup._isOpen) {\r
+                               this.closePopup();\r
+                       } else {\r
+                               this.openPopup();\r
+                       }\r
+               }\r
+               return this;\r
+       },\r
+\r
+       bindPopup: function (content, options) {\r
+               var anchor = L.point(this.options.icon.options.popupAnchor || [0, 0]);\r
+\r
+               anchor = anchor.add(L.Popup.prototype.options.offset);\r
+\r
+               if (options && options.offset) {\r
+                       anchor = anchor.add(options.offset);\r
+               }\r
+\r
+               options = L.extend({offset: anchor}, options);\r
+\r
+               if (!this._popupHandlersAdded) {\r
+                       this\r
+                           .on('click', this.togglePopup, this)\r
+                           .on('remove', this.closePopup, this)\r
+                           .on('move', this._movePopup, this);\r
+                       this._popupHandlersAdded = true;\r
+               }\r
+\r
+               if (content instanceof L.Popup) {\r
+                       L.setOptions(content, options);\r
+                       this._popup = content;\r
+               } else {\r
+                       this._popup = new L.Popup(options, this)\r
+                               .setContent(content);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       setPopupContent: function (content) {\r
+               if (this._popup) {\r
+                       this._popup.setContent(content);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       unbindPopup: function () {\r
+               if (this._popup) {\r
+                       this._popup = null;\r
+                       this\r
+                           .off('click', this.togglePopup, this)\r
+                           .off('remove', this.closePopup, this)\r
+                           .off('move', this._movePopup, this);\r
+                       this._popupHandlersAdded = false;\r
+               }\r
+               return this;\r
+       },\r
+\r
+       getPopup: function () {\r
+               return this._popup;\r
+       },\r
+\r
+       _movePopup: function (e) {\r
+               this._popup.setLatLng(e.latlng);\r
+       }\r
+});\r
+
+
+/*\r
+ * L.LayerGroup is a class to combine several layers into one so that\r
+ * you can manipulate the group (e.g. add/remove it) as one layer.\r
+ */\r
+\r
+L.LayerGroup = L.Class.extend({\r
+       initialize: function (layers) {\r
+               this._layers = {};\r
+\r
+               var i, len;\r
+\r
+               if (layers) {\r
+                       for (i = 0, len = layers.length; i < len; i++) {\r
+                               this.addLayer(layers[i]);\r
+                       }\r
+               }\r
+       },\r
+\r
+       addLayer: function (layer) {\r
+               var id = this.getLayerId(layer);\r
+\r
+               this._layers[id] = layer;\r
+\r
+               if (this._map) {\r
+                       this._map.addLayer(layer);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       removeLayer: function (layer) {\r
+               var id = layer in this._layers ? layer : this.getLayerId(layer);\r
+\r
+               if (this._map && this._layers[id]) {\r
+                       this._map.removeLayer(this._layers[id]);\r
+               }\r
+\r
+               delete this._layers[id];\r
+\r
+               return this;\r
+       },\r
+\r
+       hasLayer: function (layer) {\r
+               if (!layer) { return false; }\r
+\r
+               return (layer in this._layers || this.getLayerId(layer) in this._layers);\r
+       },\r
+\r
+       clearLayers: function () {\r
+               this.eachLayer(this.removeLayer, this);\r
+               return this;\r
+       },\r
+\r
+       invoke: function (methodName) {\r
+               var args = Array.prototype.slice.call(arguments, 1),\r
+                   i, layer;\r
+\r
+               for (i in this._layers) {\r
+                       layer = this._layers[i];\r
+\r
+                       if (layer[methodName]) {\r
+                               layer[methodName].apply(layer, args);\r
+                       }\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       onAdd: function (map) {\r
+               this._map = map;\r
+               this.eachLayer(map.addLayer, map);\r
+       },\r
+\r
+       onRemove: function (map) {\r
+               this.eachLayer(map.removeLayer, map);\r
+               this._map = null;\r
+       },\r
+\r
+       addTo: function (map) {\r
+               map.addLayer(this);\r
+               return this;\r
+       },\r
+\r
+       eachLayer: function (method, context) {\r
+               for (var i in this._layers) {\r
+                       method.call(context, this._layers[i]);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       getLayer: function (id) {\r
+               return this._layers[id];\r
+       },\r
+\r
+       getLayers: function () {\r
+               var layers = [];\r
+\r
+               for (var i in this._layers) {\r
+                       layers.push(this._layers[i]);\r
+               }\r
+               return layers;\r
+       },\r
+\r
+       setZIndex: function (zIndex) {\r
+               return this.invoke('setZIndex', zIndex);\r
+       },\r
+\r
+       getLayerId: function (layer) {\r
+               return L.stamp(layer);\r
+       }\r
+});\r
+\r
+L.layerGroup = function (layers) {\r
+       return new L.LayerGroup(layers);\r
+};\r
+
+
+/*\r
+ * L.FeatureGroup extends L.LayerGroup by introducing mouse events and additional methods\r
+ * shared between a group of interactive layers (like vectors or markers).\r
+ */\r
+\r
+L.FeatureGroup = L.LayerGroup.extend({\r
+       includes: L.Mixin.Events,\r
+\r
+       statics: {\r
+               EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose'\r
+       },\r
+\r
+       addLayer: function (layer) {\r
+               if (this.hasLayer(layer)) {\r
+                       return this;\r
+               }\r
+\r
+               if ('on' in layer) {\r
+                       layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);\r
+               }\r
+\r
+               L.LayerGroup.prototype.addLayer.call(this, layer);\r
+\r
+               if (this._popupContent && layer.bindPopup) {\r
+                       layer.bindPopup(this._popupContent, this._popupOptions);\r
+               }\r
+\r
+               return this.fire('layeradd', {layer: layer});\r
+       },\r
+\r
+       removeLayer: function (layer) {\r
+               if (!this.hasLayer(layer)) {\r
+                       return this;\r
+               }\r
+               if (layer in this._layers) {\r
+                       layer = this._layers[layer];\r
+               }\r
+\r
+               layer.off(L.FeatureGroup.EVENTS, this._propagateEvent, this);\r
+\r
+               L.LayerGroup.prototype.removeLayer.call(this, layer);\r
+\r
+               if (this._popupContent) {\r
+                       this.invoke('unbindPopup');\r
+               }\r
+\r
+               return this.fire('layerremove', {layer: layer});\r
+       },\r
+\r
+       bindPopup: function (content, options) {\r
+               this._popupContent = content;\r
+               this._popupOptions = options;\r
+               return this.invoke('bindPopup', content, options);\r
+       },\r
+\r
+       openPopup: function (latlng) {\r
+               // open popup on the first layer\r
+               for (var id in this._layers) {\r
+                       this._layers[id].openPopup(latlng);\r
+                       break;\r
+               }\r
+               return this;\r
+       },\r
+\r
+       setStyle: function (style) {\r
+               return this.invoke('setStyle', style);\r
+       },\r
+\r
+       bringToFront: function () {\r
+               return this.invoke('bringToFront');\r
+       },\r
+\r
+       bringToBack: function () {\r
+               return this.invoke('bringToBack');\r
+       },\r
+\r
+       getBounds: function () {\r
+               var bounds = new L.LatLngBounds();\r
+\r
+               this.eachLayer(function (layer) {\r
+                       bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());\r
+               });\r
+\r
+               return bounds;\r
+       },\r
+\r
+       _propagateEvent: function (e) {\r
+               e = L.extend({\r
+                       layer: e.target,\r
+                       target: this\r
+               }, e);\r
+               this.fire(e.type, e);\r
+       }\r
+});\r
+\r
+L.featureGroup = function (layers) {\r
+       return new L.FeatureGroup(layers);\r
+};\r
+
+
+/*\r
+ * L.Path is a base class for rendering vector paths on a map. Inherited by Polyline, Circle, etc.\r
+ */\r
+\r
+L.Path = L.Class.extend({\r
+       includes: [L.Mixin.Events],\r
+\r
+       statics: {\r
+               // how much to extend the clip area around the map view\r
+               // (relative to its size, e.g. 0.5 is half the screen in each direction)\r
+               // set it so that SVG element doesn't exceed 1280px (vectors flicker on dragend if it is)\r
+               CLIP_PADDING: (function () {\r
+                       var max = L.Browser.mobile ? 1280 : 2000,\r
+                           target = (max / Math.max(window.outerWidth, window.outerHeight) - 1) / 2;\r
+                       return Math.max(0, Math.min(0.5, target));\r
+               })()\r
+       },\r
+\r
+       options: {\r
+               stroke: true,\r
+               color: '#0033ff',\r
+               dashArray: null,\r
+               lineCap: null,\r
+               lineJoin: null,\r
+               weight: 5,\r
+               opacity: 0.5,\r
+\r
+               fill: false,\r
+               fillColor: null, //same as color by default\r
+               fillOpacity: 0.2,\r
+\r
+               clickable: true\r
+       },\r
+\r
+       initialize: function (options) {\r
+               L.setOptions(this, options);\r
+       },\r
+\r
+       onAdd: function (map) {\r
+               this._map = map;\r
+\r
+               if (!this._container) {\r
+                       this._initElements();\r
+                       this._initEvents();\r
+               }\r
+\r
+               this.projectLatlngs();\r
+               this._updatePath();\r
+\r
+               if (this._container) {\r
+                       this._map._pathRoot.appendChild(this._container);\r
+               }\r
+\r
+               this.fire('add');\r
+\r
+               map.on({\r
+                       'viewreset': this.projectLatlngs,\r
+                       'moveend': this._updatePath\r
+               }, this);\r
+       },\r
+\r
+       addTo: function (map) {\r
+               map.addLayer(this);\r
+               return this;\r
+       },\r
+\r
+       onRemove: function (map) {\r
+               map._pathRoot.removeChild(this._container);\r
+\r
+               // Need to fire remove event before we set _map to null as the event hooks might need the object\r
+               this.fire('remove');\r
+               this._map = null;\r
+\r
+               if (L.Browser.vml) {\r
+                       this._container = null;\r
+                       this._stroke = null;\r
+                       this._fill = null;\r
+               }\r
+\r
+               map.off({\r
+                       'viewreset': this.projectLatlngs,\r
+                       'moveend': this._updatePath\r
+               }, this);\r
+       },\r
+\r
+       projectLatlngs: function () {\r
+               // do all projection stuff here\r
+       },\r
+\r
+       setStyle: function (style) {\r
+               L.setOptions(this, style);\r
+\r
+               if (this._container) {\r
+                       this._updateStyle();\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       redraw: function () {\r
+               if (this._map) {\r
+                       this.projectLatlngs();\r
+                       this._updatePath();\r
+               }\r
+               return this;\r
+       }\r
+});\r
+\r
+L.Map.include({\r
+       _updatePathViewport: function () {\r
+               var p = L.Path.CLIP_PADDING,\r
+                   size = this.getSize(),\r
+                   panePos = L.DomUtil.getPosition(this._mapPane),\r
+                   min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)._round()),\r
+                   max = min.add(size.multiplyBy(1 + p * 2)._round());\r
+\r
+               this._pathViewport = new L.Bounds(min, max);\r
+       }\r
+});\r
+
+
+/*\r
+ * Extends L.Path with SVG-specific rendering code.\r
+ */\r
+\r
+L.Path.SVG_NS = 'http://www.w3.org/2000/svg';\r
+\r
+L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect);\r
+\r
+L.Path = L.Path.extend({\r
+       statics: {\r
+               SVG: L.Browser.svg\r
+       },\r
+\r
+       bringToFront: function () {\r
+               var root = this._map._pathRoot,\r
+                   path = this._container;\r
+\r
+               if (path && root.lastChild !== path) {\r
+                       root.appendChild(path);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       bringToBack: function () {\r
+               var root = this._map._pathRoot,\r
+                   path = this._container,\r
+                   first = root.firstChild;\r
+\r
+               if (path && first !== path) {\r
+                       root.insertBefore(path, first);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       getPathString: function () {\r
+               // form path string here\r
+       },\r
+\r
+       _createElement: function (name) {\r
+               return document.createElementNS(L.Path.SVG_NS, name);\r
+       },\r
+\r
+       _initElements: function () {\r
+               this._map._initPathRoot();\r
+               this._initPath();\r
+               this._initStyle();\r
+       },\r
+\r
+       _initPath: function () {\r
+               this._container = this._createElement('g');\r
+\r
+               this._path = this._createElement('path');\r
+\r
+               if (this.options.className) {\r
+                       L.DomUtil.addClass(this._path, this.options.className);\r
+               }\r
+\r
+               this._container.appendChild(this._path);\r
+       },\r
+\r
+       _initStyle: function () {\r
+               if (this.options.stroke) {\r
+                       this._path.setAttribute('stroke-linejoin', 'round');\r
+                       this._path.setAttribute('stroke-linecap', 'round');\r
+               }\r
+               if (this.options.fill) {\r
+                       this._path.setAttribute('fill-rule', 'evenodd');\r
+               }\r
+               if (this.options.pointerEvents) {\r
+                       this._path.setAttribute('pointer-events', this.options.pointerEvents);\r
+               }\r
+               if (!this.options.clickable && !this.options.pointerEvents) {\r
+                       this._path.setAttribute('pointer-events', 'none');\r
+               }\r
+               this._updateStyle();\r
+       },\r
+\r
+       _updateStyle: function () {\r
+               if (this.options.stroke) {\r
+                       this._path.setAttribute('stroke', this.options.color);\r
+                       this._path.setAttribute('stroke-opacity', this.options.opacity);\r
+                       this._path.setAttribute('stroke-width', this.options.weight);\r
+                       if (this.options.dashArray) {\r
+                               this._path.setAttribute('stroke-dasharray', this.options.dashArray);\r
+                       } else {\r
+                               this._path.removeAttribute('stroke-dasharray');\r
+                       }\r
+                       if (this.options.lineCap) {\r
+                               this._path.setAttribute('stroke-linecap', this.options.lineCap);\r
+                       }\r
+                       if (this.options.lineJoin) {\r
+                               this._path.setAttribute('stroke-linejoin', this.options.lineJoin);\r
+                       }\r
+               } else {\r
+                       this._path.setAttribute('stroke', 'none');\r
+               }\r
+               if (this.options.fill) {\r
+                       this._path.setAttribute('fill', this.options.fillColor || this.options.color);\r
+                       this._path.setAttribute('fill-opacity', this.options.fillOpacity);\r
+               } else {\r
+                       this._path.setAttribute('fill', 'none');\r
+               }\r
+       },\r
+\r
+       _updatePath: function () {\r
+               var str = this.getPathString();\r
+               if (!str) {\r
+                       // fix webkit empty string parsing bug\r
+                       str = 'M0 0';\r
+               }\r
+               this._path.setAttribute('d', str);\r
+       },\r
+\r
+       // TODO remove duplication with L.Map\r
+       _initEvents: function () {\r
+               if (this.options.clickable) {\r
+                       if (L.Browser.svg || !L.Browser.vml) {\r
+                               L.DomUtil.addClass(this._path, 'leaflet-clickable');\r
+                       }\r
+\r
+                       L.DomEvent.on(this._container, 'click', this._onMouseClick, this);\r
+\r
+                       var events = ['dblclick', 'mousedown', 'mouseover',\r
+                                     'mouseout', 'mousemove', 'contextmenu'];\r
+                       for (var i = 0; i < events.length; i++) {\r
+                               L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);\r
+                       }\r
+               }\r
+       },\r
+\r
+       _onMouseClick: function (e) {\r
+               if (this._map.dragging && this._map.dragging.moved()) { return; }\r
+\r
+               this._fireMouseEvent(e);\r
+       },\r
+\r
+       _fireMouseEvent: function (e) {\r
+               if (!this.hasEventListeners(e.type)) { return; }\r
+\r
+               var map = this._map,\r
+                   containerPoint = map.mouseEventToContainerPoint(e),\r
+                   layerPoint = map.containerPointToLayerPoint(containerPoint),\r
+                   latlng = map.layerPointToLatLng(layerPoint);\r
+\r
+               this.fire(e.type, {\r
+                       latlng: latlng,\r
+                       layerPoint: layerPoint,\r
+                       containerPoint: containerPoint,\r
+                       originalEvent: e\r
+               });\r
+\r
+               if (e.type === 'contextmenu') {\r
+                       L.DomEvent.preventDefault(e);\r
+               }\r
+               if (e.type !== 'mousemove') {\r
+                       L.DomEvent.stopPropagation(e);\r
+               }\r
+       }\r
+});\r
+\r
+L.Map.include({\r
+       _initPathRoot: function () {\r
+               if (!this._pathRoot) {\r
+                       this._pathRoot = L.Path.prototype._createElement('svg');\r
+                       this._panes.overlayPane.appendChild(this._pathRoot);\r
+\r
+                       if (this.options.zoomAnimation && L.Browser.any3d) {\r
+                               L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-animated');\r
+\r
+                               this.on({\r
+                                       'zoomanim': this._animatePathZoom,\r
+                                       'zoomend': this._endPathZoom\r
+                               });\r
+                       } else {\r
+                               L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-hide');\r
+                       }\r
+\r
+                       this.on('moveend', this._updateSvgViewport);\r
+                       this._updateSvgViewport();\r
+               }\r
+       },\r
+\r
+       _animatePathZoom: function (e) {\r
+               var scale = this.getZoomScale(e.zoom),\r
+                   offset = this._getCenterOffset(e.center)._multiplyBy(-scale)._add(this._pathViewport.min);\r
+\r
+               this._pathRoot.style[L.DomUtil.TRANSFORM] =\r
+                       L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ') ';\r
+\r
+               this._pathZooming = true;\r
+       },\r
+\r
+       _endPathZoom: function () {\r
+               this._pathZooming = false;\r
+       },\r
+\r
+       _updateSvgViewport: function () {\r
+\r
+               if (this._pathZooming) {\r
+                       // Do not update SVGs while a zoom animation is going on otherwise the animation will break.\r
+                       // When the zoom animation ends we will be updated again anyway\r
+                       // This fixes the case where you do a momentum move and zoom while the move is still ongoing.\r
+                       return;\r
+               }\r
+\r
+               this._updatePathViewport();\r
+\r
+               var vp = this._pathViewport,\r
+                   min = vp.min,\r
+                   max = vp.max,\r
+                   width = max.x - min.x,\r
+                   height = max.y - min.y,\r
+                   root = this._pathRoot,\r
+                   pane = this._panes.overlayPane;\r
+\r
+               // Hack to make flicker on drag end on mobile webkit less irritating\r
+               if (L.Browser.mobileWebkit) {\r
+                       pane.removeChild(root);\r
+               }\r
+\r
+               L.DomUtil.setPosition(root, min);\r
+               root.setAttribute('width', width);\r
+               root.setAttribute('height', height);\r
+               root.setAttribute('viewBox', [min.x, min.y, width, height].join(' '));\r
+\r
+               if (L.Browser.mobileWebkit) {\r
+                       pane.appendChild(root);\r
+               }\r
+       }\r
+});\r
+
+
+/*\r
+ * Popup extension to L.Path (polylines, polygons, circles), adding popup-related methods.\r
+ */\r
+\r
+L.Path.include({\r
+\r
+       bindPopup: function (content, options) {\r
+\r
+               if (content instanceof L.Popup) {\r
+                       this._popup = content;\r
+               } else {\r
+                       if (!this._popup || options) {\r
+                               this._popup = new L.Popup(options, this);\r
+                       }\r
+                       this._popup.setContent(content);\r
+               }\r
+\r
+               if (!this._popupHandlersAdded) {\r
+                       this\r
+                           .on('click', this._openPopup, this)\r
+                           .on('remove', this.closePopup, this);\r
+\r
+                       this._popupHandlersAdded = true;\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       unbindPopup: function () {\r
+               if (this._popup) {\r
+                       this._popup = null;\r
+                       this\r
+                           .off('click', this._openPopup)\r
+                           .off('remove', this.closePopup);\r
+\r
+                       this._popupHandlersAdded = false;\r
+               }\r
+               return this;\r
+       },\r
+\r
+       openPopup: function (latlng) {\r
+\r
+               if (this._popup) {\r
+                       // open the popup from one of the path's points if not specified\r
+                       latlng = latlng || this._latlng ||\r
+                                this._latlngs[Math.floor(this._latlngs.length / 2)];\r
+\r
+                       this._openPopup({latlng: latlng});\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       closePopup: function () {\r
+               if (this._popup) {\r
+                       this._popup._close();\r
+               }\r
+               return this;\r
+       },\r
+\r
+       _openPopup: function (e) {\r
+               this._popup.setLatLng(e.latlng);\r
+               this._map.openPopup(this._popup);\r
+       }\r
+});\r
+
+
+/*\r
+ * Vector rendering for IE6-8 through VML.\r
+ * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!\r
+ */\r
+\r
+L.Browser.vml = !L.Browser.svg && (function () {\r
+       try {\r
+               var div = document.createElement('div');\r
+               div.innerHTML = '<v:shape adj="1"/>';\r
+\r
+               var shape = div.firstChild;\r
+               shape.style.behavior = 'url(#default#VML)';\r
+\r
+               return shape && (typeof shape.adj === 'object');\r
+\r
+       } catch (e) {\r
+               return false;\r
+       }\r
+}());\r
+\r
+L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({\r
+       statics: {\r
+               VML: true,\r
+               CLIP_PADDING: 0.02\r
+       },\r
+\r
+       _createElement: (function () {\r
+               try {\r
+                       document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');\r
+                       return function (name) {\r
+                               return document.createElement('<lvml:' + name + ' class="lvml">');\r
+                       };\r
+               } catch (e) {\r
+                       return function (name) {\r
+                               return document.createElement(\r
+                                       '<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');\r
+                       };\r
+               }\r
+       }()),\r
+\r
+       _initPath: function () {\r
+               var container = this._container = this._createElement('shape');\r
+\r
+               L.DomUtil.addClass(container, 'leaflet-vml-shape' +\r
+                       (this.options.className ? ' ' + this.options.className : ''));\r
+\r
+               if (this.options.clickable) {\r
+                       L.DomUtil.addClass(container, 'leaflet-clickable');\r
+               }\r
+\r
+               container.coordsize = '1 1';\r
+\r
+               this._path = this._createElement('path');\r
+               container.appendChild(this._path);\r
+\r
+               this._map._pathRoot.appendChild(container);\r
+       },\r
+\r
+       _initStyle: function () {\r
+               this._updateStyle();\r
+       },\r
+\r
+       _updateStyle: function () {\r
+               var stroke = this._stroke,\r
+                   fill = this._fill,\r
+                   options = this.options,\r
+                   container = this._container;\r
+\r
+               container.stroked = options.stroke;\r
+               container.filled = options.fill;\r
+\r
+               if (options.stroke) {\r
+                       if (!stroke) {\r
+                               stroke = this._stroke = this._createElement('stroke');\r
+                               stroke.endcap = 'round';\r
+                               container.appendChild(stroke);\r
+                       }\r
+                       stroke.weight = options.weight + 'px';\r
+                       stroke.color = options.color;\r
+                       stroke.opacity = options.opacity;\r
+\r
+                       if (options.dashArray) {\r
+                               stroke.dashStyle = L.Util.isArray(options.dashArray) ?\r
+                                   options.dashArray.join(' ') :\r
+                                   options.dashArray.replace(/( *, *)/g, ' ');\r
+                       } else {\r
+                               stroke.dashStyle = '';\r
+                       }\r
+                       if (options.lineCap) {\r
+                               stroke.endcap = options.lineCap.replace('butt', 'flat');\r
+                       }\r
+                       if (options.lineJoin) {\r
+                               stroke.joinstyle = options.lineJoin;\r
+                       }\r
+\r
+               } else if (stroke) {\r
+                       container.removeChild(stroke);\r
+                       this._stroke = null;\r
+               }\r
+\r
+               if (options.fill) {\r
+                       if (!fill) {\r
+                               fill = this._fill = this._createElement('fill');\r
+                               container.appendChild(fill);\r
+                       }\r
+                       fill.color = options.fillColor || options.color;\r
+                       fill.opacity = options.fillOpacity;\r
+\r
+               } else if (fill) {\r
+                       container.removeChild(fill);\r
+                       this._fill = null;\r
+               }\r
+       },\r
+\r
+       _updatePath: function () {\r
+               var style = this._container.style;\r
+\r
+               style.display = 'none';\r
+               this._path.v = this.getPathString() + ' '; // the space fixes IE empty path string bug\r
+               style.display = '';\r
+       }\r
+});\r
+\r
+L.Map.include(L.Browser.svg || !L.Browser.vml ? {} : {\r
+       _initPathRoot: function () {\r
+               if (this._pathRoot) { return; }\r
+\r
+               var root = this._pathRoot = document.createElement('div');\r
+               root.className = 'leaflet-vml-container';\r
+               this._panes.overlayPane.appendChild(root);\r
+\r
+               this.on('moveend', this._updatePathViewport);\r
+               this._updatePathViewport();\r
+       }\r
+});\r
+
+
+/*\r
+ * Vector rendering for all browsers that support canvas.\r
+ */\r
+\r
+L.Browser.canvas = (function () {\r
+       return !!document.createElement('canvas').getContext;\r
+}());\r
+\r
+L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({\r
+       statics: {\r
+               //CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value\r
+               CANVAS: true,\r
+               SVG: false\r
+       },\r
+\r
+       redraw: function () {\r
+               if (this._map) {\r
+                       this.projectLatlngs();\r
+                       this._requestUpdate();\r
+               }\r
+               return this;\r
+       },\r
+\r
+       setStyle: function (style) {\r
+               L.setOptions(this, style);\r
+\r
+               if (this._map) {\r
+                       this._updateStyle();\r
+                       this._requestUpdate();\r
+               }\r
+               return this;\r
+       },\r
+\r
+       onRemove: function (map) {\r
+               map\r
+                   .off('viewreset', this.projectLatlngs, this)\r
+                   .off('moveend', this._updatePath, this);\r
+\r
+               if (this.options.clickable) {\r
+                       this._map.off('click', this._onClick, this);\r
+                       this._map.off('mousemove', this._onMouseMove, this);\r
+               }\r
+\r
+               this._requestUpdate();\r
+               \r
+               this.fire('remove');\r
+               this._map = null;\r
+       },\r
+\r
+       _requestUpdate: function () {\r
+               if (this._map && !L.Path._updateRequest) {\r
+                       L.Path._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map);\r
+               }\r
+       },\r
+\r
+       _fireMapMoveEnd: function () {\r
+               L.Path._updateRequest = null;\r
+               this.fire('moveend');\r
+       },\r
+\r
+       _initElements: function () {\r
+               this._map._initPathRoot();\r
+               this._ctx = this._map._canvasCtx;\r
+       },\r
+\r
+       _updateStyle: function () {\r
+               var options = this.options;\r
+\r
+               if (options.stroke) {\r
+                       this._ctx.lineWidth = options.weight;\r
+                       this._ctx.strokeStyle = options.color;\r
+               }\r
+               if (options.fill) {\r
+                       this._ctx.fillStyle = options.fillColor || options.color;\r
+               }\r
+       },\r
+\r
+       _drawPath: function () {\r
+               var i, j, len, len2, point, drawMethod;\r
+\r
+               this._ctx.beginPath();\r
+\r
+               for (i = 0, len = this._parts.length; i < len; i++) {\r
+                       for (j = 0, len2 = this._parts[i].length; j < len2; j++) {\r
+                               point = this._parts[i][j];\r
+                               drawMethod = (j === 0 ? 'move' : 'line') + 'To';\r
+\r
+                               this._ctx[drawMethod](point.x, point.y);\r
+                       }\r
+                       // TODO refactor ugly hack\r
+                       if (this instanceof L.Polygon) {\r
+                               this._ctx.closePath();\r
+                       }\r
+               }\r
+       },\r
+\r
+       _checkIfEmpty: function () {\r
+               return !this._parts.length;\r
+       },\r
+\r
+       _updatePath: function () {\r
+               if (this._checkIfEmpty()) { return; }\r
+\r
+               var ctx = this._ctx,\r
+                   options = this.options;\r
+\r
+               this._drawPath();\r
+               ctx.save();\r
+               this._updateStyle();\r
+\r
+               if (options.fill) {\r
+                       ctx.globalAlpha = options.fillOpacity;\r
+                       ctx.fill();\r
+               }\r
+\r
+               if (options.stroke) {\r
+                       ctx.globalAlpha = options.opacity;\r
+                       ctx.stroke();\r
+               }\r
+\r
+               ctx.restore();\r
+\r
+               // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature\r
+       },\r
+\r
+       _initEvents: function () {\r
+               if (this.options.clickable) {\r
+                       // TODO dblclick\r
+                       this._map.on('mousemove', this._onMouseMove, this);\r
+                       this._map.on('click', this._onClick, this);\r
+               }\r
+       },\r
+\r
+       _onClick: function (e) {\r
+               if (this._containsPoint(e.layerPoint)) {\r
+                       this.fire('click', e);\r
+               }\r
+       },\r
+\r
+       _onMouseMove: function (e) {\r
+               if (!this._map || this._map._animatingZoom) { return; }\r
+\r
+               // TODO don't do on each move\r
+               if (this._containsPoint(e.layerPoint)) {\r
+                       this._ctx.canvas.style.cursor = 'pointer';\r
+                       this._mouseInside = true;\r
+                       this.fire('mouseover', e);\r
+\r
+               } else if (this._mouseInside) {\r
+                       this._ctx.canvas.style.cursor = '';\r
+                       this._mouseInside = false;\r
+                       this.fire('mouseout', e);\r
+               }\r
+       }\r
+});\r
+\r
+L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : {\r
+       _initPathRoot: function () {\r
+               var root = this._pathRoot,\r
+                   ctx;\r
+\r
+               if (!root) {\r
+                       root = this._pathRoot = document.createElement('canvas');\r
+                       root.style.position = 'absolute';\r
+                       ctx = this._canvasCtx = root.getContext('2d');\r
+\r
+                       ctx.lineCap = 'round';\r
+                       ctx.lineJoin = 'round';\r
+\r
+                       this._panes.overlayPane.appendChild(root);\r
+\r
+                       if (this.options.zoomAnimation) {\r
+                               this._pathRoot.className = 'leaflet-zoom-animated';\r
+                               this.on('zoomanim', this._animatePathZoom);\r
+                               this.on('zoomend', this._endPathZoom);\r
+                       }\r
+                       this.on('moveend', this._updateCanvasViewport);\r
+                       this._updateCanvasViewport();\r
+               }\r
+       },\r
+\r
+       _updateCanvasViewport: function () {\r
+               // don't redraw while zooming. See _updateSvgViewport for more details\r
+               if (this._pathZooming) { return; }\r
+               this._updatePathViewport();\r
+\r
+               var vp = this._pathViewport,\r
+                   min = vp.min,\r
+                   size = vp.max.subtract(min),\r
+                   root = this._pathRoot;\r
+\r
+               //TODO check if this works properly on mobile webkit\r
+               L.DomUtil.setPosition(root, min);\r
+               root.width = size.x;\r
+               root.height = size.y;\r
+               root.getContext('2d').translate(-min.x, -min.y);\r
+       }\r
+});\r
+
+
+/*\r
+ * L.LineUtil contains different utility functions for line segments\r
+ * and polylines (clipping, simplification, distances, etc.)\r
+ */\r
+\r
+/*jshint bitwise:false */ // allow bitwise operations for this file\r
+\r
+L.LineUtil = {\r
+\r
+       // Simplify polyline with vertex reduction and Douglas-Peucker simplification.\r
+       // Improves rendering performance dramatically by lessening the number of points to draw.\r
+\r
+       simplify: function (/*Point[]*/ points, /*Number*/ tolerance) {\r
+               if (!tolerance || !points.length) {\r
+                       return points.slice();\r
+               }\r
+\r
+               var sqTolerance = tolerance * tolerance;\r
+\r
+               // stage 1: vertex reduction\r
+               points = this._reducePoints(points, sqTolerance);\r
+\r
+               // stage 2: Douglas-Peucker simplification\r
+               points = this._simplifyDP(points, sqTolerance);\r
+\r
+               return points;\r
+       },\r
+\r
+       // distance from a point to a segment between two points\r
+       pointToSegmentDistance:  function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {\r
+               return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));\r
+       },\r
+\r
+       closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {\r
+               return this._sqClosestPointOnSegment(p, p1, p2);\r
+       },\r
+\r
+       // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm\r
+       _simplifyDP: function (points, sqTolerance) {\r
+\r
+               var len = points.length,\r
+                   ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,\r
+                   markers = new ArrayConstructor(len);\r
+\r
+               markers[0] = markers[len - 1] = 1;\r
+\r
+               this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);\r
+\r
+               var i,\r
+                   newPoints = [];\r
+\r
+               for (i = 0; i < len; i++) {\r
+                       if (markers[i]) {\r
+                               newPoints.push(points[i]);\r
+                       }\r
+               }\r
+\r
+               return newPoints;\r
+       },\r
+\r
+       _simplifyDPStep: function (points, markers, sqTolerance, first, last) {\r
+\r
+               var maxSqDist = 0,\r
+                   index, i, sqDist;\r
+\r
+               for (i = first + 1; i <= last - 1; i++) {\r
+                       sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);\r
+\r
+                       if (sqDist > maxSqDist) {\r
+                               index = i;\r
+                               maxSqDist = sqDist;\r
+                       }\r
+               }\r
+\r
+               if (maxSqDist > sqTolerance) {\r
+                       markers[index] = 1;\r
+\r
+                       this._simplifyDPStep(points, markers, sqTolerance, first, index);\r
+                       this._simplifyDPStep(points, markers, sqTolerance, index, last);\r
+               }\r
+       },\r
+\r
+       // reduce points that are too close to each other to a single point\r
+       _reducePoints: function (points, sqTolerance) {\r
+               var reducedPoints = [points[0]];\r
+\r
+               for (var i = 1, prev = 0, len = points.length; i < len; i++) {\r
+                       if (this._sqDist(points[i], points[prev]) > sqTolerance) {\r
+                               reducedPoints.push(points[i]);\r
+                               prev = i;\r
+                       }\r
+               }\r
+               if (prev < len - 1) {\r
+                       reducedPoints.push(points[len - 1]);\r
+               }\r
+               return reducedPoints;\r
+       },\r
+\r
+       // Cohen-Sutherland line clipping algorithm.\r
+       // Used to avoid rendering parts of a polyline that are not currently visible.\r
+\r
+       clipSegment: function (a, b, bounds, useLastCode) {\r
+               var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),\r
+                   codeB = this._getBitCode(b, bounds),\r
+\r
+                   codeOut, p, newCode;\r
+\r
+               // save 2nd code to avoid calculating it on the next segment\r
+               this._lastCode = codeB;\r
+\r
+               while (true) {\r
+                       // if a,b is inside the clip window (trivial accept)\r
+                       if (!(codeA | codeB)) {\r
+                               return [a, b];\r
+                       // if a,b is outside the clip window (trivial reject)\r
+                       } else if (codeA & codeB) {\r
+                               return false;\r
+                       // other cases\r
+                       } else {\r
+                               codeOut = codeA || codeB;\r
+                               p = this._getEdgeIntersection(a, b, codeOut, bounds);\r
+                               newCode = this._getBitCode(p, bounds);\r
+\r
+                               if (codeOut === codeA) {\r
+                                       a = p;\r
+                                       codeA = newCode;\r
+                               } else {\r
+                                       b = p;\r
+                                       codeB = newCode;\r
+                               }\r
+                       }\r
+               }\r
+       },\r
+\r
+       _getEdgeIntersection: function (a, b, code, bounds) {\r
+               var dx = b.x - a.x,\r
+                   dy = b.y - a.y,\r
+                   min = bounds.min,\r
+                   max = bounds.max;\r
+\r
+               if (code & 8) { // top\r
+                       return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y);\r
+               } else if (code & 4) { // bottom\r
+                       return new L.Point(a.x + dx * (min.y - a.y) / dy, min.y);\r
+               } else if (code & 2) { // right\r
+                       return new L.Point(max.x, a.y + dy * (max.x - a.x) / dx);\r
+               } else if (code & 1) { // left\r
+                       return new L.Point(min.x, a.y + dy * (min.x - a.x) / dx);\r
+               }\r
+       },\r
+\r
+       _getBitCode: function (/*Point*/ p, bounds) {\r
+               var code = 0;\r
+\r
+               if (p.x < bounds.min.x) { // left\r
+                       code |= 1;\r
+               } else if (p.x > bounds.max.x) { // right\r
+                       code |= 2;\r
+               }\r
+               if (p.y < bounds.min.y) { // bottom\r
+                       code |= 4;\r
+               } else if (p.y > bounds.max.y) { // top\r
+                       code |= 8;\r
+               }\r
+\r
+               return code;\r
+       },\r
+\r
+       // square distance (to avoid unnecessary Math.sqrt calls)\r
+       _sqDist: function (p1, p2) {\r
+               var dx = p2.x - p1.x,\r
+                   dy = p2.y - p1.y;\r
+               return dx * dx + dy * dy;\r
+       },\r
+\r
+       // return closest point on segment or distance to that point\r
+       _sqClosestPointOnSegment: function (p, p1, p2, sqDist) {\r
+               var x = p1.x,\r
+                   y = p1.y,\r
+                   dx = p2.x - x,\r
+                   dy = p2.y - y,\r
+                   dot = dx * dx + dy * dy,\r
+                   t;\r
+\r
+               if (dot > 0) {\r
+                       t = ((p.x - x) * dx + (p.y - y) * dy) / dot;\r
+\r
+                       if (t > 1) {\r
+                               x = p2.x;\r
+                               y = p2.y;\r
+                       } else if (t > 0) {\r
+                               x += dx * t;\r
+                               y += dy * t;\r
+                       }\r
+               }\r
+\r
+               dx = p.x - x;\r
+               dy = p.y - y;\r
+\r
+               return sqDist ? dx * dx + dy * dy : new L.Point(x, y);\r
+       }\r
+};\r
+
+
+/*\r
+ * L.Polyline is used to display polylines on a map.\r
+ */\r
+\r
+L.Polyline = L.Path.extend({\r
+       initialize: function (latlngs, options) {\r
+               L.Path.prototype.initialize.call(this, options);\r
+\r
+               this._latlngs = this._convertLatLngs(latlngs);\r
+       },\r
+\r
+       options: {\r
+               // how much to simplify the polyline on each zoom level\r
+               // more = better performance and smoother look, less = more accurate\r
+               smoothFactor: 1.0,\r
+               noClip: false\r
+       },\r
+\r
+       projectLatlngs: function () {\r
+               this._originalPoints = [];\r
+\r
+               for (var i = 0, len = this._latlngs.length; i < len; i++) {\r
+                       this._originalPoints[i] = this._map.latLngToLayerPoint(this._latlngs[i]);\r
+               }\r
+       },\r
+\r
+       getPathString: function () {\r
+               for (var i = 0, len = this._parts.length, str = ''; i < len; i++) {\r
+                       str += this._getPathPartStr(this._parts[i]);\r
+               }\r
+               return str;\r
+       },\r
+\r
+       getLatLngs: function () {\r
+               return this._latlngs;\r
+       },\r
+\r
+       setLatLngs: function (latlngs) {\r
+               this._latlngs = this._convertLatLngs(latlngs);\r
+               return this.redraw();\r
+       },\r
+\r
+       addLatLng: function (latlng) {\r
+               this._latlngs.push(L.latLng(latlng));\r
+               return this.redraw();\r
+       },\r
+\r
+       spliceLatLngs: function () { // (Number index, Number howMany)\r
+               var removed = [].splice.apply(this._latlngs, arguments);\r
+               this._convertLatLngs(this._latlngs, true);\r
+               this.redraw();\r
+               return removed;\r
+       },\r
+\r
+       closestLayerPoint: function (p) {\r
+               var minDistance = Infinity, parts = this._parts, p1, p2, minPoint = null;\r
+\r
+               for (var j = 0, jLen = parts.length; j < jLen; j++) {\r
+                       var points = parts[j];\r
+                       for (var i = 1, len = points.length; i < len; i++) {\r
+                               p1 = points[i - 1];\r
+                               p2 = points[i];\r
+                               var sqDist = L.LineUtil._sqClosestPointOnSegment(p, p1, p2, true);\r
+                               if (sqDist < minDistance) {\r
+                                       minDistance = sqDist;\r
+                                       minPoint = L.LineUtil._sqClosestPointOnSegment(p, p1, p2);\r
+                               }\r
+                       }\r
+               }\r
+               if (minPoint) {\r
+                       minPoint.distance = Math.sqrt(minDistance);\r
+               }\r
+               return minPoint;\r
+       },\r
+\r
+       getBounds: function () {\r
+               return new L.LatLngBounds(this.getLatLngs());\r
+       },\r
+\r
+       _convertLatLngs: function (latlngs, overwrite) {\r
+               var i, len, target = overwrite ? latlngs : [];\r
+\r
+               for (i = 0, len = latlngs.length; i < len; i++) {\r
+                       if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') {\r
+                               return;\r
+                       }\r
+                       target[i] = L.latLng(latlngs[i]);\r
+               }\r
+               return target;\r
+       },\r
+\r
+       _initEvents: function () {\r
+               L.Path.prototype._initEvents.call(this);\r
+       },\r
+\r
+       _getPathPartStr: function (points) {\r
+               var round = L.Path.VML;\r
+\r
+               for (var j = 0, len2 = points.length, str = '', p; j < len2; j++) {\r
+                       p = points[j];\r
+                       if (round) {\r
+                               p._round();\r
+                       }\r
+                       str += (j ? 'L' : 'M') + p.x + ' ' + p.y;\r
+               }\r
+               return str;\r
+       },\r
+\r
+       _clipPoints: function () {\r
+               var points = this._originalPoints,\r
+                   len = points.length,\r
+                   i, k, segment;\r
+\r
+               if (this.options.noClip) {\r
+                       this._parts = [points];\r
+                       return;\r
+               }\r
+\r
+               this._parts = [];\r
+\r
+               var parts = this._parts,\r
+                   vp = this._map._pathViewport,\r
+                   lu = L.LineUtil;\r
+\r
+               for (i = 0, k = 0; i < len - 1; i++) {\r
+                       segment = lu.clipSegment(points[i], points[i + 1], vp, i);\r
+                       if (!segment) {\r
+                               continue;\r
+                       }\r
+\r
+                       parts[k] = parts[k] || [];\r
+                       parts[k].push(segment[0]);\r
+\r
+                       // if segment goes out of screen, or it's the last one, it's the end of the line part\r
+                       if ((segment[1] !== points[i + 1]) || (i === len - 2)) {\r
+                               parts[k].push(segment[1]);\r
+                               k++;\r
+                       }\r
+               }\r
+       },\r
+\r
+       // simplify each clipped part of the polyline\r
+       _simplifyPoints: function () {\r
+               var parts = this._parts,\r
+                   lu = L.LineUtil;\r
+\r
+               for (var i = 0, len = parts.length; i < len; i++) {\r
+                       parts[i] = lu.simplify(parts[i], this.options.smoothFactor);\r
+               }\r
+       },\r
+\r
+       _updatePath: function () {\r
+               if (!this._map) { return; }\r
+\r
+               this._clipPoints();\r
+               this._simplifyPoints();\r
+\r
+               L.Path.prototype._updatePath.call(this);\r
+       }\r
+});\r
+\r
+L.polyline = function (latlngs, options) {\r
+       return new L.Polyline(latlngs, options);\r
+};\r
+
+
+/*\r
+ * L.PolyUtil contains utility functions for polygons (clipping, etc.).\r
+ */\r
+\r
+/*jshint bitwise:false */ // allow bitwise operations here\r
+\r
+L.PolyUtil = {};\r
+\r
+/*\r
+ * Sutherland-Hodgeman polygon clipping algorithm.\r
+ * Used to avoid rendering parts of a polygon that are not currently visible.\r
+ */\r
+L.PolyUtil.clipPolygon = function (points, bounds) {\r
+       var clippedPoints,\r
+           edges = [1, 4, 2, 8],\r
+           i, j, k,\r
+           a, b,\r
+           len, edge, p,\r
+           lu = L.LineUtil;\r
+\r
+       for (i = 0, len = points.length; i < len; i++) {\r
+               points[i]._code = lu._getBitCode(points[i], bounds);\r
+       }\r
+\r
+       // for each edge (left, bottom, right, top)\r
+       for (k = 0; k < 4; k++) {\r
+               edge = edges[k];\r
+               clippedPoints = [];\r
+\r
+               for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {\r
+                       a = points[i];\r
+                       b = points[j];\r
+\r
+                       // if a is inside the clip window\r
+                       if (!(a._code & edge)) {\r
+                               // if b is outside the clip window (a->b goes out of screen)\r
+                               if (b._code & edge) {\r
+                                       p = lu._getEdgeIntersection(b, a, edge, bounds);\r
+                                       p._code = lu._getBitCode(p, bounds);\r
+                                       clippedPoints.push(p);\r
+                               }\r
+                               clippedPoints.push(a);\r
+\r
+                       // else if b is inside the clip window (a->b enters the screen)\r
+                       } else if (!(b._code & edge)) {\r
+                               p = lu._getEdgeIntersection(b, a, edge, bounds);\r
+                               p._code = lu._getBitCode(p, bounds);\r
+                               clippedPoints.push(p);\r
+                       }\r
+               }\r
+               points = clippedPoints;\r
+       }\r
+\r
+       return points;\r
+};\r
+
+
+/*\r
+ * L.Polygon is used to display polygons on a map.\r
+ */\r
+\r
+L.Polygon = L.Polyline.extend({\r
+       options: {\r
+               fill: true\r
+       },\r
+\r
+       initialize: function (latlngs, options) {\r
+               L.Polyline.prototype.initialize.call(this, latlngs, options);\r
+               this._initWithHoles(latlngs);\r
+       },\r
+\r
+       _initWithHoles: function (latlngs) {\r
+               var i, len, hole;\r
+               if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {\r
+                       this._latlngs = this._convertLatLngs(latlngs[0]);\r
+                       this._holes = latlngs.slice(1);\r
+\r
+                       for (i = 0, len = this._holes.length; i < len; i++) {\r
+                               hole = this._holes[i] = this._convertLatLngs(this._holes[i]);\r
+                               if (hole[0].equals(hole[hole.length - 1])) {\r
+                                       hole.pop();\r
+                               }\r
+                       }\r
+               }\r
+\r
+               // filter out last point if its equal to the first one\r
+               latlngs = this._latlngs;\r
+\r
+               if (latlngs.length >= 2 && latlngs[0].equals(latlngs[latlngs.length - 1])) {\r
+                       latlngs.pop();\r
+               }\r
+       },\r
+\r
+       projectLatlngs: function () {\r
+               L.Polyline.prototype.projectLatlngs.call(this);\r
+\r
+               // project polygon holes points\r
+               // TODO move this logic to Polyline to get rid of duplication\r
+               this._holePoints = [];\r
+\r
+               if (!this._holes) { return; }\r
+\r
+               var i, j, len, len2;\r
+\r
+               for (i = 0, len = this._holes.length; i < len; i++) {\r
+                       this._holePoints[i] = [];\r
+\r
+                       for (j = 0, len2 = this._holes[i].length; j < len2; j++) {\r
+                               this._holePoints[i][j] = this._map.latLngToLayerPoint(this._holes[i][j]);\r
+                       }\r
+               }\r
+       },\r
+\r
+       setLatLngs: function (latlngs) {\r
+               if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {\r
+                       this._initWithHoles(latlngs);\r
+                       return this.redraw();\r
+               } else {\r
+                       return L.Polyline.prototype.setLatLngs.call(this, latlngs);\r
+               }\r
+       },\r
+\r
+       _clipPoints: function () {\r
+               var points = this._originalPoints,\r
+                   newParts = [];\r
+\r
+               this._parts = [points].concat(this._holePoints);\r
+\r
+               if (this.options.noClip) { return; }\r
+\r
+               for (var i = 0, len = this._parts.length; i < len; i++) {\r
+                       var clipped = L.PolyUtil.clipPolygon(this._parts[i], this._map._pathViewport);\r
+                       if (clipped.length) {\r
+                               newParts.push(clipped);\r
+                       }\r
+               }\r
+\r
+               this._parts = newParts;\r
+       },\r
+\r
+       _getPathPartStr: function (points) {\r
+               var str = L.Polyline.prototype._getPathPartStr.call(this, points);\r
+               return str + (L.Browser.svg ? 'z' : 'x');\r
+       }\r
+});\r
+\r
+L.polygon = function (latlngs, options) {\r
+       return new L.Polygon(latlngs, options);\r
+};\r
+
+
+/*\r
+ * Contains L.MultiPolyline and L.MultiPolygon layers.\r
+ */\r
+\r
+(function () {\r
+       function createMulti(Klass) {\r
+\r
+               return L.FeatureGroup.extend({\r
+\r
+                       initialize: function (latlngs, options) {\r
+                               this._layers = {};\r
+                               this._options = options;\r
+                               this.setLatLngs(latlngs);\r
+                       },\r
+\r
+                       setLatLngs: function (latlngs) {\r
+                               var i = 0,\r
+                                   len = latlngs.length;\r
+\r
+                               this.eachLayer(function (layer) {\r
+                                       if (i < len) {\r
+                                               layer.setLatLngs(latlngs[i++]);\r
+                                       } else {\r
+                                               this.removeLayer(layer);\r
+                                       }\r
+                               }, this);\r
+\r
+                               while (i < len) {\r
+                                       this.addLayer(new Klass(latlngs[i++], this._options));\r
+                               }\r
+\r
+                               return this;\r
+                       },\r
+\r
+                       getLatLngs: function () {\r
+                               var latlngs = [];\r
+\r
+                               this.eachLayer(function (layer) {\r
+                                       latlngs.push(layer.getLatLngs());\r
+                               });\r
+\r
+                               return latlngs;\r
+                       }\r
+               });\r
+       }\r
+\r
+       L.MultiPolyline = createMulti(L.Polyline);\r
+       L.MultiPolygon = createMulti(L.Polygon);\r
+\r
+       L.multiPolyline = function (latlngs, options) {\r
+               return new L.MultiPolyline(latlngs, options);\r
+       };\r
+\r
+       L.multiPolygon = function (latlngs, options) {\r
+               return new L.MultiPolygon(latlngs, options);\r
+       };\r
+}());\r
+
+
+/*\r
+ * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.\r
+ */\r
+\r
+L.Rectangle = L.Polygon.extend({\r
+       initialize: function (latLngBounds, options) {\r
+               L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);\r
+       },\r
+\r
+       setBounds: function (latLngBounds) {\r
+               this.setLatLngs(this._boundsToLatLngs(latLngBounds));\r
+       },\r
+\r
+       _boundsToLatLngs: function (latLngBounds) {\r
+               latLngBounds = L.latLngBounds(latLngBounds);\r
+               return [\r
+                       latLngBounds.getSouthWest(),\r
+                       latLngBounds.getNorthWest(),\r
+                       latLngBounds.getNorthEast(),\r
+                       latLngBounds.getSouthEast()\r
+               ];\r
+       }\r
+});\r
+\r
+L.rectangle = function (latLngBounds, options) {\r
+       return new L.Rectangle(latLngBounds, options);\r
+};\r
+
+
+/*\r
+ * L.Circle is a circle overlay (with a certain radius in meters).\r
+ */\r
+\r
+L.Circle = L.Path.extend({\r
+       initialize: function (latlng, radius, options) {\r
+               L.Path.prototype.initialize.call(this, options);\r
+\r
+               this._latlng = L.latLng(latlng);\r
+               this._mRadius = radius;\r
+       },\r
+\r
+       options: {\r
+               fill: true\r
+       },\r
+\r
+       setLatLng: function (latlng) {\r
+               this._latlng = L.latLng(latlng);\r
+               return this.redraw();\r
+       },\r
+\r
+       setRadius: function (radius) {\r
+               this._mRadius = radius;\r
+               return this.redraw();\r
+       },\r
+\r
+       projectLatlngs: function () {\r
+               var lngRadius = this._getLngRadius(),\r
+                   latlng = this._latlng,\r
+                   pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]);\r
+\r
+               this._point = this._map.latLngToLayerPoint(latlng);\r
+               this._radius = Math.max(this._point.x - pointLeft.x, 1);\r
+       },\r
+\r
+       getBounds: function () {\r
+               var lngRadius = this._getLngRadius(),\r
+                   latRadius = (this._mRadius / 40075017) * 360,\r
+                   latlng = this._latlng;\r
+\r
+               return new L.LatLngBounds(\r
+                       [latlng.lat - latRadius, latlng.lng - lngRadius],\r
+                       [latlng.lat + latRadius, latlng.lng + lngRadius]);\r
+       },\r
+\r
+       getLatLng: function () {\r
+               return this._latlng;\r
+       },\r
+\r
+       getPathString: function () {\r
+               var p = this._point,\r
+                   r = this._radius;\r
+\r
+               if (this._checkIfEmpty()) {\r
+                       return '';\r
+               }\r
+\r
+               if (L.Browser.svg) {\r
+                       return 'M' + p.x + ',' + (p.y - r) +\r
+                              'A' + r + ',' + r + ',0,1,1,' +\r
+                              (p.x - 0.1) + ',' + (p.y - r) + ' z';\r
+               } else {\r
+                       p._round();\r
+                       r = Math.round(r);\r
+                       return 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r + ' 0,' + (65535 * 360);\r
+               }\r
+       },\r
+\r
+       getRadius: function () {\r
+               return this._mRadius;\r
+       },\r
+\r
+       // TODO Earth hardcoded, move into projection code!\r
+\r
+       _getLatRadius: function () {\r
+               return (this._mRadius / 40075017) * 360;\r
+       },\r
+\r
+       _getLngRadius: function () {\r
+               return this._getLatRadius() / Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat);\r
+       },\r
+\r
+       _checkIfEmpty: function () {\r
+               if (!this._map) {\r
+                       return false;\r
+               }\r
+               var vp = this._map._pathViewport,\r
+                   r = this._radius,\r
+                   p = this._point;\r
+\r
+               return p.x - r > vp.max.x || p.y - r > vp.max.y ||\r
+                      p.x + r < vp.min.x || p.y + r < vp.min.y;\r
+       }\r
+});\r
+\r
+L.circle = function (latlng, radius, options) {\r
+       return new L.Circle(latlng, radius, options);\r
+};\r
+
+
+/*\r
+ * L.CircleMarker is a circle overlay with a permanent pixel radius.\r
+ */\r
+\r
+L.CircleMarker = L.Circle.extend({\r
+       options: {\r
+               radius: 10,\r
+               weight: 2\r
+       },\r
+\r
+       initialize: function (latlng, options) {\r
+               L.Circle.prototype.initialize.call(this, latlng, null, options);\r
+               this._radius = this.options.radius;\r
+       },\r
+\r
+       projectLatlngs: function () {\r
+               this._point = this._map.latLngToLayerPoint(this._latlng);\r
+       },\r
+\r
+       _updateStyle : function () {\r
+               L.Circle.prototype._updateStyle.call(this);\r
+               this.setRadius(this.options.radius);\r
+       },\r
+\r
+       setLatLng: function (latlng) {\r
+               L.Circle.prototype.setLatLng.call(this, latlng);\r
+               if (this._popup && this._popup._isOpen) {\r
+                       this._popup.setLatLng(latlng);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       setRadius: function (radius) {\r
+               this.options.radius = this._radius = radius;\r
+               return this.redraw();\r
+       },\r
+\r
+       getRadius: function () {\r
+               return this._radius;\r
+       }\r
+});\r
+\r
+L.circleMarker = function (latlng, options) {\r
+       return new L.CircleMarker(latlng, options);\r
+};\r
+
+
+/*\r
+ * Extends L.Polyline to be able to manually detect clicks on Canvas-rendered polylines.\r
+ */\r
+\r
+L.Polyline.include(!L.Path.CANVAS ? {} : {\r
+       _containsPoint: function (p, closed) {\r
+               var i, j, k, len, len2, dist, part,\r
+                   w = this.options.weight / 2;\r
+\r
+               if (L.Browser.touch) {\r
+                       w += 10; // polyline click tolerance on touch devices\r
+               }\r
+\r
+               for (i = 0, len = this._parts.length; i < len; i++) {\r
+                       part = this._parts[i];\r
+                       for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {\r
+                               if (!closed && (j === 0)) {\r
+                                       continue;\r
+                               }\r
+\r
+                               dist = L.LineUtil.pointToSegmentDistance(p, part[k], part[j]);\r
+\r
+                               if (dist <= w) {\r
+                                       return true;\r
+                               }\r
+                       }\r
+               }\r
+               return false;\r
+       }\r
+});\r
+
+
+/*\r
+ * Extends L.Polygon to be able to manually detect clicks on Canvas-rendered polygons.\r
+ */\r
+\r
+L.Polygon.include(!L.Path.CANVAS ? {} : {\r
+       _containsPoint: function (p) {\r
+               var inside = false,\r
+                   part, p1, p2,\r
+                   i, j, k,\r
+                   len, len2;\r
+\r
+               // TODO optimization: check if within bounds first\r
+\r
+               if (L.Polyline.prototype._containsPoint.call(this, p, true)) {\r
+                       // click on polygon border\r
+                       return true;\r
+               }\r
+\r
+               // ray casting algorithm for detecting if point is in polygon\r
+\r
+               for (i = 0, len = this._parts.length; i < len; i++) {\r
+                       part = this._parts[i];\r
+\r
+                       for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {\r
+                               p1 = part[j];\r
+                               p2 = part[k];\r
+\r
+                               if (((p1.y > p.y) !== (p2.y > p.y)) &&\r
+                                               (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {\r
+                                       inside = !inside;\r
+                               }\r
+                       }\r
+               }\r
+\r
+               return inside;\r
+       }\r
+});\r
+
+
+/*\r
+ * Extends L.Circle with Canvas-specific code.\r
+ */\r
+\r
+L.Circle.include(!L.Path.CANVAS ? {} : {\r
+       _drawPath: function () {\r
+               var p = this._point;\r
+               this._ctx.beginPath();\r
+               this._ctx.arc(p.x, p.y, this._radius, 0, Math.PI * 2, false);\r
+       },\r
+\r
+       _containsPoint: function (p) {\r
+               var center = this._point,\r
+                   w2 = this.options.stroke ? this.options.weight / 2 : 0;\r
+\r
+               return (p.distanceTo(center) <= this._radius + w2);\r
+       }\r
+});\r
+
+
+/*
+ * CircleMarker canvas specific drawing parts.
+ */
+
+L.CircleMarker.include(!L.Path.CANVAS ? {} : {
+       _updateStyle: function () {
+               L.Path.prototype._updateStyle.call(this);
+       }
+});
+
+
+/*\r
+ * L.GeoJSON turns any GeoJSON data into a Leaflet layer.\r
+ */\r
+\r
+L.GeoJSON = L.FeatureGroup.extend({\r
+\r
+       initialize: function (geojson, options) {\r
+               L.setOptions(this, options);\r
+\r
+               this._layers = {};\r
+\r
+               if (geojson) {\r
+                       this.addData(geojson);\r
+               }\r
+       },\r
+\r
+       addData: function (geojson) {\r
+               var features = L.Util.isArray(geojson) ? geojson : geojson.features,\r
+                   i, len, feature;\r
+\r
+               if (features) {\r
+                       for (i = 0, len = features.length; i < len; i++) {\r
+                               // Only add this if geometry or geometries are set and not null\r
+                               feature = features[i];\r
+                               if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {\r
+                                       this.addData(features[i]);\r
+                               }\r
+                       }\r
+                       return this;\r
+               }\r
+\r
+               var options = this.options;\r
+\r
+               if (options.filter && !options.filter(geojson)) { return; }\r
+\r
+               var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer, options.coordsToLatLng, options);\r
+               layer.feature = L.GeoJSON.asFeature(geojson);\r
+\r
+               layer.defaultOptions = layer.options;\r
+               this.resetStyle(layer);\r
+\r
+               if (options.onEachFeature) {\r
+                       options.onEachFeature(geojson, layer);\r
+               }\r
+\r
+               return this.addLayer(layer);\r
+       },\r
+\r
+       resetStyle: function (layer) {\r
+               var style = this.options.style;\r
+               if (style) {\r
+                       // reset any custom styles\r
+                       L.Util.extend(layer.options, layer.defaultOptions);\r
+\r
+                       this._setLayerStyle(layer, style);\r
+               }\r
+       },\r
+\r
+       setStyle: function (style) {\r
+               this.eachLayer(function (layer) {\r
+                       this._setLayerStyle(layer, style);\r
+               }, this);\r
+       },\r
+\r
+       _setLayerStyle: function (layer, style) {\r
+               if (typeof style === 'function') {\r
+                       style = style(layer.feature);\r
+               }\r
+               if (layer.setStyle) {\r
+                       layer.setStyle(style);\r
+               }\r
+       }\r
+});\r
+\r
+L.extend(L.GeoJSON, {\r
+       geometryToLayer: function (geojson, pointToLayer, coordsToLatLng, vectorOptions) {\r
+               var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,\r
+                   coords = geometry.coordinates,\r
+                   layers = [],\r
+                   latlng, latlngs, i, len;\r
+\r
+               coordsToLatLng = coordsToLatLng || this.coordsToLatLng;\r
+\r
+               switch (geometry.type) {\r
+               case 'Point':\r
+                       latlng = coordsToLatLng(coords);\r
+                       return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);\r
+\r
+               case 'MultiPoint':\r
+                       for (i = 0, len = coords.length; i < len; i++) {\r
+                               latlng = coordsToLatLng(coords[i]);\r
+                               layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng));\r
+                       }\r
+                       return new L.FeatureGroup(layers);\r
+\r
+               case 'LineString':\r
+                       latlngs = this.coordsToLatLngs(coords, 0, coordsToLatLng);\r
+                       return new L.Polyline(latlngs, vectorOptions);\r
+\r
+               case 'Polygon':\r
+                       if (coords.length === 2 && !coords[1].length) {\r
+                               throw new Error('Invalid GeoJSON object.');\r
+                       }\r
+                       latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng);\r
+                       return new L.Polygon(latlngs, vectorOptions);\r
+\r
+               case 'MultiLineString':\r
+                       latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng);\r
+                       return new L.MultiPolyline(latlngs, vectorOptions);\r
+\r
+               case 'MultiPolygon':\r
+                       latlngs = this.coordsToLatLngs(coords, 2, coordsToLatLng);\r
+                       return new L.MultiPolygon(latlngs, vectorOptions);\r
+\r
+               case 'GeometryCollection':\r
+                       for (i = 0, len = geometry.geometries.length; i < len; i++) {\r
+\r
+                               layers.push(this.geometryToLayer({\r
+                                       geometry: geometry.geometries[i],\r
+                                       type: 'Feature',\r
+                                       properties: geojson.properties\r
+                               }, pointToLayer, coordsToLatLng, vectorOptions));\r
+                       }\r
+                       return new L.FeatureGroup(layers);\r
+\r
+               default:\r
+                       throw new Error('Invalid GeoJSON object.');\r
+               }\r
+       },\r
+\r
+       coordsToLatLng: function (coords) { // (Array[, Boolean]) -> LatLng\r
+               return new L.LatLng(coords[1], coords[0], coords[2]);\r
+       },\r
+\r
+       coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) { // (Array[, Number, Function]) -> Array\r
+               var latlng, i, len,\r
+                   latlngs = [];\r
+\r
+               for (i = 0, len = coords.length; i < len; i++) {\r
+                       latlng = levelsDeep ?\r
+                               this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) :\r
+                               (coordsToLatLng || this.coordsToLatLng)(coords[i]);\r
+\r
+                       latlngs.push(latlng);\r
+               }\r
+\r
+               return latlngs;\r
+       },\r
+\r
+       latLngToCoords: function (latlng) {\r
+               var coords = [latlng.lng, latlng.lat];\r
+\r
+               if (latlng.alt !== undefined) {\r
+                       coords.push(latlng.alt);\r
+               }\r
+               return coords;\r
+       },\r
+\r
+       latLngsToCoords: function (latLngs) {\r
+               var coords = [];\r
+\r
+               for (var i = 0, len = latLngs.length; i < len; i++) {\r
+                       coords.push(L.GeoJSON.latLngToCoords(latLngs[i]));\r
+               }\r
+\r
+               return coords;\r
+       },\r
+\r
+       getFeature: function (layer, newGeometry) {\r
+               return layer.feature ? L.extend({}, layer.feature, {geometry: newGeometry}) : L.GeoJSON.asFeature(newGeometry);\r
+       },\r
+\r
+       asFeature: function (geoJSON) {\r
+               if (geoJSON.type === 'Feature') {\r
+                       return geoJSON;\r
+               }\r
+\r
+               return {\r
+                       type: 'Feature',\r
+                       properties: {},\r
+                       geometry: geoJSON\r
+               };\r
+       }\r
+});\r
+\r
+var PointToGeoJSON = {\r
+       toGeoJSON: function () {\r
+               return L.GeoJSON.getFeature(this, {\r
+                       type: 'Point',\r
+                       coordinates: L.GeoJSON.latLngToCoords(this.getLatLng())\r
+               });\r
+       }\r
+};\r
+\r
+L.Marker.include(PointToGeoJSON);\r
+L.Circle.include(PointToGeoJSON);\r
+L.CircleMarker.include(PointToGeoJSON);\r
+\r
+L.Polyline.include({\r
+       toGeoJSON: function () {\r
+               return L.GeoJSON.getFeature(this, {\r
+                       type: 'LineString',\r
+                       coordinates: L.GeoJSON.latLngsToCoords(this.getLatLngs())\r
+               });\r
+       }\r
+});\r
+\r
+L.Polygon.include({\r
+       toGeoJSON: function () {\r
+               var coords = [L.GeoJSON.latLngsToCoords(this.getLatLngs())],\r
+                   i, len, hole;\r
+\r
+               coords[0].push(coords[0][0]);\r
+\r
+               if (this._holes) {\r
+                       for (i = 0, len = this._holes.length; i < len; i++) {\r
+                               hole = L.GeoJSON.latLngsToCoords(this._holes[i]);\r
+                               hole.push(hole[0]);\r
+                               coords.push(hole);\r
+                       }\r
+               }\r
+\r
+               return L.GeoJSON.getFeature(this, {\r
+                       type: 'Polygon',\r
+                       coordinates: coords\r
+               });\r
+       }\r
+});\r
+\r
+(function () {\r
+       function multiToGeoJSON(type) {\r
+               return function () {\r
+                       var coords = [];\r
+\r
+                       this.eachLayer(function (layer) {\r
+                               coords.push(layer.toGeoJSON().geometry.coordinates);\r
+                       });\r
+\r
+                       return L.GeoJSON.getFeature(this, {\r
+                               type: type,\r
+                               coordinates: coords\r
+                       });\r
+               };\r
+       }\r
+\r
+       L.MultiPolyline.include({toGeoJSON: multiToGeoJSON('MultiLineString')});\r
+       L.MultiPolygon.include({toGeoJSON: multiToGeoJSON('MultiPolygon')});\r
+\r
+       L.LayerGroup.include({\r
+               toGeoJSON: function () {\r
+\r
+                       var geometry = this.feature && this.feature.geometry,\r
+                               jsons = [],\r
+                               json;\r
+\r
+                       if (geometry && geometry.type === 'MultiPoint') {\r
+                               return multiToGeoJSON('MultiPoint').call(this);\r
+                       }\r
+\r
+                       var isGeometryCollection = geometry && geometry.type === 'GeometryCollection';\r
+\r
+                       this.eachLayer(function (layer) {\r
+                               if (layer.toGeoJSON) {\r
+                                       json = layer.toGeoJSON();\r
+                                       jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json));\r
+                               }\r
+                       });\r
+\r
+                       if (isGeometryCollection) {\r
+                               return L.GeoJSON.getFeature(this, {\r
+                                       geometries: jsons,\r
+                                       type: 'GeometryCollection'\r
+                               });\r
+                       }\r
+\r
+                       return {\r
+                               type: 'FeatureCollection',\r
+                               features: jsons\r
+                       };\r
+               }\r
+       });\r
+}());\r
+\r
+L.geoJson = function (geojson, options) {\r
+       return new L.GeoJSON(geojson, options);\r
+};\r
+
+
+/*\r
+ * L.DomEvent contains functions for working with DOM events.\r
+ */\r
+\r
+L.DomEvent = {\r
+       /* inspired by John Resig, Dean Edwards and YUI addEvent implementations */\r
+       addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object])\r
+\r
+               var id = L.stamp(fn),\r
+                   key = '_leaflet_' + type + id,\r
+                   handler, originalHandler, newType;\r
+\r
+               if (obj[key]) { return this; }\r
+\r
+               handler = function (e) {\r
+                       return fn.call(context || obj, e || L.DomEvent._getEvent());\r
+               };\r
+\r
+               if (L.Browser.pointer && type.indexOf('touch') === 0) {\r
+                       return this.addPointerListener(obj, type, handler, id);\r
+               }\r
+               if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {\r
+                       this.addDoubleTapListener(obj, handler, id);\r
+               }\r
+\r
+               if ('addEventListener' in obj) {\r
+\r
+                       if (type === 'mousewheel') {\r
+                               obj.addEventListener('DOMMouseScroll', handler, false);\r
+                               obj.addEventListener(type, handler, false);\r
+\r
+                       } else if ((type === 'mouseenter') || (type === 'mouseleave')) {\r
+\r
+                               originalHandler = handler;\r
+                               newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout');\r
+\r
+                               handler = function (e) {\r
+                                       if (!L.DomEvent._checkMouse(obj, e)) { return; }\r
+                                       return originalHandler(e);\r
+                               };\r
+\r
+                               obj.addEventListener(newType, handler, false);\r
+\r
+                       } else if (type === 'click' && L.Browser.android) {\r
+                               originalHandler = handler;\r
+                               handler = function (e) {\r
+                                       return L.DomEvent._filterClick(e, originalHandler);\r
+                               };\r
+\r
+                               obj.addEventListener(type, handler, false);\r
+                       } else {\r
+                               obj.addEventListener(type, handler, false);\r
+                       }\r
+\r
+               } else if ('attachEvent' in obj) {\r
+                       obj.attachEvent('on' + type, handler);\r
+               }\r
+\r
+               obj[key] = handler;\r
+\r
+               return this;\r
+       },\r
+\r
+       removeListener: function (obj, type, fn) {  // (HTMLElement, String, Function)\r
+\r
+               var id = L.stamp(fn),\r
+                   key = '_leaflet_' + type + id,\r
+                   handler = obj[key];\r
+\r
+               if (!handler) { return this; }\r
+\r
+               if (L.Browser.pointer && type.indexOf('touch') === 0) {\r
+                       this.removePointerListener(obj, type, id);\r
+               } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {\r
+                       this.removeDoubleTapListener(obj, id);\r
+\r
+               } else if ('removeEventListener' in obj) {\r
+\r
+                       if (type === 'mousewheel') {\r
+                               obj.removeEventListener('DOMMouseScroll', handler, false);\r
+                               obj.removeEventListener(type, handler, false);\r
+\r
+                       } else if ((type === 'mouseenter') || (type === 'mouseleave')) {\r
+                               obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false);\r
+                       } else {\r
+                               obj.removeEventListener(type, handler, false);\r
+                       }\r
+               } else if ('detachEvent' in obj) {\r
+                       obj.detachEvent('on' + type, handler);\r
+               }\r
+\r
+               obj[key] = null;\r
+\r
+               return this;\r
+       },\r
+\r
+       stopPropagation: function (e) {\r
+\r
+               if (e.stopPropagation) {\r
+                       e.stopPropagation();\r
+               } else {\r
+                       e.cancelBubble = true;\r
+               }\r
+               L.DomEvent._skipped(e);\r
+\r
+               return this;\r
+       },\r
+\r
+       disableScrollPropagation: function (el) {\r
+               var stop = L.DomEvent.stopPropagation;\r
+\r
+               return L.DomEvent\r
+                       .on(el, 'mousewheel', stop)\r
+                       .on(el, 'MozMousePixelScroll', stop);\r
+       },\r
+\r
+       disableClickPropagation: function (el) {\r
+               var stop = L.DomEvent.stopPropagation;\r
+\r
+               for (var i = L.Draggable.START.length - 1; i >= 0; i--) {\r
+                       L.DomEvent.on(el, L.Draggable.START[i], stop);\r
+               }\r
+\r
+               return L.DomEvent\r
+                       .on(el, 'click', L.DomEvent._fakeStop)\r
+                       .on(el, 'dblclick', stop);\r
+       },\r
+\r
+       preventDefault: function (e) {\r
+\r
+               if (e.preventDefault) {\r
+                       e.preventDefault();\r
+               } else {\r
+                       e.returnValue = false;\r
+               }\r
+               return this;\r
+       },\r
+\r
+       stop: function (e) {\r
+               return L.DomEvent\r
+                       .preventDefault(e)\r
+                       .stopPropagation(e);\r
+       },\r
+\r
+       getMousePosition: function (e, container) {\r
+               if (!container) {\r
+                       return new L.Point(e.clientX, e.clientY);\r
+               }\r
+\r
+               var rect = container.getBoundingClientRect();\r
+\r
+               return new L.Point(\r
+                       e.clientX - rect.left - container.clientLeft,\r
+                       e.clientY - rect.top - container.clientTop);\r
+       },\r
+\r
+       getWheelDelta: function (e) {\r
+\r
+               var delta = 0;\r
+\r
+               if (e.wheelDelta) {\r
+                       delta = e.wheelDelta / 120;\r
+               }\r
+               if (e.detail) {\r
+                       delta = -e.detail / 3;\r
+               }\r
+               return delta;\r
+       },\r
+\r
+       _skipEvents: {},\r
+\r
+       _fakeStop: function (e) {\r
+               // fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e)\r
+               L.DomEvent._skipEvents[e.type] = true;\r
+       },\r
+\r
+       _skipped: function (e) {\r
+               var skipped = this._skipEvents[e.type];\r
+               // reset when checking, as it's only used in map container and propagates outside of the map\r
+               this._skipEvents[e.type] = false;\r
+               return skipped;\r
+       },\r
+\r
+       // check if element really left/entered the event target (for mouseenter/mouseleave)\r
+       _checkMouse: function (el, e) {\r
+\r
+               var related = e.relatedTarget;\r
+\r
+               if (!related) { return true; }\r
+\r
+               try {\r
+                       while (related && (related !== el)) {\r
+                               related = related.parentNode;\r
+                       }\r
+               } catch (err) {\r
+                       return false;\r
+               }\r
+               return (related !== el);\r
+       },\r
+\r
+       _getEvent: function () { // evil magic for IE\r
+               /*jshint noarg:false */\r
+               var e = window.event;\r
+               if (!e) {\r
+                       var caller = arguments.callee.caller;\r
+                       while (caller) {\r
+                               e = caller['arguments'][0];\r
+                               if (e && window.Event === e.constructor) {\r
+                                       break;\r
+                               }\r
+                               caller = caller.caller;\r
+                       }\r
+               }\r
+               return e;\r
+       },\r
+\r
+       // this is a horrible workaround for a bug in Android where a single touch triggers two click events\r
+       _filterClick: function (e, handler) {\r
+               var timeStamp = (e.timeStamp || e.originalEvent.timeStamp),\r
+                       elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);\r
+\r
+               // are they closer together than 500ms yet more than 100ms?\r
+               // Android typically triggers them ~300ms apart while multiple listeners\r
+               // on the same event should be triggered far faster;\r
+               // or check if click is simulated on the element, and if it is, reject any non-simulated events\r
+\r
+               if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {\r
+                       L.DomEvent.stop(e);\r
+                       return;\r
+               }\r
+               L.DomEvent._lastClick = timeStamp;\r
+\r
+               return handler(e);\r
+       }\r
+};\r
+\r
+L.DomEvent.on = L.DomEvent.addListener;\r
+L.DomEvent.off = L.DomEvent.removeListener;\r
+
+
+/*\r
+ * L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too.\r
+ */\r
+\r
+L.Draggable = L.Class.extend({\r
+       includes: L.Mixin.Events,\r
+\r
+       statics: {\r
+               START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],\r
+               END: {\r
+                       mousedown: 'mouseup',\r
+                       touchstart: 'touchend',\r
+                       pointerdown: 'touchend',\r
+                       MSPointerDown: 'touchend'\r
+               },\r
+               MOVE: {\r
+                       mousedown: 'mousemove',\r
+                       touchstart: 'touchmove',\r
+                       pointerdown: 'touchmove',\r
+                       MSPointerDown: 'touchmove'\r
+               }\r
+       },\r
+\r
+       initialize: function (element, dragStartTarget) {\r
+               this._element = element;\r
+               this._dragStartTarget = dragStartTarget || element;\r
+       },\r
+\r
+       enable: function () {\r
+               if (this._enabled) { return; }\r
+\r
+               for (var i = L.Draggable.START.length - 1; i >= 0; i--) {\r
+                       L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);\r
+               }\r
+\r
+               this._enabled = true;\r
+       },\r
+\r
+       disable: function () {\r
+               if (!this._enabled) { return; }\r
+\r
+               for (var i = L.Draggable.START.length - 1; i >= 0; i--) {\r
+                       L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);\r
+               }\r
+\r
+               this._enabled = false;\r
+               this._moved = false;\r
+       },\r
+\r
+       _onDown: function (e) {\r
+               this._moved = false;\r
+\r
+               if (e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }\r
+\r
+               L.DomEvent.stopPropagation(e);\r
+\r
+               if (L.Draggable._disabled) { return; }\r
+\r
+               L.DomUtil.disableImageDrag();\r
+               L.DomUtil.disableTextSelection();\r
+\r
+               if (this._moving) { return; }\r
+\r
+               var first = e.touches ? e.touches[0] : e;\r
+\r
+               this._startPoint = new L.Point(first.clientX, first.clientY);\r
+               this._startPos = this._newPos = L.DomUtil.getPosition(this._element);\r
+\r
+               L.DomEvent\r
+                   .on(document, L.Draggable.MOVE[e.type], this._onMove, this)\r
+                   .on(document, L.Draggable.END[e.type], this._onUp, this);\r
+       },\r
+\r
+       _onMove: function (e) {\r
+               if (e.touches && e.touches.length > 1) {\r
+                       this._moved = true;\r
+                       return;\r
+               }\r
+\r
+               var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),\r
+                   newPoint = new L.Point(first.clientX, first.clientY),\r
+                   offset = newPoint.subtract(this._startPoint);\r
+\r
+               if (!offset.x && !offset.y) { return; }\r
+               if (L.Browser.touch && Math.abs(offset.x) + Math.abs(offset.y) < 3) { return; }\r
+\r
+               L.DomEvent.preventDefault(e);\r
+\r
+               if (!this._moved) {\r
+                       this.fire('dragstart');\r
+\r
+                       this._moved = true;\r
+                       this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);\r
+\r
+                       L.DomUtil.addClass(document.body, 'leaflet-dragging');\r
+                       this._lastTarget = e.target || e.srcElement;\r
+                       L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target');\r
+               }\r
+\r
+               this._newPos = this._startPos.add(offset);\r
+               this._moving = true;\r
+\r
+               L.Util.cancelAnimFrame(this._animRequest);\r
+               this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget);\r
+       },\r
+\r
+       _updatePosition: function () {\r
+               this.fire('predrag');\r
+               L.DomUtil.setPosition(this._element, this._newPos);\r
+               this.fire('drag');\r
+       },\r
+\r
+       _onUp: function () {\r
+               L.DomUtil.removeClass(document.body, 'leaflet-dragging');\r
+\r
+               if (this._lastTarget) {\r
+                       L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target');\r
+                       this._lastTarget = null;\r
+               }\r
+\r
+               for (var i in L.Draggable.MOVE) {\r
+                       L.DomEvent\r
+                           .off(document, L.Draggable.MOVE[i], this._onMove)\r
+                           .off(document, L.Draggable.END[i], this._onUp);\r
+               }\r
+\r
+               L.DomUtil.enableImageDrag();\r
+               L.DomUtil.enableTextSelection();\r
+\r
+               if (this._moved && this._moving) {\r
+                       // ensure drag is not fired after dragend\r
+                       L.Util.cancelAnimFrame(this._animRequest);\r
+\r
+                       this.fire('dragend', {\r
+                               distance: this._newPos.distanceTo(this._startPos)\r
+                       });\r
+               }\r
+\r
+               this._moving = false;\r
+       }\r
+});\r
+
+
+/*
+       L.Handler is a base class for handler classes that are used internally to inject
+       interaction features like dragging to classes like Map and Marker.
+*/
+
+L.Handler = L.Class.extend({
+       initialize: function (map) {
+               this._map = map;
+       },
+
+       enable: function () {
+               if (this._enabled) { return; }
+
+               this._enabled = true;
+               this.addHooks();
+       },
+
+       disable: function () {
+               if (!this._enabled) { return; }
+
+               this._enabled = false;
+               this.removeHooks();
+       },
+
+       enabled: function () {
+               return !!this._enabled;
+       }
+});
+
+
+/*
+ * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
+ */
+
+L.Map.mergeOptions({
+       dragging: true,
+
+       inertia: !L.Browser.android23,
+       inertiaDeceleration: 3400, // px/s^2
+       inertiaMaxSpeed: Infinity, // px/s
+       inertiaThreshold: L.Browser.touch ? 32 : 18, // ms
+       easeLinearity: 0.25,
+
+       // TODO refactor, move to CRS
+       worldCopyJump: false
+});
+
+L.Map.Drag = L.Handler.extend({
+       addHooks: function () {
+               if (!this._draggable) {
+                       var map = this._map;
+
+                       this._draggable = new L.Draggable(map._mapPane, map._container);
+
+                       this._draggable.on({
+                               'dragstart': this._onDragStart,
+                               'drag': this._onDrag,
+                               'dragend': this._onDragEnd
+                       }, this);
+
+                       if (map.options.worldCopyJump) {
+                               this._draggable.on('predrag', this._onPreDrag, this);
+                               map.on('viewreset', this._onViewReset, this);
+
+                               map.whenReady(this._onViewReset, this);
+                       }
+               }
+               this._draggable.enable();
+       },
+
+       removeHooks: function () {
+               this._draggable.disable();
+       },
+
+       moved: function () {
+               return this._draggable && this._draggable._moved;
+       },
+
+       _onDragStart: function () {
+               var map = this._map;
+
+               if (map._panAnim) {
+                       map._panAnim.stop();
+               }
+
+               map
+                   .fire('movestart')
+                   .fire('dragstart');
+
+               if (map.options.inertia) {
+                       this._positions = [];
+                       this._times = [];
+               }
+       },
+
+       _onDrag: function () {
+               if (this._map.options.inertia) {
+                       var time = this._lastTime = +new Date(),
+                           pos = this._lastPos = this._draggable._newPos;
+
+                       this._positions.push(pos);
+                       this._times.push(time);
+
+                       if (time - this._times[0] > 200) {
+                               this._positions.shift();
+                               this._times.shift();
+                       }
+               }
+
+               this._map
+                   .fire('move')
+                   .fire('drag');
+       },
+
+       _onViewReset: function () {
+               // TODO fix hardcoded Earth values
+               var pxCenter = this._map.getSize()._divideBy(2),
+                   pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
+
+               this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
+               this._worldWidth = this._map.project([0, 180]).x;
+       },
+
+       _onPreDrag: function () {
+               // TODO refactor to be able to adjust map pane position after zoom
+               var worldWidth = this._worldWidth,
+                   halfWidth = Math.round(worldWidth / 2),
+                   dx = this._initialWorldOffset,
+                   x = this._draggable._newPos.x,
+                   newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
+                   newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
+                   newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
+
+               this._draggable._newPos.x = newX;
+       },
+
+       _onDragEnd: function (e) {
+               var map = this._map,
+                   options = map.options,
+                   delay = +new Date() - this._lastTime,
+
+                   noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0];
+
+               map.fire('dragend', e);
+
+               if (noInertia) {
+                       map.fire('moveend');
+
+               } else {
+
+                       var direction = this._lastPos.subtract(this._positions[0]),
+                           duration = (this._lastTime + delay - this._times[0]) / 1000,
+                           ease = options.easeLinearity,
+
+                           speedVector = direction.multiplyBy(ease / duration),
+                           speed = speedVector.distanceTo([0, 0]),
+
+                           limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
+                           limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
+
+                           decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
+                           offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
+
+                       if (!offset.x || !offset.y) {
+                               map.fire('moveend');
+
+                       } else {
+                               offset = map._limitOffset(offset, map.options.maxBounds);
+
+                               L.Util.requestAnimFrame(function () {
+                                       map.panBy(offset, {
+                                               duration: decelerationDuration,
+                                               easeLinearity: ease,
+                                               noMoveStart: true
+                                       });
+                               });
+                       }
+               }
+       }
+});
+
+L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
+
+
+/*
+ * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
+ */
+
+L.Map.mergeOptions({
+       doubleClickZoom: true
+});
+
+L.Map.DoubleClickZoom = L.Handler.extend({
+       addHooks: function () {
+               this._map.on('dblclick', this._onDoubleClick, this);
+       },
+
+       removeHooks: function () {
+               this._map.off('dblclick', this._onDoubleClick, this);
+       },
+
+       _onDoubleClick: function (e) {
+               var map = this._map,
+                   zoom = map.getZoom() + (e.originalEvent.shiftKey ? -1 : 1);
+
+               if (map.options.doubleClickZoom === 'center') {
+                       map.setZoom(zoom);
+               } else {
+                       map.setZoomAround(e.containerPoint, zoom);
+               }
+       }
+});
+
+L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
+
+
+/*
+ * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
+ */
+
+L.Map.mergeOptions({
+       scrollWheelZoom: true
+});
+
+L.Map.ScrollWheelZoom = L.Handler.extend({
+       addHooks: function () {
+               L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
+               L.DomEvent.on(this._map._container, 'MozMousePixelScroll', L.DomEvent.preventDefault);
+               this._delta = 0;
+       },
+
+       removeHooks: function () {
+               L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll);
+               L.DomEvent.off(this._map._container, 'MozMousePixelScroll', L.DomEvent.preventDefault);
+       },
+
+       _onWheelScroll: function (e) {
+               var delta = L.DomEvent.getWheelDelta(e);
+
+               this._delta += delta;
+               this._lastMousePos = this._map.mouseEventToContainerPoint(e);
+
+               if (!this._startTime) {
+                       this._startTime = +new Date();
+               }
+
+               var left = Math.max(40 - (+new Date() - this._startTime), 0);
+
+               clearTimeout(this._timer);
+               this._timer = setTimeout(L.bind(this._performZoom, this), left);
+
+               L.DomEvent.preventDefault(e);
+               L.DomEvent.stopPropagation(e);
+       },
+
+       _performZoom: function () {
+               var map = this._map,
+                   delta = this._delta,
+                   zoom = map.getZoom();
+
+               delta = delta > 0 ? Math.ceil(delta) : Math.floor(delta);
+               delta = Math.max(Math.min(delta, 4), -4);
+               delta = map._limitZoom(zoom + delta) - zoom;
+
+               this._delta = 0;
+               this._startTime = null;
+
+               if (!delta) { return; }
+
+               if (map.options.scrollWheelZoom === 'center') {
+                       map.setZoom(zoom + delta);
+               } else {
+                       map.setZoomAround(this._lastMousePos, zoom + delta);
+               }
+       }
+});
+
+L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
+
+
+/*\r
+ * Extends the event handling code with double tap support for mobile browsers.\r
+ */\r
+\r
+L.extend(L.DomEvent, {\r
+\r
+       _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',\r
+       _touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend',\r
+\r
+       // inspired by Zepto touch code by Thomas Fuchs\r
+       addDoubleTapListener: function (obj, handler, id) {\r
+               var last,\r
+                   doubleTap = false,\r
+                   delay = 250,\r
+                   touch,\r
+                   pre = '_leaflet_',\r
+                   touchstart = this._touchstart,\r
+                   touchend = this._touchend,\r
+                   trackedTouches = [];\r
+\r
+               function onTouchStart(e) {\r
+                       var count;\r
+\r
+                       if (L.Browser.pointer) {\r
+                               trackedTouches.push(e.pointerId);\r
+                               count = trackedTouches.length;\r
+                       } else {\r
+                               count = e.touches.length;\r
+                       }\r
+                       if (count > 1) {\r
+                               return;\r
+                       }\r
+\r
+                       var now = Date.now(),\r
+                               delta = now - (last || now);\r
+\r
+                       touch = e.touches ? e.touches[0] : e;\r
+                       doubleTap = (delta > 0 && delta <= delay);\r
+                       last = now;\r
+               }\r
+\r
+               function onTouchEnd(e) {\r
+                       if (L.Browser.pointer) {\r
+                               var idx = trackedTouches.indexOf(e.pointerId);\r
+                               if (idx === -1) {\r
+                                       return;\r
+                               }\r
+                               trackedTouches.splice(idx, 1);\r
+                       }\r
+\r
+                       if (doubleTap) {\r
+                               if (L.Browser.pointer) {\r
+                                       // work around .type being readonly with MSPointer* events\r
+                                       var newTouch = { },\r
+                                               prop;\r
+\r
+                                       // jshint forin:false\r
+                                       for (var i in touch) {\r
+                                               prop = touch[i];\r
+                                               if (typeof prop === 'function') {\r
+                                                       newTouch[i] = prop.bind(touch);\r
+                                               } else {\r
+                                                       newTouch[i] = prop;\r
+                                               }\r
+                                       }\r
+                                       touch = newTouch;\r
+                               }\r
+                               touch.type = 'dblclick';\r
+                               handler(touch);\r
+                               last = null;\r
+                       }\r
+               }\r
+               obj[pre + touchstart + id] = onTouchStart;\r
+               obj[pre + touchend + id] = onTouchEnd;\r
+\r
+               // on pointer we need to listen on the document, otherwise a drag starting on the map and moving off screen\r
+               // will not come through to us, so we will lose track of how many touches are ongoing\r
+               var endElement = L.Browser.pointer ? document.documentElement : obj;\r
+\r
+               obj.addEventListener(touchstart, onTouchStart, false);\r
+               endElement.addEventListener(touchend, onTouchEnd, false);\r
+\r
+               if (L.Browser.pointer) {\r
+                       endElement.addEventListener(L.DomEvent.POINTER_CANCEL, onTouchEnd, false);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       removeDoubleTapListener: function (obj, id) {\r
+               var pre = '_leaflet_';\r
+\r
+               obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false);\r
+               (L.Browser.pointer ? document.documentElement : obj).removeEventListener(\r
+                       this._touchend, obj[pre + this._touchend + id], false);\r
+\r
+               if (L.Browser.pointer) {\r
+                       document.documentElement.removeEventListener(L.DomEvent.POINTER_CANCEL, obj[pre + this._touchend + id],\r
+                               false);\r
+               }\r
+\r
+               return this;\r
+       }\r
+});\r
+
+
+/*
+ * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
+ */
+
+L.extend(L.DomEvent, {
+
+       //static
+       POINTER_DOWN: L.Browser.msPointer ? 'MSPointerDown' : 'pointerdown',
+       POINTER_MOVE: L.Browser.msPointer ? 'MSPointerMove' : 'pointermove',
+       POINTER_UP: L.Browser.msPointer ? 'MSPointerUp' : 'pointerup',
+       POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
+
+       _pointers: [],
+       _pointerDocumentListener: false,
+
+       // Provides a touch events wrapper for (ms)pointer events.
+       // Based on changes by veproza https://github.com/CloudMade/Leaflet/pull/1019
+       //ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
+
+       addPointerListener: function (obj, type, handler, id) {
+
+               switch (type) {
+               case 'touchstart':
+                       return this.addPointerListenerStart(obj, type, handler, id);
+               case 'touchend':
+                       return this.addPointerListenerEnd(obj, type, handler, id);
+               case 'touchmove':
+                       return this.addPointerListenerMove(obj, type, handler, id);
+               default:
+                       throw 'Unknown touch event type';
+               }
+       },
+
+       addPointerListenerStart: function (obj, type, handler, id) {
+               var pre = '_leaflet_',
+                   pointers = this._pointers;
+
+               var cb = function (e) {
+
+                       L.DomEvent.preventDefault(e);
+
+                       var alreadyInArray = false;
+                       for (var i = 0; i < pointers.length; i++) {
+                               if (pointers[i].pointerId === e.pointerId) {
+                                       alreadyInArray = true;
+                                       break;
+                               }
+                       }
+                       if (!alreadyInArray) {
+                               pointers.push(e);
+                       }
+
+                       e.touches = pointers.slice();
+                       e.changedTouches = [e];
+
+                       handler(e);
+               };
+
+               obj[pre + 'touchstart' + id] = cb;
+               obj.addEventListener(this.POINTER_DOWN, cb, false);
+
+               // need to also listen for end events to keep the _pointers list accurate
+               // this needs to be on the body and never go away
+               if (!this._pointerDocumentListener) {
+                       var internalCb = function (e) {
+                               for (var i = 0; i < pointers.length; i++) {
+                                       if (pointers[i].pointerId === e.pointerId) {
+                                               pointers.splice(i, 1);
+                                               break;
+                                       }
+                               }
+                       };
+                       //We listen on the documentElement as any drags that end by moving the touch off the screen get fired there
+                       document.documentElement.addEventListener(this.POINTER_UP, internalCb, false);
+                       document.documentElement.addEventListener(this.POINTER_CANCEL, internalCb, false);
+
+                       this._pointerDocumentListener = true;
+               }
+
+               return this;
+       },
+
+       addPointerListenerMove: function (obj, type, handler, id) {
+               var pre = '_leaflet_',
+                   touches = this._pointers;
+
+               function cb(e) {
+
+                       // don't fire touch moves when mouse isn't down
+                       if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
+
+                       for (var i = 0; i < touches.length; i++) {
+                               if (touches[i].pointerId === e.pointerId) {
+                                       touches[i] = e;
+                                       break;
+                               }
+                       }
+
+                       e.touches = touches.slice();
+                       e.changedTouches = [e];
+
+                       handler(e);
+               }
+
+               obj[pre + 'touchmove' + id] = cb;
+               obj.addEventListener(this.POINTER_MOVE, cb, false);
+
+               return this;
+       },
+
+       addPointerListenerEnd: function (obj, type, handler, id) {
+               var pre = '_leaflet_',
+                   touches = this._pointers;
+
+               var cb = function (e) {
+                       for (var i = 0; i < touches.length; i++) {
+                               if (touches[i].pointerId === e.pointerId) {
+                                       touches.splice(i, 1);
+                                       break;
+                               }
+                       }
+
+                       e.touches = touches.slice();
+                       e.changedTouches = [e];
+
+                       handler(e);
+               };
+
+               obj[pre + 'touchend' + id] = cb;
+               obj.addEventListener(this.POINTER_UP, cb, false);
+               obj.addEventListener(this.POINTER_CANCEL, cb, false);
+
+               return this;
+       },
+
+       removePointerListener: function (obj, type, id) {
+               var pre = '_leaflet_',
+                   cb = obj[pre + type + id];
+
+               switch (type) {
+               case 'touchstart':
+                       obj.removeEventListener(this.POINTER_DOWN, cb, false);
+                       break;
+               case 'touchmove':
+                       obj.removeEventListener(this.POINTER_MOVE, cb, false);
+                       break;
+               case 'touchend':
+                       obj.removeEventListener(this.POINTER_UP, cb, false);
+                       obj.removeEventListener(this.POINTER_CANCEL, cb, false);
+                       break;
+               }
+
+               return this;
+       }
+});
+
+
+/*
+ * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
+ */
+
+L.Map.mergeOptions({
+       touchZoom: L.Browser.touch && !L.Browser.android23,
+       bounceAtZoomLimits: true
+});
+
+L.Map.TouchZoom = L.Handler.extend({
+       addHooks: function () {
+               L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
+       },
+
+       removeHooks: function () {
+               L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
+       },
+
+       _onTouchStart: function (e) {
+               var map = this._map;
+
+               if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
+
+               var p1 = map.mouseEventToLayerPoint(e.touches[0]),
+                   p2 = map.mouseEventToLayerPoint(e.touches[1]),
+                   viewCenter = map._getCenterLayerPoint();
+
+               this._startCenter = p1.add(p2)._divideBy(2);
+               this._startDist = p1.distanceTo(p2);
+
+               this._moved = false;
+               this._zooming = true;
+
+               this._centerOffset = viewCenter.subtract(this._startCenter);
+
+               if (map._panAnim) {
+                       map._panAnim.stop();
+               }
+
+               L.DomEvent
+                   .on(document, 'touchmove', this._onTouchMove, this)
+                   .on(document, 'touchend', this._onTouchEnd, this);
+
+               L.DomEvent.preventDefault(e);
+       },
+
+       _onTouchMove: function (e) {
+               var map = this._map;
+
+               if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
+
+               var p1 = map.mouseEventToLayerPoint(e.touches[0]),
+                   p2 = map.mouseEventToLayerPoint(e.touches[1]);
+
+               this._scale = p1.distanceTo(p2) / this._startDist;
+               this._delta = p1._add(p2)._divideBy(2)._subtract(this._startCenter);
+
+               if (this._scale === 1) { return; }
+
+               if (!map.options.bounceAtZoomLimits) {
+                       if ((map.getZoom() === map.getMinZoom() && this._scale < 1) ||
+                           (map.getZoom() === map.getMaxZoom() && this._scale > 1)) { return; }
+               }
+
+               if (!this._moved) {
+                       L.DomUtil.addClass(map._mapPane, 'leaflet-touching');
+
+                       map
+                           .fire('movestart')
+                           .fire('zoomstart');
+
+                       this._moved = true;
+               }
+
+               L.Util.cancelAnimFrame(this._animRequest);
+               this._animRequest = L.Util.requestAnimFrame(
+                       this._updateOnMove, this, true, this._map._container);
+
+               L.DomEvent.preventDefault(e);
+       },
+
+       _updateOnMove: function () {
+               var map = this._map,
+                   origin = this._getScaleOrigin(),
+                   center = map.layerPointToLatLng(origin),
+                   zoom = map.getScaleZoom(this._scale);
+
+               map._animateZoom(center, zoom, this._startCenter, this._scale, this._delta, false, true);
+       },
+
+       _onTouchEnd: function () {
+               if (!this._moved || !this._zooming) {
+                       this._zooming = false;
+                       return;
+               }
+
+               var map = this._map;
+
+               this._zooming = false;
+               L.DomUtil.removeClass(map._mapPane, 'leaflet-touching');
+               L.Util.cancelAnimFrame(this._animRequest);
+
+               L.DomEvent
+                   .off(document, 'touchmove', this._onTouchMove)
+                   .off(document, 'touchend', this._onTouchEnd);
+
+               var origin = this._getScaleOrigin(),
+                   center = map.layerPointToLatLng(origin),
+
+                   oldZoom = map.getZoom(),
+                   floatZoomDelta = map.getScaleZoom(this._scale) - oldZoom,
+                   roundZoomDelta = (floatZoomDelta > 0 ?
+                           Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)),
+
+                   zoom = map._limitZoom(oldZoom + roundZoomDelta),
+                   scale = map.getZoomScale(zoom) / this._scale;
+
+               map._animateZoom(center, zoom, origin, scale);
+       },
+
+       _getScaleOrigin: function () {
+               var centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale);
+               return this._startCenter.add(centerOffset);
+       }
+});
+
+L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
+
+
+/*
+ * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
+ */
+
+L.Map.mergeOptions({
+       tap: true,
+       tapTolerance: 15
+});
+
+L.Map.Tap = L.Handler.extend({
+       addHooks: function () {
+               L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
+       },
+
+       removeHooks: function () {
+               L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
+       },
+
+       _onDown: function (e) {
+               if (!e.touches) { return; }
+
+               L.DomEvent.preventDefault(e);
+
+               this._fireClick = true;
+
+               // don't simulate click or track longpress if more than 1 touch
+               if (e.touches.length > 1) {
+                       this._fireClick = false;
+                       clearTimeout(this._holdTimeout);
+                       return;
+               }
+
+               var first = e.touches[0],
+                   el = first.target;
+
+               this._startPos = this._newPos = new L.Point(first.clientX, first.clientY);
+
+               // if touching a link, highlight it
+               if (el.tagName && el.tagName.toLowerCase() === 'a') {
+                       L.DomUtil.addClass(el, 'leaflet-active');
+               }
+
+               // simulate long hold but setting a timeout
+               this._holdTimeout = setTimeout(L.bind(function () {
+                       if (this._isTapValid()) {
+                               this._fireClick = false;
+                               this._onUp();
+                               this._simulateEvent('contextmenu', first);
+                       }
+               }, this), 1000);
+
+               L.DomEvent
+                       .on(document, 'touchmove', this._onMove, this)
+                       .on(document, 'touchend', this._onUp, this);
+       },
+
+       _onUp: function (e) {
+               clearTimeout(this._holdTimeout);
+
+               L.DomEvent
+                       .off(document, 'touchmove', this._onMove, this)
+                       .off(document, 'touchend', this._onUp, this);
+
+               if (this._fireClick && e && e.changedTouches) {
+
+                       var first = e.changedTouches[0],
+                           el = first.target;
+
+                       if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
+                               L.DomUtil.removeClass(el, 'leaflet-active');
+                       }
+
+                       // simulate click if the touch didn't move too much
+                       if (this._isTapValid()) {
+                               this._simulateEvent('click', first);
+                       }
+               }
+       },
+
+       _isTapValid: function () {
+               return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
+       },
+
+       _onMove: function (e) {
+               var first = e.touches[0];
+               this._newPos = new L.Point(first.clientX, first.clientY);
+       },
+
+       _simulateEvent: function (type, e) {
+               var simulatedEvent = document.createEvent('MouseEvents');
+
+               simulatedEvent._simulated = true;
+               e.target._simulatedClick = true;
+
+               simulatedEvent.initMouseEvent(
+                       type, true, true, window, 1,
+                       e.screenX, e.screenY,
+                       e.clientX, e.clientY,
+                       false, false, false, false, 0, null);
+
+               e.target.dispatchEvent(simulatedEvent);
+       }
+});
+
+if (L.Browser.touch && !L.Browser.pointer) {
+       L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);
+}
+
+
+/*
+ * L.Handler.ShiftDragZoom is used to add shift-drag zoom interaction to the map
+  * (zoom to a selected bounding box), enabled by default.
+ */
+
+L.Map.mergeOptions({
+       boxZoom: true
+});
+
+L.Map.BoxZoom = L.Handler.extend({
+       initialize: function (map) {
+               this._map = map;
+               this._container = map._container;
+               this._pane = map._panes.overlayPane;
+               this._moved = false;
+       },
+
+       addHooks: function () {
+               L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
+       },
+
+       removeHooks: function () {
+               L.DomEvent.off(this._container, 'mousedown', this._onMouseDown);
+               this._moved = false;
+       },
+
+       moved: function () {
+               return this._moved;
+       },
+
+       _onMouseDown: function (e) {
+               this._moved = false;
+
+               if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
+
+               L.DomUtil.disableTextSelection();
+               L.DomUtil.disableImageDrag();
+
+               this._startLayerPoint = this._map.mouseEventToLayerPoint(e);
+
+               L.DomEvent
+                   .on(document, 'mousemove', this._onMouseMove, this)
+                   .on(document, 'mouseup', this._onMouseUp, this)
+                   .on(document, 'keydown', this._onKeyDown, this);
+       },
+
+       _onMouseMove: function (e) {
+               if (!this._moved) {
+                       this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane);
+                       L.DomUtil.setPosition(this._box, this._startLayerPoint);
+
+                       //TODO refactor: move cursor to styles
+                       this._container.style.cursor = 'crosshair';
+                       this._map.fire('boxzoomstart');
+               }
+
+               var startPoint = this._startLayerPoint,
+                   box = this._box,
+
+                   layerPoint = this._map.mouseEventToLayerPoint(e),
+                   offset = layerPoint.subtract(startPoint),
+
+                   newPos = new L.Point(
+                       Math.min(layerPoint.x, startPoint.x),
+                       Math.min(layerPoint.y, startPoint.y));
+
+               L.DomUtil.setPosition(box, newPos);
+
+               this._moved = true;
+
+               // TODO refactor: remove hardcoded 4 pixels
+               box.style.width  = (Math.max(0, Math.abs(offset.x) - 4)) + 'px';
+               box.style.height = (Math.max(0, Math.abs(offset.y) - 4)) + 'px';
+       },
+
+       _finish: function () {
+               if (this._moved) {
+                       this._pane.removeChild(this._box);
+                       this._container.style.cursor = '';
+               }
+
+               L.DomUtil.enableTextSelection();
+               L.DomUtil.enableImageDrag();
+
+               L.DomEvent
+                   .off(document, 'mousemove', this._onMouseMove)
+                   .off(document, 'mouseup', this._onMouseUp)
+                   .off(document, 'keydown', this._onKeyDown);
+       },
+
+       _onMouseUp: function (e) {
+
+               this._finish();
+
+               var map = this._map,
+                   layerPoint = map.mouseEventToLayerPoint(e);
+
+               if (this._startLayerPoint.equals(layerPoint)) { return; }
+
+               var bounds = new L.LatLngBounds(
+                       map.layerPointToLatLng(this._startLayerPoint),
+                       map.layerPointToLatLng(layerPoint));
+
+               map.fitBounds(bounds);
+
+               map.fire('boxzoomend', {
+                       boxZoomBounds: bounds
+               });
+       },
+
+       _onKeyDown: function (e) {
+               if (e.keyCode === 27) {
+                       this._finish();
+               }
+       }
+});
+
+L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
+
+
+/*
+ * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
+ */
+
+L.Map.mergeOptions({
+       keyboard: true,
+       keyboardPanOffset: 80,
+       keyboardZoomOffset: 1
+});
+
+L.Map.Keyboard = L.Handler.extend({
+
+       keyCodes: {
+               left:    [37],
+               right:   [39],
+               down:    [40],
+               up:      [38],
+               zoomIn:  [187, 107, 61, 171],
+               zoomOut: [189, 109, 173]
+       },
+
+       initialize: function (map) {
+               this._map = map;
+
+               this._setPanOffset(map.options.keyboardPanOffset);
+               this._setZoomOffset(map.options.keyboardZoomOffset);
+       },
+
+       addHooks: function () {
+               var container = this._map._container;
+
+               // make the container focusable by tabbing
+               if (container.tabIndex === -1) {
+                       container.tabIndex = '0';
+               }
+
+               L.DomEvent
+                   .on(container, 'focus', this._onFocus, this)
+                   .on(container, 'blur', this._onBlur, this)
+                   .on(container, 'mousedown', this._onMouseDown, this);
+
+               this._map
+                   .on('focus', this._addHooks, this)
+                   .on('blur', this._removeHooks, this);
+       },
+
+       removeHooks: function () {
+               this._removeHooks();
+
+               var container = this._map._container;
+
+               L.DomEvent
+                   .off(container, 'focus', this._onFocus, this)
+                   .off(container, 'blur', this._onBlur, this)
+                   .off(container, 'mousedown', this._onMouseDown, this);
+
+               this._map
+                   .off('focus', this._addHooks, this)
+                   .off('blur', this._removeHooks, this);
+       },
+
+       _onMouseDown: function () {
+               if (this._focused) { return; }
+
+               var body = document.body,
+                   docEl = document.documentElement,
+                   top = body.scrollTop || docEl.scrollTop,
+                   left = body.scrollLeft || docEl.scrollLeft;
+
+               this._map._container.focus();
+
+               window.scrollTo(left, top);
+       },
+
+       _onFocus: function () {
+               this._focused = true;
+               this._map.fire('focus');
+       },
+
+       _onBlur: function () {
+               this._focused = false;
+               this._map.fire('blur');
+       },
+
+       _setPanOffset: function (pan) {
+               var keys = this._panKeys = {},
+                   codes = this.keyCodes,
+                   i, len;
+
+               for (i = 0, len = codes.left.length; i < len; i++) {
+                       keys[codes.left[i]] = [-1 * pan, 0];
+               }
+               for (i = 0, len = codes.right.length; i < len; i++) {
+                       keys[codes.right[i]] = [pan, 0];
+               }
+               for (i = 0, len = codes.down.length; i < len; i++) {
+                       keys[codes.down[i]] = [0, pan];
+               }
+               for (i = 0, len = codes.up.length; i < len; i++) {
+                       keys[codes.up[i]] = [0, -1 * pan];
+               }
+       },
+
+       _setZoomOffset: function (zoom) {
+               var keys = this._zoomKeys = {},
+                   codes = this.keyCodes,
+                   i, len;
+
+               for (i = 0, len = codes.zoomIn.length; i < len; i++) {
+                       keys[codes.zoomIn[i]] = zoom;
+               }
+               for (i = 0, len = codes.zoomOut.length; i < len; i++) {
+                       keys[codes.zoomOut[i]] = -zoom;
+               }
+       },
+
+       _addHooks: function () {
+               L.DomEvent.on(document, 'keydown', this._onKeyDown, this);
+       },
+
+       _removeHooks: function () {
+               L.DomEvent.off(document, 'keydown', this._onKeyDown, this);
+       },
+
+       _onKeyDown: function (e) {
+               var key = e.keyCode,
+                   map = this._map;
+
+               if (key in this._panKeys) {
+
+                       if (map._panAnim && map._panAnim._inProgress) { return; }
+
+                       map.panBy(this._panKeys[key]);
+
+                       if (map.options.maxBounds) {
+                               map.panInsideBounds(map.options.maxBounds);
+                       }
+
+               } else if (key in this._zoomKeys) {
+                       map.setZoom(map.getZoom() + this._zoomKeys[key]);
+
+               } else {
+                       return;
+               }
+
+               L.DomEvent.stop(e);
+       }
+});
+
+L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
+
+
+/*
+ * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
+ */
+
+L.Handler.MarkerDrag = L.Handler.extend({
+       initialize: function (marker) {
+               this._marker = marker;
+       },
+
+       addHooks: function () {
+               var icon = this._marker._icon;
+               if (!this._draggable) {
+                       this._draggable = new L.Draggable(icon, icon);
+               }
+
+               this._draggable
+                       .on('dragstart', this._onDragStart, this)
+                       .on('drag', this._onDrag, this)
+                       .on('dragend', this._onDragEnd, this);
+               this._draggable.enable();
+               L.DomUtil.addClass(this._marker._icon, 'leaflet-marker-draggable');
+       },
+
+       removeHooks: function () {
+               this._draggable
+                       .off('dragstart', this._onDragStart, this)
+                       .off('drag', this._onDrag, this)
+                       .off('dragend', this._onDragEnd, this);
+
+               this._draggable.disable();
+               L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable');
+       },
+
+       moved: function () {
+               return this._draggable && this._draggable._moved;
+       },
+
+       _onDragStart: function () {
+               this._marker
+                   .closePopup()
+                   .fire('movestart')
+                   .fire('dragstart');
+       },
+
+       _onDrag: function () {
+               var marker = this._marker,
+                   shadow = marker._shadow,
+                   iconPos = L.DomUtil.getPosition(marker._icon),
+                   latlng = marker._map.layerPointToLatLng(iconPos);
+
+               // update shadow position
+               if (shadow) {
+                       L.DomUtil.setPosition(shadow, iconPos);
+               }
+
+               marker._latlng = latlng;
+
+               marker
+                   .fire('move', {latlng: latlng})
+                   .fire('drag');
+       },
+
+       _onDragEnd: function (e) {
+               this._marker
+                   .fire('moveend')
+                   .fire('dragend', e);
+       }
+});
+
+
+/*\r
+ * L.Control is a base class for implementing map controls. Handles positioning.\r
+ * All other controls extend from this class.\r
+ */\r
+\r
+L.Control = L.Class.extend({\r
+       options: {\r
+               position: 'topright'\r
+       },\r
+\r
+       initialize: function (options) {\r
+               L.setOptions(this, options);\r
+       },\r
+\r
+       getPosition: function () {\r
+               return this.options.position;\r
+       },\r
+\r
+       setPosition: function (position) {\r
+               var map = this._map;\r
+\r
+               if (map) {\r
+                       map.removeControl(this);\r
+               }\r
+\r
+               this.options.position = position;\r
+\r
+               if (map) {\r
+                       map.addControl(this);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       getContainer: function () {\r
+               return this._container;\r
+       },\r
+\r
+       addTo: function (map) {\r
+               this._map = map;\r
+\r
+               var container = this._container = this.onAdd(map),\r
+                   pos = this.getPosition(),\r
+                   corner = map._controlCorners[pos];\r
+\r
+               L.DomUtil.addClass(container, 'leaflet-control');\r
+\r
+               if (pos.indexOf('bottom') !== -1) {\r
+                       corner.insertBefore(container, corner.firstChild);\r
+               } else {\r
+                       corner.appendChild(container);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       removeFrom: function (map) {\r
+               var pos = this.getPosition(),\r
+                   corner = map._controlCorners[pos];\r
+\r
+               corner.removeChild(this._container);\r
+               this._map = null;\r
+\r
+               if (this.onRemove) {\r
+                       this.onRemove(map);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       _refocusOnMap: function () {\r
+               if (this._map) {\r
+                       this._map.getContainer().focus();\r
+               }\r
+       }\r
+});\r
+\r
+L.control = function (options) {\r
+       return new L.Control(options);\r
+};\r
+\r
+\r
+// adds control-related methods to L.Map\r
+\r
+L.Map.include({\r
+       addControl: function (control) {\r
+               control.addTo(this);\r
+               return this;\r
+       },\r
+\r
+       removeControl: function (control) {\r
+               control.removeFrom(this);\r
+               return this;\r
+       },\r
+\r
+       _initControlPos: function () {\r
+               var corners = this._controlCorners = {},\r
+                   l = 'leaflet-',\r
+                   container = this._controlContainer =\r
+                           L.DomUtil.create('div', l + 'control-container', this._container);\r
+\r
+               function createCorner(vSide, hSide) {\r
+                       var className = l + vSide + ' ' + l + hSide;\r
+\r
+                       corners[vSide + hSide] = L.DomUtil.create('div', className, container);\r
+               }\r
+\r
+               createCorner('top', 'left');\r
+               createCorner('top', 'right');\r
+               createCorner('bottom', 'left');\r
+               createCorner('bottom', 'right');\r
+       },\r
+\r
+       _clearControlPos: function () {\r
+               this._container.removeChild(this._controlContainer);\r
+       }\r
+});\r
+
+
+/*\r
+ * L.Control.Zoom is used for the default zoom buttons on the map.\r
+ */\r
+\r
+L.Control.Zoom = L.Control.extend({\r
+       options: {\r
+               position: 'topleft',\r
+               zoomInText: '+',\r
+               zoomInTitle: 'Zoom in',\r
+               zoomOutText: '-',\r
+               zoomOutTitle: 'Zoom out'\r
+       },\r
+\r
+       onAdd: function (map) {\r
+               var zoomName = 'leaflet-control-zoom',\r
+                   container = L.DomUtil.create('div', zoomName + ' leaflet-bar');\r
+\r
+               this._map = map;\r
+\r
+               this._zoomInButton  = this._createButton(\r
+                       this.options.zoomInText, this.options.zoomInTitle,\r
+                       zoomName + '-in',  container, this._zoomIn,  this);\r
+               this._zoomOutButton = this._createButton(\r
+                       this.options.zoomOutText, this.options.zoomOutTitle,\r
+                       zoomName + '-out', container, this._zoomOut, this);\r
+\r
+               this._updateDisabled();\r
+               map.on('zoomend zoomlevelschange', this._updateDisabled, this);\r
+\r
+               return container;\r
+       },\r
+\r
+       onRemove: function (map) {\r
+               map.off('zoomend zoomlevelschange', this._updateDisabled, this);\r
+       },\r
+\r
+       _zoomIn: function (e) {\r
+               this._map.zoomIn(e.shiftKey ? 3 : 1);\r
+       },\r
+\r
+       _zoomOut: function (e) {\r
+               this._map.zoomOut(e.shiftKey ? 3 : 1);\r
+       },\r
+\r
+       _createButton: function (html, title, className, container, fn, context) {\r
+               var link = L.DomUtil.create('a', className, container);\r
+               link.innerHTML = html;\r
+               link.href = '#';\r
+               link.title = title;\r
+\r
+               var stop = L.DomEvent.stopPropagation;\r
+\r
+               L.DomEvent\r
+                   .on(link, 'click', stop)\r
+                   .on(link, 'mousedown', stop)\r
+                   .on(link, 'dblclick', stop)\r
+                   .on(link, 'click', L.DomEvent.preventDefault)\r
+                   .on(link, 'click', fn, context)\r
+                   .on(link, 'click', this._refocusOnMap, context);\r
+\r
+               return link;\r
+       },\r
+\r
+       _updateDisabled: function () {\r
+               var map = this._map,\r
+                       className = 'leaflet-disabled';\r
+\r
+               L.DomUtil.removeClass(this._zoomInButton, className);\r
+               L.DomUtil.removeClass(this._zoomOutButton, className);\r
+\r
+               if (map._zoom === map.getMinZoom()) {\r
+                       L.DomUtil.addClass(this._zoomOutButton, className);\r
+               }\r
+               if (map._zoom === map.getMaxZoom()) {\r
+                       L.DomUtil.addClass(this._zoomInButton, className);\r
+               }\r
+       }\r
+});\r
+\r
+L.Map.mergeOptions({\r
+       zoomControl: true\r
+});\r
+\r
+L.Map.addInitHook(function () {\r
+       if (this.options.zoomControl) {\r
+               this.zoomControl = new L.Control.Zoom();\r
+               this.addControl(this.zoomControl);\r
+       }\r
+});\r
+\r
+L.control.zoom = function (options) {\r
+       return new L.Control.Zoom(options);\r
+};\r
+\r
+
+
+/*\r
+ * L.Control.Attribution is used for displaying attribution on the map (added by default).\r
+ */\r
+\r
+L.Control.Attribution = L.Control.extend({\r
+       options: {\r
+               position: 'bottomright',\r
+               prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'\r
+       },\r
+\r
+       initialize: function (options) {\r
+               L.setOptions(this, options);\r
+\r
+               this._attributions = {};\r
+       },\r
+\r
+       onAdd: function (map) {\r
+               this._container = L.DomUtil.create('div', 'leaflet-control-attribution');\r
+               L.DomEvent.disableClickPropagation(this._container);\r
+\r
+               for (var i in map._layers) {\r
+                       if (map._layers[i].getAttribution) {\r
+                               this.addAttribution(map._layers[i].getAttribution());\r
+                       }\r
+               }\r
+               \r
+               map\r
+                   .on('layeradd', this._onLayerAdd, this)\r
+                   .on('layerremove', this._onLayerRemove, this);\r
+\r
+               this._update();\r
+\r
+               return this._container;\r
+       },\r
+\r
+       onRemove: function (map) {\r
+               map\r
+                   .off('layeradd', this._onLayerAdd)\r
+                   .off('layerremove', this._onLayerRemove);\r
+\r
+       },\r
+\r
+       setPrefix: function (prefix) {\r
+               this.options.prefix = prefix;\r
+               this._update();\r
+               return this;\r
+       },\r
+\r
+       addAttribution: function (text) {\r
+               if (!text) { return; }\r
+\r
+               if (!this._attributions[text]) {\r
+                       this._attributions[text] = 0;\r
+               }\r
+               this._attributions[text]++;\r
+\r
+               this._update();\r
+\r
+               return this;\r
+       },\r
+\r
+       removeAttribution: function (text) {\r
+               if (!text) { return; }\r
+\r
+               if (this._attributions[text]) {\r
+                       this._attributions[text]--;\r
+                       this._update();\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       _update: function () {\r
+               if (!this._map) { return; }\r
+\r
+               var attribs = [];\r
+\r
+               for (var i in this._attributions) {\r
+                       if (this._attributions[i]) {\r
+                               attribs.push(i);\r
+                       }\r
+               }\r
+\r
+               var prefixAndAttribs = [];\r
+\r
+               if (this.options.prefix) {\r
+                       prefixAndAttribs.push(this.options.prefix);\r
+               }\r
+               if (attribs.length) {\r
+                       prefixAndAttribs.push(attribs.join(', '));\r
+               }\r
+\r
+               this._container.innerHTML = prefixAndAttribs.join(' | ');\r
+       },\r
+\r
+       _onLayerAdd: function (e) {\r
+               if (e.layer.getAttribution) {\r
+                       this.addAttribution(e.layer.getAttribution());\r
+               }\r
+       },\r
+\r
+       _onLayerRemove: function (e) {\r
+               if (e.layer.getAttribution) {\r
+                       this.removeAttribution(e.layer.getAttribution());\r
+               }\r
+       }\r
+});\r
+\r
+L.Map.mergeOptions({\r
+       attributionControl: true\r
+});\r
+\r
+L.Map.addInitHook(function () {\r
+       if (this.options.attributionControl) {\r
+               this.attributionControl = (new L.Control.Attribution()).addTo(this);\r
+       }\r
+});\r
+\r
+L.control.attribution = function (options) {\r
+       return new L.Control.Attribution(options);\r
+};\r
+
+
+/*
+ * L.Control.Scale is used for displaying metric/imperial scale on the map.
+ */
+
+L.Control.Scale = L.Control.extend({
+       options: {
+               position: 'bottomleft',
+               maxWidth: 100,
+               metric: true,
+               imperial: true,
+               updateWhenIdle: false
+       },
+
+       onAdd: function (map) {
+               this._map = map;
+
+               var className = 'leaflet-control-scale',
+                   container = L.DomUtil.create('div', className),
+                   options = this.options;
+
+               this._addScales(options, className, container);
+
+               map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
+               map.whenReady(this._update, this);
+
+               return container;
+       },
+
+       onRemove: function (map) {
+               map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
+       },
+
+       _addScales: function (options, className, container) {
+               if (options.metric) {
+                       this._mScale = L.DomUtil.create('div', className + '-line', container);
+               }
+               if (options.imperial) {
+                       this._iScale = L.DomUtil.create('div', className + '-line', container);
+               }
+       },
+
+       _update: function () {
+               var bounds = this._map.getBounds(),
+                   centerLat = bounds.getCenter().lat,
+                   halfWorldMeters = 6378137 * Math.PI * Math.cos(centerLat * Math.PI / 180),
+                   dist = halfWorldMeters * (bounds.getNorthEast().lng - bounds.getSouthWest().lng) / 180,
+
+                   size = this._map.getSize(),
+                   options = this.options,
+                   maxMeters = 0;
+
+               if (size.x > 0) {
+                       maxMeters = dist * (options.maxWidth / size.x);
+               }
+
+               this._updateScales(options, maxMeters);
+       },
+
+       _updateScales: function (options, maxMeters) {
+               if (options.metric && maxMeters) {
+                       this._updateMetric(maxMeters);
+               }
+
+               if (options.imperial && maxMeters) {
+                       this._updateImperial(maxMeters);
+               }
+       },
+
+       _updateMetric: function (maxMeters) {
+               var meters = this._getRoundNum(maxMeters);
+
+               this._mScale.style.width = this._getScaleWidth(meters / maxMeters) + 'px';
+               this._mScale.innerHTML = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
+       },
+
+       _updateImperial: function (maxMeters) {
+               var maxFeet = maxMeters * 3.2808399,
+                   scale = this._iScale,
+                   maxMiles, miles, feet;
+
+               if (maxFeet > 5280) {
+                       maxMiles = maxFeet / 5280;
+                       miles = this._getRoundNum(maxMiles);
+
+                       scale.style.width = this._getScaleWidth(miles / maxMiles) + 'px';
+                       scale.innerHTML = miles + ' mi';
+
+               } else {
+                       feet = this._getRoundNum(maxFeet);
+
+                       scale.style.width = this._getScaleWidth(feet / maxFeet) + 'px';
+                       scale.innerHTML = feet + ' ft';
+               }
+       },
+
+       _getScaleWidth: function (ratio) {
+               return Math.round(this.options.maxWidth * ratio) - 10;
+       },
+
+       _getRoundNum: function (num) {
+               var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
+                   d = num / pow10;
+
+               d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1;
+
+               return pow10 * d;
+       }
+});
+
+L.control.scale = function (options) {
+       return new L.Control.Scale(options);
+};
+
+
+/*\r
+ * L.Control.Layers is a control to allow users to switch between different layers on the map.\r
+ */\r
+\r
+L.Control.Layers = L.Control.extend({\r
+       options: {\r
+               collapsed: true,\r
+               position: 'topright',\r
+               autoZIndex: true\r
+       },\r
+\r
+       initialize: function (baseLayers, overlays, options) {\r
+               L.setOptions(this, options);\r
+\r
+               this._layers = {};\r
+               this._lastZIndex = 0;\r
+               this._handlingClick = false;\r
+\r
+               for (var i in baseLayers) {\r
+                       this._addLayer(baseLayers[i], i);\r
+               }\r
+\r
+               for (i in overlays) {\r
+                       this._addLayer(overlays[i], i, true);\r
+               }\r
+       },\r
+\r
+       onAdd: function (map) {\r
+               this._initLayout();\r
+               this._update();\r
+\r
+               map\r
+                   .on('layeradd', this._onLayerChange, this)\r
+                   .on('layerremove', this._onLayerChange, this);\r
+\r
+               return this._container;\r
+       },\r
+\r
+       onRemove: function (map) {\r
+               map\r
+                   .off('layeradd', this._onLayerChange, this)\r
+                   .off('layerremove', this._onLayerChange, this);\r
+       },\r
+\r
+       addBaseLayer: function (layer, name) {\r
+               this._addLayer(layer, name);\r
+               this._update();\r
+               return this;\r
+       },\r
+\r
+       addOverlay: function (layer, name) {\r
+               this._addLayer(layer, name, true);\r
+               this._update();\r
+               return this;\r
+       },\r
+\r
+       removeLayer: function (layer) {\r
+               var id = L.stamp(layer);\r
+               delete this._layers[id];\r
+               this._update();\r
+               return this;\r
+       },\r
+\r
+       _initLayout: function () {\r
+               var className = 'leaflet-control-layers',\r
+                   container = this._container = L.DomUtil.create('div', className);\r
+\r
+               //Makes this work on IE10 Touch devices by stopping it from firing a mouseout event when the touch is released\r
+               container.setAttribute('aria-haspopup', true);\r
+\r
+               if (!L.Browser.touch) {\r
+                       L.DomEvent\r
+                               .disableClickPropagation(container)\r
+                               .disableScrollPropagation(container);\r
+               } else {\r
+                       L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);\r
+               }\r
+\r
+               var form = this._form = L.DomUtil.create('form', className + '-list');\r
+\r
+               if (this.options.collapsed) {\r
+                       if (!L.Browser.android) {\r
+                               L.DomEvent\r
+                                   .on(container, 'mouseover', this._expand, this)\r
+                                   .on(container, 'mouseout', this._collapse, this);\r
+                       }\r
+                       var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);\r
+                       link.href = '#';\r
+                       link.title = 'Layers';\r
+\r
+                       if (L.Browser.touch) {\r
+                               L.DomEvent\r
+                                   .on(link, 'click', L.DomEvent.stop)\r
+                                   .on(link, 'click', this._expand, this);\r
+                       }\r
+                       else {\r
+                               L.DomEvent.on(link, 'focus', this._expand, this);\r
+                       }\r
+                       //Work around for Firefox android issue https://github.com/Leaflet/Leaflet/issues/2033\r
+                       L.DomEvent.on(form, 'click', function () {\r
+                               setTimeout(L.bind(this._onInputClick, this), 0);\r
+                       }, this);\r
+\r
+                       this._map.on('click', this._collapse, this);\r
+                       // TODO keyboard accessibility\r
+               } else {\r
+                       this._expand();\r
+               }\r
+\r
+               this._baseLayersList = L.DomUtil.create('div', className + '-base', form);\r
+               this._separator = L.DomUtil.create('div', className + '-separator', form);\r
+               this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);\r
+\r
+               container.appendChild(form);\r
+       },\r
+\r
+       _addLayer: function (layer, name, overlay) {\r
+               var id = L.stamp(layer);\r
+\r
+               this._layers[id] = {\r
+                       layer: layer,\r
+                       name: name,\r
+                       overlay: overlay\r
+               };\r
+\r
+               if (this.options.autoZIndex && layer.setZIndex) {\r
+                       this._lastZIndex++;\r
+                       layer.setZIndex(this._lastZIndex);\r
+               }\r
+       },\r
+\r
+       _update: function () {\r
+               if (!this._container) {\r
+                       return;\r
+               }\r
+\r
+               this._baseLayersList.innerHTML = '';\r
+               this._overlaysList.innerHTML = '';\r
+\r
+               var baseLayersPresent = false,\r
+                   overlaysPresent = false,\r
+                   i, obj;\r
+\r
+               for (i in this._layers) {\r
+                       obj = this._layers[i];\r
+                       this._addItem(obj);\r
+                       overlaysPresent = overlaysPresent || obj.overlay;\r
+                       baseLayersPresent = baseLayersPresent || !obj.overlay;\r
+               }\r
+\r
+               this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';\r
+       },\r
+\r
+       _onLayerChange: function (e) {\r
+               var obj = this._layers[L.stamp(e.layer)];\r
+\r
+               if (!obj) { return; }\r
+\r
+               if (!this._handlingClick) {\r
+                       this._update();\r
+               }\r
+\r
+               var type = obj.overlay ?\r
+                       (e.type === 'layeradd' ? 'overlayadd' : 'overlayremove') :\r
+                       (e.type === 'layeradd' ? 'baselayerchange' : null);\r
+\r
+               if (type) {\r
+                       this._map.fire(type, obj);\r
+               }\r
+       },\r
+\r
+       // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)\r
+       _createRadioElement: function (name, checked) {\r
+\r
+               var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + name + '"';\r
+               if (checked) {\r
+                       radioHtml += ' checked="checked"';\r
+               }\r
+               radioHtml += '/>';\r
+\r
+               var radioFragment = document.createElement('div');\r
+               radioFragment.innerHTML = radioHtml;\r
+\r
+               return radioFragment.firstChild;\r
+       },\r
+\r
+       _addItem: function (obj) {\r
+               var label = document.createElement('label'),\r
+                   input,\r
+                   checked = this._map.hasLayer(obj.layer);\r
+\r
+               if (obj.overlay) {\r
+                       input = document.createElement('input');\r
+                       input.type = 'checkbox';\r
+                       input.className = 'leaflet-control-layers-selector';\r
+                       input.defaultChecked = checked;\r
+               } else {\r
+                       input = this._createRadioElement('leaflet-base-layers', checked);\r
+               }\r
+\r
+               input.layerId = L.stamp(obj.layer);\r
+\r
+               L.DomEvent.on(input, 'click', this._onInputClick, this);\r
+\r
+               var name = document.createElement('span');\r
+               name.innerHTML = ' ' + obj.name;\r
+\r
+               label.appendChild(input);\r
+               label.appendChild(name);\r
+\r
+               var container = obj.overlay ? this._overlaysList : this._baseLayersList;\r
+               container.appendChild(label);\r
+\r
+               return label;\r
+       },\r
+\r
+       _onInputClick: function () {\r
+               var i, input, obj,\r
+                   inputs = this._form.getElementsByTagName('input'),\r
+                   inputsLen = inputs.length;\r
+\r
+               this._handlingClick = true;\r
+\r
+               for (i = 0; i < inputsLen; i++) {\r
+                       input = inputs[i];\r
+                       obj = this._layers[input.layerId];\r
+\r
+                       if (input.checked && !this._map.hasLayer(obj.layer)) {\r
+                               this._map.addLayer(obj.layer);\r
+\r
+                       } else if (!input.checked && this._map.hasLayer(obj.layer)) {\r
+                               this._map.removeLayer(obj.layer);\r
+                       }\r
+               }\r
+\r
+               this._handlingClick = false;\r
+\r
+               this._refocusOnMap();\r
+       },\r
+\r
+       _expand: function () {\r
+               L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');\r
+       },\r
+\r
+       _collapse: function () {\r
+               this._container.className = this._container.className.replace(' leaflet-control-layers-expanded', '');\r
+       }\r
+});\r
+\r
+L.control.layers = function (baseLayers, overlays, options) {\r
+       return new L.Control.Layers(baseLayers, overlays, options);\r
+};\r
+
+
+/*
+ * L.PosAnimation is used by Leaflet internally for pan animations.
+ */
+
+L.PosAnimation = L.Class.extend({
+       includes: L.Mixin.Events,
+
+       run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number])
+               this.stop();
+
+               this._el = el;
+               this._inProgress = true;
+               this._newPos = newPos;
+
+               this.fire('start');
+
+               el.style[L.DomUtil.TRANSITION] = 'all ' + (duration || 0.25) +
+                       's cubic-bezier(0,0,' + (easeLinearity || 0.5) + ',1)';
+
+               L.DomEvent.on(el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this);
+               L.DomUtil.setPosition(el, newPos);
+
+               // toggle reflow, Chrome flickers for some reason if you don't do this
+               L.Util.falseFn(el.offsetWidth);
+
+               // there's no native way to track value updates of transitioned properties, so we imitate this
+               this._stepTimer = setInterval(L.bind(this._onStep, this), 50);
+       },
+
+       stop: function () {
+               if (!this._inProgress) { return; }
+
+               // if we just removed the transition property, the element would jump to its final position,
+               // so we need to make it stay at the current position
+
+               L.DomUtil.setPosition(this._el, this._getPos());
+               this._onTransitionEnd();
+               L.Util.falseFn(this._el.offsetWidth); // force reflow in case we are about to start a new animation
+       },
+
+       _onStep: function () {
+               var stepPos = this._getPos();
+               if (!stepPos) {
+                       this._onTransitionEnd();
+                       return;
+               }
+               // jshint camelcase: false
+               // make L.DomUtil.getPosition return intermediate position value during animation
+               this._el._leaflet_pos = stepPos;
+
+               this.fire('step');
+       },
+
+       // you can't easily get intermediate values of properties animated with CSS3 Transitions,
+       // we need to parse computed style (in case of transform it returns matrix string)
+
+       _transformRe: /([-+]?(?:\d*\.)?\d+)\D*, ([-+]?(?:\d*\.)?\d+)\D*\)/,
+
+       _getPos: function () {
+               var left, top, matches,
+                   el = this._el,
+                   style = window.getComputedStyle(el);
+
+               if (L.Browser.any3d) {
+                       matches = style[L.DomUtil.TRANSFORM].match(this._transformRe);
+                       if (!matches) { return; }
+                       left = parseFloat(matches[1]);
+                       top  = parseFloat(matches[2]);
+               } else {
+                       left = parseFloat(style.left);
+                       top  = parseFloat(style.top);
+               }
+
+               return new L.Point(left, top, true);
+       },
+
+       _onTransitionEnd: function () {
+               L.DomEvent.off(this._el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this);
+
+               if (!this._inProgress) { return; }
+               this._inProgress = false;
+
+               this._el.style[L.DomUtil.TRANSITION] = '';
+
+               // jshint camelcase: false
+               // make sure L.DomUtil.getPosition returns the final position value after animation
+               this._el._leaflet_pos = this._newPos;
+
+               clearInterval(this._stepTimer);
+
+               this.fire('step').fire('end');
+       }
+
+});
+
+
+/*
+ * Extends L.Map to handle panning animations.
+ */
+
+L.Map.include({
+
+       setView: function (center, zoom, options) {
+
+               zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
+               center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
+               options = options || {};
+
+               if (this._panAnim) {
+                       this._panAnim.stop();
+               }
+
+               if (this._loaded && !options.reset && options !== true) {
+
+                       if (options.animate !== undefined) {
+                               options.zoom = L.extend({animate: options.animate}, options.zoom);
+                               options.pan = L.extend({animate: options.animate}, options.pan);
+                       }
+
+                       // try animating pan or zoom
+                       var animated = (this._zoom !== zoom) ?
+                               this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
+                               this._tryAnimatedPan(center, options.pan);
+
+                       if (animated) {
+                               // prevent resize handler call, the view will refresh after animation anyway
+                               clearTimeout(this._sizeTimer);
+                               return this;
+                       }
+               }
+
+               // animation didn't start, just reset the map view
+               this._resetView(center, zoom);
+
+               return this;
+       },
+
+       panBy: function (offset, options) {
+               offset = L.point(offset).round();
+               options = options || {};
+
+               if (!offset.x && !offset.y) {
+                       return this;
+               }
+
+               if (!this._panAnim) {
+                       this._panAnim = new L.PosAnimation();
+
+                       this._panAnim.on({
+                               'step': this._onPanTransitionStep,
+                               'end': this._onPanTransitionEnd
+                       }, this);
+               }
+
+               // don't fire movestart if animating inertia
+               if (!options.noMoveStart) {
+                       this.fire('movestart');
+               }
+
+               // animate pan unless animate: false specified
+               if (options.animate !== false) {
+                       L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
+
+                       var newPos = this._getMapPanePos().subtract(offset);
+                       this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
+               } else {
+                       this._rawPanBy(offset);
+                       this.fire('move').fire('moveend');
+               }
+
+               return this;
+       },
+
+       _onPanTransitionStep: function () {
+               this.fire('move');
+       },
+
+       _onPanTransitionEnd: function () {
+               L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
+               this.fire('moveend');
+       },
+
+       _tryAnimatedPan: function (center, options) {
+               // difference between the new and current centers in pixels
+               var offset = this._getCenterOffset(center)._floor();
+
+               // don't animate too far unless animate: true specified in options
+               if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
+
+               this.panBy(offset, options);
+
+               return true;
+       }
+});
+
+
+/*
+ * L.PosAnimation fallback implementation that powers Leaflet pan animations
+ * in browsers that don't support CSS3 Transitions.
+ */
+
+L.PosAnimation = L.DomUtil.TRANSITION ? L.PosAnimation : L.PosAnimation.extend({
+
+       run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number])
+               this.stop();
+
+               this._el = el;
+               this._inProgress = true;
+               this._duration = duration || 0.25;
+               this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
+
+               this._startPos = L.DomUtil.getPosition(el);
+               this._offset = newPos.subtract(this._startPos);
+               this._startTime = +new Date();
+
+               this.fire('start');
+
+               this._animate();
+       },
+
+       stop: function () {
+               if (!this._inProgress) { return; }
+
+               this._step();
+               this._complete();
+       },
+
+       _animate: function () {
+               // animation loop
+               this._animId = L.Util.requestAnimFrame(this._animate, this);
+               this._step();
+       },
+
+       _step: function () {
+               var elapsed = (+new Date()) - this._startTime,
+                   duration = this._duration * 1000;
+
+               if (elapsed < duration) {
+                       this._runFrame(this._easeOut(elapsed / duration));
+               } else {
+                       this._runFrame(1);
+                       this._complete();
+               }
+       },
+
+       _runFrame: function (progress) {
+               var pos = this._startPos.add(this._offset.multiplyBy(progress));
+               L.DomUtil.setPosition(this._el, pos);
+
+               this.fire('step');
+       },
+
+       _complete: function () {
+               L.Util.cancelAnimFrame(this._animId);
+
+               this._inProgress = false;
+               this.fire('end');
+       },
+
+       _easeOut: function (t) {
+               return 1 - Math.pow(1 - t, this._easeOutPower);
+       }
+});
+
+
+/*
+ * Extends L.Map to handle zoom animations.
+ */
+
+L.Map.mergeOptions({
+       zoomAnimation: true,
+       zoomAnimationThreshold: 4
+});
+
+if (L.DomUtil.TRANSITION) {
+
+       L.Map.addInitHook(function () {
+               // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
+               this._zoomAnimated = this.options.zoomAnimation && L.DomUtil.TRANSITION &&
+                               L.Browser.any3d && !L.Browser.android23 && !L.Browser.mobileOpera;
+
+               // zoom transitions run with the same duration for all layers, so if one of transitionend events
+               // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
+               if (this._zoomAnimated) {
+                       L.DomEvent.on(this._mapPane, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
+               }
+       });
+}
+
+L.Map.include(!L.DomUtil.TRANSITION ? {} : {
+
+       _catchTransitionEnd: function (e) {
+               if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
+                       this._onZoomTransitionEnd();
+               }
+       },
+
+       _nothingToAnimate: function () {
+               return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
+       },
+
+       _tryAnimatedZoom: function (center, zoom, options) {
+
+               if (this._animatingZoom) { return true; }
+
+               options = options || {};
+
+               // don't animate if disabled, not supported or zoom difference is too large
+               if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
+                       Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
+
+               // offset is the pixel coords of the zoom origin relative to the current center
+               var scale = this.getZoomScale(zoom),
+                   offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale),
+                       origin = this._getCenterLayerPoint()._add(offset);
+
+               // don't animate if the zoom origin isn't within one screen from the current center, unless forced
+               if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
+
+               this
+                   .fire('movestart')
+                   .fire('zoomstart');
+
+               this._animateZoom(center, zoom, origin, scale, null, true);
+
+               return true;
+       },
+
+       _animateZoom: function (center, zoom, origin, scale, delta, backwards, forTouchZoom) {
+
+               if (!forTouchZoom) {
+                       this._animatingZoom = true;
+               }
+
+               // put transform transition on all layers with leaflet-zoom-animated class
+               L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
+
+               // remember what center/zoom to set after animation
+               this._animateToCenter = center;
+               this._animateToZoom = zoom;
+
+               // disable any dragging during animation
+               if (L.Draggable) {
+                       L.Draggable._disabled = true;
+               }
+
+               L.Util.requestAnimFrame(function () {
+                       this.fire('zoomanim', {
+                               center: center,
+                               zoom: zoom,
+                               origin: origin,
+                               scale: scale,
+                               delta: delta,
+                               backwards: backwards
+                       });
+               }, this);
+       },
+
+       _onZoomTransitionEnd: function () {
+
+               this._animatingZoom = false;
+
+               L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
+
+               this._resetView(this._animateToCenter, this._animateToZoom, true, true);
+
+               if (L.Draggable) {
+                       L.Draggable._disabled = false;
+               }
+       }
+});
+
+
+/*
+       Zoom animation logic for L.TileLayer.
+*/
+
+L.TileLayer.include({
+       _animateZoom: function (e) {
+               if (!this._animating) {
+                       this._animating = true;
+                       this._prepareBgBuffer();
+               }
+
+               var bg = this._bgBuffer,
+                   transform = L.DomUtil.TRANSFORM,
+                   initialTransform = e.delta ? L.DomUtil.getTranslateString(e.delta) : bg.style[transform],
+                   scaleStr = L.DomUtil.getScaleString(e.scale, e.origin);
+
+               bg.style[transform] = e.backwards ?
+                               scaleStr + ' ' + initialTransform :
+                               initialTransform + ' ' + scaleStr;
+       },
+
+       _endZoomAnim: function () {
+               var front = this._tileContainer,
+                   bg = this._bgBuffer;
+
+               front.style.visibility = '';
+               front.parentNode.appendChild(front); // Bring to fore
+
+               // force reflow
+               L.Util.falseFn(bg.offsetWidth);
+
+               this._animating = false;
+       },
+
+       _clearBgBuffer: function () {
+               var map = this._map;
+
+               if (map && !map._animatingZoom && !map.touchZoom._zooming) {
+                       this._bgBuffer.innerHTML = '';
+                       this._bgBuffer.style[L.DomUtil.TRANSFORM] = '';
+               }
+       },
+
+       _prepareBgBuffer: function () {
+
+               var front = this._tileContainer,
+                   bg = this._bgBuffer;
+
+               // if foreground layer doesn't have many tiles but bg layer does,
+               // keep the existing bg layer and just zoom it some more
+
+               var bgLoaded = this._getLoadedTilesPercentage(bg),
+                   frontLoaded = this._getLoadedTilesPercentage(front);
+
+               if (bg && bgLoaded > 0.5 && frontLoaded < 0.5) {
+
+                       front.style.visibility = 'hidden';
+                       this._stopLoadingImages(front);
+                       return;
+               }
+
+               // prepare the buffer to become the front tile pane
+               bg.style.visibility = 'hidden';
+               bg.style[L.DomUtil.TRANSFORM] = '';
+
+               // switch out the current layer to be the new bg layer (and vice-versa)
+               this._tileContainer = bg;
+               bg = this._bgBuffer = front;
+
+               this._stopLoadingImages(bg);
+
+               //prevent bg buffer from clearing right after zoom
+               clearTimeout(this._clearBgBufferTimer);
+       },
+
+       _getLoadedTilesPercentage: function (container) {
+               var tiles = container.getElementsByTagName('img'),
+                   i, len, count = 0;
+
+               for (i = 0, len = tiles.length; i < len; i++) {
+                       if (tiles[i].complete) {
+                               count++;
+                       }
+               }
+               return count / len;
+       },
+
+       // stops loading all tiles in the background layer
+       _stopLoadingImages: function (container) {
+               var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
+                   i, len, tile;
+
+               for (i = 0, len = tiles.length; i < len; i++) {
+                       tile = tiles[i];
+
+                       if (!tile.complete) {
+                               tile.onload = L.Util.falseFn;
+                               tile.onerror = L.Util.falseFn;
+                               tile.src = L.Util.emptyImageUrl;
+
+                               tile.parentNode.removeChild(tile);
+                       }
+               }
+       }
+});
+
+
+/*\r
+ * Provides L.Map with convenient shortcuts for using browser geolocation features.\r
+ */\r
+\r
+L.Map.include({\r
+       _defaultLocateOptions: {\r
+               watch: false,\r
+               setView: false,\r
+               maxZoom: Infinity,\r
+               timeout: 10000,\r
+               maximumAge: 0,\r
+               enableHighAccuracy: false\r
+       },\r
+\r
+       locate: function (/*Object*/ options) {\r
+\r
+               options = this._locateOptions = L.extend(this._defaultLocateOptions, options);\r
+\r
+               if (!navigator.geolocation) {\r
+                       this._handleGeolocationError({\r
+                               code: 0,\r
+                               message: 'Geolocation not supported.'\r
+                       });\r
+                       return this;\r
+               }\r
+\r
+               var onResponse = L.bind(this._handleGeolocationResponse, this),\r
+                       onError = L.bind(this._handleGeolocationError, this);\r
+\r
+               if (options.watch) {\r
+                       this._locationWatchId =\r
+                               navigator.geolocation.watchPosition(onResponse, onError, options);\r
+               } else {\r
+                       navigator.geolocation.getCurrentPosition(onResponse, onError, options);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       stopLocate: function () {\r
+               if (navigator.geolocation) {\r
+                       navigator.geolocation.clearWatch(this._locationWatchId);\r
+               }\r
+               if (this._locateOptions) {\r
+                       this._locateOptions.setView = false;\r
+               }\r
+               return this;\r
+       },\r
+\r
+       _handleGeolocationError: function (error) {\r
+               var c = error.code,\r
+                   message = error.message ||\r
+                           (c === 1 ? 'permission denied' :\r
+                           (c === 2 ? 'position unavailable' : 'timeout'));\r
+\r
+               if (this._locateOptions.setView && !this._loaded) {\r
+                       this.fitWorld();\r
+               }\r
+\r
+               this.fire('locationerror', {\r
+                       code: c,\r
+                       message: 'Geolocation error: ' + message + '.'\r
+               });\r
+       },\r
+\r
+       _handleGeolocationResponse: function (pos) {\r
+               var lat = pos.coords.latitude,\r
+                   lng = pos.coords.longitude,\r
+                   latlng = new L.LatLng(lat, lng),\r
+\r
+                   latAccuracy = 180 * pos.coords.accuracy / 40075017,\r
+                   lngAccuracy = latAccuracy / Math.cos(L.LatLng.DEG_TO_RAD * lat),\r
+\r
+                   bounds = L.latLngBounds(\r
+                           [lat - latAccuracy, lng - lngAccuracy],\r
+                           [lat + latAccuracy, lng + lngAccuracy]),\r
+\r
+                   options = this._locateOptions;\r
+\r
+               if (options.setView) {\r
+                       var zoom = Math.min(this.getBoundsZoom(bounds), options.maxZoom);\r
+                       this.setView(latlng, zoom);\r
+               }\r
+\r
+               var data = {\r
+                       latlng: latlng,\r
+                       bounds: bounds,\r
+                       timestamp: pos.timestamp\r
+               };\r
+\r
+               for (var i in pos.coords) {\r
+                       if (typeof pos.coords[i] === 'number') {\r
+                               data[i] = pos.coords[i];\r
+                       }\r
+               }\r
+\r
+               this.fire('locationfound', data);\r
+       }\r
+});\r
+
+
+}(window, document));
\ No newline at end of file
diff --git a/www/plugins/gis/lib/leaflet/dist/leaflet.css b/www/plugins/gis/lib/leaflet/dist/leaflet.css
new file mode 100644 (file)
index 0000000..ac0cd17
--- /dev/null
@@ -0,0 +1,478 @@
+/* required styles */\r
+\r
+.leaflet-map-pane,\r
+.leaflet-tile,\r
+.leaflet-marker-icon,\r
+.leaflet-marker-shadow,\r
+.leaflet-tile-pane,\r
+.leaflet-tile-container,\r
+.leaflet-overlay-pane,\r
+.leaflet-shadow-pane,\r
+.leaflet-marker-pane,\r
+.leaflet-popup-pane,\r
+.leaflet-overlay-pane svg,\r
+.leaflet-zoom-box,\r
+.leaflet-image-layer,\r
+.leaflet-layer {\r
+       position: absolute;\r
+       left: 0;\r
+       top: 0;\r
+       }\r
+.leaflet-container {\r
+       overflow: hidden;\r
+       -ms-touch-action: none;\r
+       }\r
+.leaflet-tile,\r
+.leaflet-marker-icon,\r
+.leaflet-marker-shadow {\r
+       -webkit-user-select: none;\r
+          -moz-user-select: none;\r
+               user-select: none;\r
+       -webkit-user-drag: none;\r
+       }\r
+.leaflet-marker-icon,\r
+.leaflet-marker-shadow {\r
+       display: block;\r
+       }\r
+/* map is broken in FF if you have max-width: 100% on tiles */\r
+.leaflet-container img {\r
+       max-width: none !important;\r
+       }\r
+/* stupid Android 2 doesn't understand "max-width: none" properly */\r
+.leaflet-container img.leaflet-image-layer {\r
+       max-width: 15000px !important;\r
+       }\r
+.leaflet-tile {\r
+       filter: inherit;\r
+       visibility: hidden;\r
+       }\r
+.leaflet-tile-loaded {\r
+       visibility: inherit;\r
+       }\r
+.leaflet-zoom-box {\r
+       width: 0;\r
+       height: 0;\r
+       }\r
+/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */\r
+.leaflet-overlay-pane svg {\r
+       -moz-user-select: none;\r
+       }\r
+\r
+.leaflet-tile-pane    { z-index: 2; }\r
+.leaflet-objects-pane { z-index: 3; }\r
+.leaflet-overlay-pane { z-index: 4; }\r
+.leaflet-shadow-pane  { z-index: 5; }\r
+.leaflet-marker-pane  { z-index: 6; }\r
+.leaflet-popup-pane   { z-index: 7; }\r
+\r
+.leaflet-vml-shape {\r
+       width: 1px;\r
+       height: 1px;\r
+       }\r
+.lvml {\r
+       behavior: url(#default#VML);\r
+       display: inline-block;\r
+       position: absolute;\r
+       }\r
+\r
+\r
+/* control positioning */\r
+\r
+.leaflet-control {\r
+       position: relative;\r
+       z-index: 7;\r
+       pointer-events: auto;\r
+       }\r
+.leaflet-top,\r
+.leaflet-bottom {\r
+       position: absolute;\r
+       z-index: 1000;\r
+       pointer-events: none;\r
+       }\r
+.leaflet-top {\r
+       top: 0;\r
+       }\r
+.leaflet-right {\r
+       right: 0;\r
+       }\r
+.leaflet-bottom {\r
+       bottom: 0;\r
+       }\r
+.leaflet-left {\r
+       left: 0;\r
+       }\r
+.leaflet-control {\r
+       float: left;\r
+       clear: both;\r
+       }\r
+.leaflet-right .leaflet-control {\r
+       float: right;\r
+       }\r
+.leaflet-top .leaflet-control {\r
+       margin-top: 10px;\r
+       }\r
+.leaflet-bottom .leaflet-control {\r
+       margin-bottom: 10px;\r
+       }\r
+.leaflet-left .leaflet-control {\r
+       margin-left: 10px;\r
+       }\r
+.leaflet-right .leaflet-control {\r
+       margin-right: 10px;\r
+       }\r
+\r
+\r
+/* zoom and fade animations */\r
+\r
+.leaflet-fade-anim .leaflet-tile,\r
+.leaflet-fade-anim .leaflet-popup {\r
+       opacity: 0;\r
+       -webkit-transition: opacity 0.2s linear;\r
+          -moz-transition: opacity 0.2s linear;\r
+            -o-transition: opacity 0.2s linear;\r
+               transition: opacity 0.2s linear;\r
+       }\r
+.leaflet-fade-anim .leaflet-tile-loaded,\r
+.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {\r
+       opacity: 1;\r
+       }\r
+\r
+.leaflet-zoom-anim .leaflet-zoom-animated {\r
+       -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);\r
+          -moz-transition:    -moz-transform 0.25s cubic-bezier(0,0,0.25,1);\r
+            -o-transition:      -o-transform 0.25s cubic-bezier(0,0,0.25,1);\r
+               transition:         transform 0.25s cubic-bezier(0,0,0.25,1);\r
+       }\r
+.leaflet-zoom-anim .leaflet-tile,\r
+.leaflet-pan-anim .leaflet-tile,\r
+.leaflet-touching .leaflet-zoom-animated {\r
+       -webkit-transition: none;\r
+          -moz-transition: none;\r
+            -o-transition: none;\r
+               transition: none;\r
+       }\r
+\r
+.leaflet-zoom-anim .leaflet-zoom-hide {\r
+       visibility: hidden;\r
+       }\r
+\r
+\r
+/* cursors */\r
+\r
+.leaflet-clickable {\r
+       cursor: pointer;\r
+       }\r
+.leaflet-container {\r
+       cursor: -webkit-grab;\r
+       cursor:    -moz-grab;\r
+       }\r
+.leaflet-popup-pane,\r
+.leaflet-control {\r
+       cursor: auto;\r
+       }\r
+.leaflet-dragging .leaflet-container,\r
+.leaflet-dragging .leaflet-clickable {\r
+       cursor: move;\r
+       cursor: -webkit-grabbing;\r
+       cursor:    -moz-grabbing;\r
+       }\r
+\r
+\r
+/* visual tweaks */\r
+\r
+.leaflet-container {\r
+       background: #ddd;\r
+       outline: 0;\r
+       }\r
+.leaflet-container a {\r
+       color: #0078A8;\r
+       }\r
+.leaflet-container a.leaflet-active {\r
+       outline: 2px solid orange;\r
+       }\r
+.leaflet-zoom-box {\r
+       border: 2px dotted #38f;\r
+       background: rgba(255,255,255,0.5);\r
+       }\r
+\r
+\r
+/* general typography */\r
+.leaflet-container {\r
+       font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;\r
+       }\r
+\r
+\r
+/* general toolbar styles */\r
+\r
+.leaflet-bar {\r
+       box-shadow: 0 1px 5px rgba(0,0,0,0.65);\r
+       border-radius: 4px;\r
+       }\r
+.leaflet-bar a,\r
+.leaflet-bar a:hover {\r
+       background-color: #fff;\r
+       border-bottom: 1px solid #ccc;\r
+       width: 26px;\r
+       height: 26px;\r
+       line-height: 26px;\r
+       display: block;\r
+       text-align: center;\r
+       text-decoration: none;\r
+       color: black;\r
+       }\r
+.leaflet-bar a,\r
+.leaflet-control-layers-toggle {\r
+       background-position: 50% 50%;\r
+       background-repeat: no-repeat;\r
+       display: block;\r
+       }\r
+.leaflet-bar a:hover {\r
+       background-color: #f4f4f4;\r
+       }\r
+.leaflet-bar a:first-child {\r
+       border-top-left-radius: 4px;\r
+       border-top-right-radius: 4px;\r
+       }\r
+.leaflet-bar a:last-child {\r
+       border-bottom-left-radius: 4px;\r
+       border-bottom-right-radius: 4px;\r
+       border-bottom: none;\r
+       }\r
+.leaflet-bar a.leaflet-disabled {\r
+       cursor: default;\r
+       background-color: #f4f4f4;\r
+       color: #bbb;\r
+       }\r
+\r
+.leaflet-touch .leaflet-bar a {\r
+       width: 30px;\r
+       height: 30px;\r
+       line-height: 30px;\r
+       }\r
+\r
+\r
+/* zoom control */\r
+\r
+.leaflet-control-zoom-in,\r
+.leaflet-control-zoom-out {\r
+       font: bold 18px 'Lucida Console', Monaco, monospace;\r
+       text-indent: 1px;\r
+       }\r
+.leaflet-control-zoom-out {\r
+       font-size: 20px;\r
+       }\r
+\r
+.leaflet-touch .leaflet-control-zoom-in {\r
+       font-size: 22px;\r
+       }\r
+.leaflet-touch .leaflet-control-zoom-out {\r
+       font-size: 24px;\r
+       }\r
+\r
+\r
+/* layers control */\r
+\r
+.leaflet-control-layers {\r
+       box-shadow: 0 1px 5px rgba(0,0,0,0.4);\r
+       background: #fff;\r
+       border-radius: 5px;\r
+       }\r
+.leaflet-control-layers-toggle {\r
+       background-image: url(images/layers.png);\r
+       width: 36px;\r
+       height: 36px;\r
+       }\r
+.leaflet-retina .leaflet-control-layers-toggle {\r
+       background-image: url(images/layers-2x.png);\r
+       background-size: 26px 26px;\r
+       }\r
+.leaflet-touch .leaflet-control-layers-toggle {\r
+       width: 44px;\r
+       height: 44px;\r
+       }\r
+.leaflet-control-layers .leaflet-control-layers-list,\r
+.leaflet-control-layers-expanded .leaflet-control-layers-toggle {\r
+       display: none;\r
+       }\r
+.leaflet-control-layers-expanded .leaflet-control-layers-list {\r
+       display: block;\r
+       position: relative;\r
+       }\r
+.leaflet-control-layers-expanded {\r
+       padding: 6px 10px 6px 6px;\r
+       color: #333;\r
+       background: #fff;\r
+       }\r
+.leaflet-control-layers-selector {\r
+       margin-top: 2px;\r
+       position: relative;\r
+       top: 1px;\r
+       }\r
+.leaflet-control-layers label {\r
+       display: block;\r
+       }\r
+.leaflet-control-layers-separator {\r
+       height: 0;\r
+       border-top: 1px solid #ddd;\r
+       margin: 5px -10px 5px -6px;\r
+       }\r
+\r
+\r
+/* attribution and scale controls */\r
+\r
+.leaflet-container .leaflet-control-attribution {\r
+       background: #fff;\r
+       background: rgba(255, 255, 255, 0.7);\r
+       margin: 0;\r
+       }\r
+.leaflet-control-attribution,\r
+.leaflet-control-scale-line {\r
+       padding: 0 5px;\r
+       color: #333;\r
+       }\r
+.leaflet-control-attribution a {\r
+       text-decoration: none;\r
+       }\r
+.leaflet-control-attribution a:hover {\r
+       text-decoration: underline;\r
+       }\r
+.leaflet-container .leaflet-control-attribution,\r
+.leaflet-container .leaflet-control-scale {\r
+       font-size: 11px;\r
+       }\r
+.leaflet-left .leaflet-control-scale {\r
+       margin-left: 5px;\r
+       }\r
+.leaflet-bottom .leaflet-control-scale {\r
+       margin-bottom: 5px;\r
+       }\r
+.leaflet-control-scale-line {\r
+       border: 2px solid #777;\r
+       border-top: none;\r
+       line-height: 1.1;\r
+       padding: 2px 5px 1px;\r
+       font-size: 11px;\r
+       white-space: nowrap;\r
+       overflow: hidden;\r
+       -moz-box-sizing: content-box;\r
+            box-sizing: content-box;\r
+\r
+       background: #fff;\r
+       background: rgba(255, 255, 255, 0.5);\r
+       }\r
+.leaflet-control-scale-line:not(:first-child) {\r
+       border-top: 2px solid #777;\r
+       border-bottom: none;\r
+       margin-top: -2px;\r
+       }\r
+.leaflet-control-scale-line:not(:first-child):not(:last-child) {\r
+       border-bottom: 2px solid #777;\r
+       }\r
+\r
+.leaflet-touch .leaflet-control-attribution,\r
+.leaflet-touch .leaflet-control-layers,\r
+.leaflet-touch .leaflet-bar {\r
+       box-shadow: none;\r
+       }\r
+.leaflet-touch .leaflet-control-layers,\r
+.leaflet-touch .leaflet-bar {\r
+       border: 2px solid rgba(0,0,0,0.2);\r
+       background-clip: padding-box;\r
+       }\r
+\r
+\r
+/* popup */\r
+\r
+.leaflet-popup {\r
+       position: absolute;\r
+       text-align: center;\r
+       }\r
+.leaflet-popup-content-wrapper {\r
+       padding: 1px;\r
+       text-align: left;\r
+       border-radius: 12px;\r
+       }\r
+.leaflet-popup-content {\r
+       margin: 13px 19px;\r
+       line-height: 1.4;\r
+       }\r
+.leaflet-popup-content p {\r
+       margin: 18px 0;\r
+       }\r
+.leaflet-popup-tip-container {\r
+       margin: 0 auto;\r
+       width: 40px;\r
+       height: 20px;\r
+       position: relative;\r
+       overflow: hidden;\r
+       }\r
+.leaflet-popup-tip {\r
+       width: 17px;\r
+       height: 17px;\r
+       padding: 1px;\r
+\r
+       margin: -10px auto 0;\r
+\r
+       -webkit-transform: rotate(45deg);\r
+          -moz-transform: rotate(45deg);\r
+           -ms-transform: rotate(45deg);\r
+            -o-transform: rotate(45deg);\r
+               transform: rotate(45deg);\r
+       }\r
+.leaflet-popup-content-wrapper,\r
+.leaflet-popup-tip {\r
+       background: white;\r
+\r
+       box-shadow: 0 3px 14px rgba(0,0,0,0.4);\r
+       }\r
+.leaflet-container a.leaflet-popup-close-button {\r
+       position: absolute;\r
+       top: 0;\r
+       right: 0;\r
+       padding: 4px 4px 0 0;\r
+       text-align: center;\r
+       width: 18px;\r
+       height: 14px;\r
+       font: 16px/14px Tahoma, Verdana, sans-serif;\r
+       color: #c3c3c3;\r
+       text-decoration: none;\r
+       font-weight: bold;\r
+       background: transparent;\r
+       }\r
+.leaflet-container a.leaflet-popup-close-button:hover {\r
+       color: #999;\r
+       }\r
+.leaflet-popup-scrolled {\r
+       overflow: auto;\r
+       border-bottom: 1px solid #ddd;\r
+       border-top: 1px solid #ddd;\r
+       }\r
+\r
+.leaflet-oldie .leaflet-popup-content-wrapper {\r
+       zoom: 1;\r
+       }\r
+.leaflet-oldie .leaflet-popup-tip {\r
+       width: 24px;\r
+       margin: 0 auto;\r
+\r
+       -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";\r
+       filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);\r
+       }\r
+.leaflet-oldie .leaflet-popup-tip-container {\r
+       margin-top: -1px;\r
+       }\r
+\r
+.leaflet-oldie .leaflet-control-zoom,\r
+.leaflet-oldie .leaflet-control-layers,\r
+.leaflet-oldie .leaflet-popup-content-wrapper,\r
+.leaflet-oldie .leaflet-popup-tip {\r
+       border: 1px solid #999;\r
+       }\r
+\r
+\r
+/* div icon */\r
+\r
+.leaflet-div-icon {\r
+       background: #fff;\r
+       border: 1px solid #666;\r
+       }\r
diff --git a/www/plugins/gis/lib/leaflet/dist/leaflet.js b/www/plugins/gis/lib/leaflet/dist/leaflet.js
new file mode 100644 (file)
index 0000000..03434b7
--- /dev/null
@@ -0,0 +1,9 @@
+/*
+ Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com
+ (c) 2010-2013, Vladimir Agafonkin
+ (c) 2010-2011, CloudMade
+*/
+!function(t,e,i){var n=t.L,o={};o.version="0.7.3","object"==typeof module&&"object"==typeof module.exports?module.exports=o:"function"==typeof define&&define.amd&&define(o),o.noConflict=function(){return t.L=n,this},t.L=o,o.Util={extend:function(t){var e,i,n,o,s=Array.prototype.slice.call(arguments,1);for(i=0,n=s.length;n>i;i++){o=s[i]||{};for(e in o)o.hasOwnProperty(e)&&(t[e]=o[e])}return t},bind:function(t,e){var i=arguments.length>2?Array.prototype.slice.call(arguments,2):null;return function(){return t.apply(e,i||arguments)}},stamp:function(){var t=0,e="_leaflet_id";return function(i){return i[e]=i[e]||++t,i[e]}}(),invokeEach:function(t,e,i){var n,o;if("object"==typeof t){o=Array.prototype.slice.call(arguments,3);for(n in t)e.apply(i,[n,t[n]].concat(o));return!0}return!1},limitExecByInterval:function(t,e,i){var n,o;return function s(){var a=arguments;return n?void(o=!0):(n=!0,setTimeout(function(){n=!1,o&&(s.apply(i,a),o=!1)},e),void t.apply(i,a))}},falseFn:function(){return!1},formatNum:function(t,e){var i=Math.pow(10,e||5);return Math.round(t*i)/i},trim:function(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")},splitWords:function(t){return o.Util.trim(t).split(/\s+/)},setOptions:function(t,e){return t.options=o.extend({},t.options,e),t.options},getParamString:function(t,e,i){var n=[];for(var o in t)n.push(encodeURIComponent(i?o.toUpperCase():o)+"="+encodeURIComponent(t[o]));return(e&&-1!==e.indexOf("?")?"&":"?")+n.join("&")},template:function(t,e){return t.replace(/\{ *([\w_]+) *\}/g,function(t,n){var o=e[n];if(o===i)throw new Error("No value provided for variable "+t);return"function"==typeof o&&(o=o(e)),o})},isArray:Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)},emptyImageUrl:"data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="},function(){function e(e){var i,n,o=["webkit","moz","o","ms"];for(i=0;i<o.length&&!n;i++)n=t[o[i]+e];return n}function i(e){var i=+new Date,o=Math.max(0,16-(i-n));return n=i+o,t.setTimeout(e,o)}var n=0,s=t.requestAnimationFrame||e("RequestAnimationFrame")||i,a=t.cancelAnimationFrame||e("CancelAnimationFrame")||e("CancelRequestAnimationFrame")||function(e){t.clearTimeout(e)};o.Util.requestAnimFrame=function(e,n,a,r){return e=o.bind(e,n),a&&s===i?void e():s.call(t,e,r)},o.Util.cancelAnimFrame=function(e){e&&a.call(t,e)}}(),o.extend=o.Util.extend,o.bind=o.Util.bind,o.stamp=o.Util.stamp,o.setOptions=o.Util.setOptions,o.Class=function(){},o.Class.extend=function(t){var e=function(){this.initialize&&this.initialize.apply(this,arguments),this._initHooks&&this.callInitHooks()},i=function(){};i.prototype=this.prototype;var n=new i;n.constructor=e,e.prototype=n;for(var s in this)this.hasOwnProperty(s)&&"prototype"!==s&&(e[s]=this[s]);t.statics&&(o.extend(e,t.statics),delete t.statics),t.includes&&(o.Util.extend.apply(null,[n].concat(t.includes)),delete t.includes),t.options&&n.options&&(t.options=o.extend({},n.options,t.options)),o.extend(n,t),n._initHooks=[];var a=this;return e.__super__=a.prototype,n.callInitHooks=function(){if(!this._initHooksCalled){a.prototype.callInitHooks&&a.prototype.callInitHooks.call(this),this._initHooksCalled=!0;for(var t=0,e=n._initHooks.length;e>t;t++)n._initHooks[t].call(this)}},e},o.Class.include=function(t){o.extend(this.prototype,t)},o.Class.mergeOptions=function(t){o.extend(this.prototype.options,t)},o.Class.addInitHook=function(t){var e=Array.prototype.slice.call(arguments,1),i="function"==typeof t?t:function(){this[t].apply(this,e)};this.prototype._initHooks=this.prototype._initHooks||[],this.prototype._initHooks.push(i)};var s="_leaflet_events";o.Mixin={},o.Mixin.Events={addEventListener:function(t,e,i){if(o.Util.invokeEach(t,this.addEventListener,this,e,i))return this;var n,a,r,h,l,u,c,d=this[s]=this[s]||{},p=i&&i!==this&&o.stamp(i);for(t=o.Util.splitWords(t),n=0,a=t.length;a>n;n++)r={action:e,context:i||this},h=t[n],p?(l=h+"_idx",u=l+"_len",c=d[l]=d[l]||{},c[p]||(c[p]=[],d[u]=(d[u]||0)+1),c[p].push(r)):(d[h]=d[h]||[],d[h].push(r));return this},hasEventListeners:function(t){var e=this[s];return!!e&&(t in e&&e[t].length>0||t+"_idx"in e&&e[t+"_idx_len"]>0)},removeEventListener:function(t,e,i){if(!this[s])return this;if(!t)return this.clearAllEventListeners();if(o.Util.invokeEach(t,this.removeEventListener,this,e,i))return this;var n,a,r,h,l,u,c,d,p,_=this[s],m=i&&i!==this&&o.stamp(i);for(t=o.Util.splitWords(t),n=0,a=t.length;a>n;n++)if(r=t[n],u=r+"_idx",c=u+"_len",d=_[u],e){if(h=m&&d?d[m]:_[r]){for(l=h.length-1;l>=0;l--)h[l].action!==e||i&&h[l].context!==i||(p=h.splice(l,1),p[0].action=o.Util.falseFn);i&&d&&0===h.length&&(delete d[m],_[c]--)}}else delete _[r],delete _[u],delete _[c];return this},clearAllEventListeners:function(){return delete this[s],this},fireEvent:function(t,e){if(!this.hasEventListeners(t))return this;var i,n,a,r,h,l=o.Util.extend({},e,{type:t,target:this}),u=this[s];if(u[t])for(i=u[t].slice(),n=0,a=i.length;a>n;n++)i[n].action.call(i[n].context,l);r=u[t+"_idx"];for(h in r)if(i=r[h].slice())for(n=0,a=i.length;a>n;n++)i[n].action.call(i[n].context,l);return this},addOneTimeEventListener:function(t,e,i){if(o.Util.invokeEach(t,this.addOneTimeEventListener,this,e,i))return this;var n=o.bind(function(){this.removeEventListener(t,e,i).removeEventListener(t,n,i)},this);return this.addEventListener(t,e,i).addEventListener(t,n,i)}},o.Mixin.Events.on=o.Mixin.Events.addEventListener,o.Mixin.Events.off=o.Mixin.Events.removeEventListener,o.Mixin.Events.once=o.Mixin.Events.addOneTimeEventListener,o.Mixin.Events.fire=o.Mixin.Events.fireEvent,function(){var n="ActiveXObject"in t,s=n&&!e.addEventListener,a=navigator.userAgent.toLowerCase(),r=-1!==a.indexOf("webkit"),h=-1!==a.indexOf("chrome"),l=-1!==a.indexOf("phantom"),u=-1!==a.indexOf("android"),c=-1!==a.search("android [23]"),d=-1!==a.indexOf("gecko"),p=typeof orientation!=i+"",_=t.navigator&&t.navigator.msPointerEnabled&&t.navigator.msMaxTouchPoints&&!t.PointerEvent,m=t.PointerEvent&&t.navigator.pointerEnabled&&t.navigator.maxTouchPoints||_,f="devicePixelRatio"in t&&t.devicePixelRatio>1||"matchMedia"in t&&t.matchMedia("(min-resolution:144dpi)")&&t.matchMedia("(min-resolution:144dpi)").matches,g=e.documentElement,v=n&&"transition"in g.style,y="WebKitCSSMatrix"in t&&"m11"in new t.WebKitCSSMatrix&&!c,P="MozPerspective"in g.style,L="OTransition"in g.style,x=!t.L_DISABLE_3D&&(v||y||P||L)&&!l,w=!t.L_NO_TOUCH&&!l&&function(){var t="ontouchstart";if(m||t in g)return!0;var i=e.createElement("div"),n=!1;return i.setAttribute?(i.setAttribute(t,"return;"),"function"==typeof i[t]&&(n=!0),i.removeAttribute(t),i=null,n):!1}();o.Browser={ie:n,ielt9:s,webkit:r,gecko:d&&!r&&!t.opera&&!n,android:u,android23:c,chrome:h,ie3d:v,webkit3d:y,gecko3d:P,opera3d:L,any3d:x,mobile:p,mobileWebkit:p&&r,mobileWebkit3d:p&&y,mobileOpera:p&&t.opera,touch:w,msPointer:_,pointer:m,retina:f}}(),o.Point=function(t,e,i){this.x=i?Math.round(t):t,this.y=i?Math.round(e):e},o.Point.prototype={clone:function(){return new o.Point(this.x,this.y)},add:function(t){return this.clone()._add(o.point(t))},_add:function(t){return this.x+=t.x,this.y+=t.y,this},subtract:function(t){return this.clone()._subtract(o.point(t))},_subtract:function(t){return this.x-=t.x,this.y-=t.y,this},divideBy:function(t){return this.clone()._divideBy(t)},_divideBy:function(t){return this.x/=t,this.y/=t,this},multiplyBy:function(t){return this.clone()._multiplyBy(t)},_multiplyBy:function(t){return this.x*=t,this.y*=t,this},round:function(){return this.clone()._round()},_round:function(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this},floor:function(){return this.clone()._floor()},_floor:function(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this},distanceTo:function(t){t=o.point(t);var e=t.x-this.x,i=t.y-this.y;return Math.sqrt(e*e+i*i)},equals:function(t){return t=o.point(t),t.x===this.x&&t.y===this.y},contains:function(t){return t=o.point(t),Math.abs(t.x)<=Math.abs(this.x)&&Math.abs(t.y)<=Math.abs(this.y)},toString:function(){return"Point("+o.Util.formatNum(this.x)+", "+o.Util.formatNum(this.y)+")"}},o.point=function(t,e,n){return t instanceof o.Point?t:o.Util.isArray(t)?new o.Point(t[0],t[1]):t===i||null===t?t:new o.Point(t,e,n)},o.Bounds=function(t,e){if(t)for(var i=e?[t,e]:t,n=0,o=i.length;o>n;n++)this.extend(i[n])},o.Bounds.prototype={extend:function(t){return t=o.point(t),this.min||this.max?(this.min.x=Math.min(t.x,this.min.x),this.max.x=Math.max(t.x,this.max.x),this.min.y=Math.min(t.y,this.min.y),this.max.y=Math.max(t.y,this.max.y)):(this.min=t.clone(),this.max=t.clone()),this},getCenter:function(t){return new o.Point((this.min.x+this.max.x)/2,(this.min.y+this.max.y)/2,t)},getBottomLeft:function(){return new o.Point(this.min.x,this.max.y)},getTopRight:function(){return new o.Point(this.max.x,this.min.y)},getSize:function(){return this.max.subtract(this.min)},contains:function(t){var e,i;return t="number"==typeof t[0]||t instanceof o.Point?o.point(t):o.bounds(t),t instanceof o.Bounds?(e=t.min,i=t.max):e=i=t,e.x>=this.min.x&&i.x<=this.max.x&&e.y>=this.min.y&&i.y<=this.max.y},intersects:function(t){t=o.bounds(t);var e=this.min,i=this.max,n=t.min,s=t.max,a=s.x>=e.x&&n.x<=i.x,r=s.y>=e.y&&n.y<=i.y;return a&&r},isValid:function(){return!(!this.min||!this.max)}},o.bounds=function(t,e){return!t||t instanceof o.Bounds?t:new o.Bounds(t,e)},o.Transformation=function(t,e,i,n){this._a=t,this._b=e,this._c=i,this._d=n},o.Transformation.prototype={transform:function(t,e){return this._transform(t.clone(),e)},_transform:function(t,e){return e=e||1,t.x=e*(this._a*t.x+this._b),t.y=e*(this._c*t.y+this._d),t},untransform:function(t,e){return e=e||1,new o.Point((t.x/e-this._b)/this._a,(t.y/e-this._d)/this._c)}},o.DomUtil={get:function(t){return"string"==typeof t?e.getElementById(t):t},getStyle:function(t,i){var n=t.style[i];if(!n&&t.currentStyle&&(n=t.currentStyle[i]),(!n||"auto"===n)&&e.defaultView){var o=e.defaultView.getComputedStyle(t,null);n=o?o[i]:null}return"auto"===n?null:n},getViewportOffset:function(t){var i,n=0,s=0,a=t,r=e.body,h=e.documentElement;do{if(n+=a.offsetTop||0,s+=a.offsetLeft||0,n+=parseInt(o.DomUtil.getStyle(a,"borderTopWidth"),10)||0,s+=parseInt(o.DomUtil.getStyle(a,"borderLeftWidth"),10)||0,i=o.DomUtil.getStyle(a,"position"),a.offsetParent===r&&"absolute"===i)break;if("fixed"===i){n+=r.scrollTop||h.scrollTop||0,s+=r.scrollLeft||h.scrollLeft||0;break}if("relative"===i&&!a.offsetLeft){var l=o.DomUtil.getStyle(a,"width"),u=o.DomUtil.getStyle(a,"max-width"),c=a.getBoundingClientRect();("none"!==l||"none"!==u)&&(s+=c.left+a.clientLeft),n+=c.top+(r.scrollTop||h.scrollTop||0);break}a=a.offsetParent}while(a);a=t;do{if(a===r)break;n-=a.scrollTop||0,s-=a.scrollLeft||0,a=a.parentNode}while(a);return new o.Point(s,n)},documentIsLtr:function(){return o.DomUtil._docIsLtrCached||(o.DomUtil._docIsLtrCached=!0,o.DomUtil._docIsLtr="ltr"===o.DomUtil.getStyle(e.body,"direction")),o.DomUtil._docIsLtr},create:function(t,i,n){var o=e.createElement(t);return o.className=i,n&&n.appendChild(o),o},hasClass:function(t,e){if(t.classList!==i)return t.classList.contains(e);var n=o.DomUtil._getClass(t);return n.length>0&&new RegExp("(^|\\s)"+e+"(\\s|$)").test(n)},addClass:function(t,e){if(t.classList!==i)for(var n=o.Util.splitWords(e),s=0,a=n.length;a>s;s++)t.classList.add(n[s]);else if(!o.DomUtil.hasClass(t,e)){var r=o.DomUtil._getClass(t);o.DomUtil._setClass(t,(r?r+" ":"")+e)}},removeClass:function(t,e){t.classList!==i?t.classList.remove(e):o.DomUtil._setClass(t,o.Util.trim((" "+o.DomUtil._getClass(t)+" ").replace(" "+e+" "," ")))},_setClass:function(t,e){t.className.baseVal===i?t.className=e:t.className.baseVal=e},_getClass:function(t){return t.className.baseVal===i?t.className:t.className.baseVal},setOpacity:function(t,e){if("opacity"in t.style)t.style.opacity=e;else if("filter"in t.style){var i=!1,n="DXImageTransform.Microsoft.Alpha";try{i=t.filters.item(n)}catch(o){if(1===e)return}e=Math.round(100*e),i?(i.Enabled=100!==e,i.Opacity=e):t.style.filter+=" progid:"+n+"(opacity="+e+")"}},testProp:function(t){for(var i=e.documentElement.style,n=0;n<t.length;n++)if(t[n]in i)return t[n];return!1},getTranslateString:function(t){var e=o.Browser.webkit3d,i="translate"+(e?"3d":"")+"(",n=(e?",0":"")+")";return i+t.x+"px,"+t.y+"px"+n},getScaleString:function(t,e){var i=o.DomUtil.getTranslateString(e.add(e.multiplyBy(-1*t))),n=" scale("+t+") ";return i+n},setPosition:function(t,e,i){t._leaflet_pos=e,!i&&o.Browser.any3d?t.style[o.DomUtil.TRANSFORM]=o.DomUtil.getTranslateString(e):(t.style.left=e.x+"px",t.style.top=e.y+"px")},getPosition:function(t){return t._leaflet_pos}},o.DomUtil.TRANSFORM=o.DomUtil.testProp(["transform","WebkitTransform","OTransform","MozTransform","msTransform"]),o.DomUtil.TRANSITION=o.DomUtil.testProp(["webkitTransition","transition","OTransition","MozTransition","msTransition"]),o.DomUtil.TRANSITION_END="webkitTransition"===o.DomUtil.TRANSITION||"OTransition"===o.DomUtil.TRANSITION?o.DomUtil.TRANSITION+"End":"transitionend",function(){if("onselectstart"in e)o.extend(o.DomUtil,{disableTextSelection:function(){o.DomEvent.on(t,"selectstart",o.DomEvent.preventDefault)},enableTextSelection:function(){o.DomEvent.off(t,"selectstart",o.DomEvent.preventDefault)}});else{var i=o.DomUtil.testProp(["userSelect","WebkitUserSelect","OUserSelect","MozUserSelect","msUserSelect"]);o.extend(o.DomUtil,{disableTextSelection:function(){if(i){var t=e.documentElement.style;this._userSelect=t[i],t[i]="none"}},enableTextSelection:function(){i&&(e.documentElement.style[i]=this._userSelect,delete this._userSelect)}})}o.extend(o.DomUtil,{disableImageDrag:function(){o.DomEvent.on(t,"dragstart",o.DomEvent.preventDefault)},enableImageDrag:function(){o.DomEvent.off(t,"dragstart",o.DomEvent.preventDefault)}})}(),o.LatLng=function(t,e,n){if(t=parseFloat(t),e=parseFloat(e),isNaN(t)||isNaN(e))throw new Error("Invalid LatLng object: ("+t+", "+e+")");this.lat=t,this.lng=e,n!==i&&(this.alt=parseFloat(n))},o.extend(o.LatLng,{DEG_TO_RAD:Math.PI/180,RAD_TO_DEG:180/Math.PI,MAX_MARGIN:1e-9}),o.LatLng.prototype={equals:function(t){if(!t)return!1;t=o.latLng(t);var e=Math.max(Math.abs(this.lat-t.lat),Math.abs(this.lng-t.lng));return e<=o.LatLng.MAX_MARGIN},toString:function(t){return"LatLng("+o.Util.formatNum(this.lat,t)+", "+o.Util.formatNum(this.lng,t)+")"},distanceTo:function(t){t=o.latLng(t);var e=6378137,i=o.LatLng.DEG_TO_RAD,n=(t.lat-this.lat)*i,s=(t.lng-this.lng)*i,a=this.lat*i,r=t.lat*i,h=Math.sin(n/2),l=Math.sin(s/2),u=h*h+l*l*Math.cos(a)*Math.cos(r);return 2*e*Math.atan2(Math.sqrt(u),Math.sqrt(1-u))},wrap:function(t,e){var i=this.lng;return t=t||-180,e=e||180,i=(i+e)%(e-t)+(t>i||i===e?e:t),new o.LatLng(this.lat,i)}},o.latLng=function(t,e){return t instanceof o.LatLng?t:o.Util.isArray(t)?"number"==typeof t[0]||"string"==typeof t[0]?new o.LatLng(t[0],t[1],t[2]):null:t===i||null===t?t:"object"==typeof t&&"lat"in t?new o.LatLng(t.lat,"lng"in t?t.lng:t.lon):e===i?null:new o.LatLng(t,e)},o.LatLngBounds=function(t,e){if(t)for(var i=e?[t,e]:t,n=0,o=i.length;o>n;n++)this.extend(i[n])},o.LatLngBounds.prototype={extend:function(t){if(!t)return this;var e=o.latLng(t);return t=null!==e?e:o.latLngBounds(t),t instanceof o.LatLng?this._southWest||this._northEast?(this._southWest.lat=Math.min(t.lat,this._southWest.lat),this._southWest.lng=Math.min(t.lng,this._southWest.lng),this._northEast.lat=Math.max(t.lat,this._northEast.lat),this._northEast.lng=Math.max(t.lng,this._northEast.lng)):(this._southWest=new o.LatLng(t.lat,t.lng),this._northEast=new o.LatLng(t.lat,t.lng)):t instanceof o.LatLngBounds&&(this.extend(t._southWest),this.extend(t._northEast)),this},pad:function(t){var e=this._southWest,i=this._northEast,n=Math.abs(e.lat-i.lat)*t,s=Math.abs(e.lng-i.lng)*t;return new o.LatLngBounds(new o.LatLng(e.lat-n,e.lng-s),new o.LatLng(i.lat+n,i.lng+s))},getCenter:function(){return new o.LatLng((this._southWest.lat+this._northEast.lat)/2,(this._southWest.lng+this._northEast.lng)/2)},getSouthWest:function(){return this._southWest},getNorthEast:function(){return this._northEast},getNorthWest:function(){return new o.LatLng(this.getNorth(),this.getWest())},getSouthEast:function(){return new o.LatLng(this.getSouth(),this.getEast())},getWest:function(){return this._southWest.lng},getSouth:function(){return this._southWest.lat},getEast:function(){return this._northEast.lng},getNorth:function(){return this._northEast.lat},contains:function(t){t="number"==typeof t[0]||t instanceof o.LatLng?o.latLng(t):o.latLngBounds(t);var e,i,n=this._southWest,s=this._northEast;return t instanceof o.LatLngBounds?(e=t.getSouthWest(),i=t.getNorthEast()):e=i=t,e.lat>=n.lat&&i.lat<=s.lat&&e.lng>=n.lng&&i.lng<=s.lng},intersects:function(t){t=o.latLngBounds(t);var e=this._southWest,i=this._northEast,n=t.getSouthWest(),s=t.getNorthEast(),a=s.lat>=e.lat&&n.lat<=i.lat,r=s.lng>=e.lng&&n.lng<=i.lng;return a&&r},toBBoxString:function(){return[this.getWest(),this.getSouth(),this.getEast(),this.getNorth()].join(",")},equals:function(t){return t?(t=o.latLngBounds(t),this._southWest.equals(t.getSouthWest())&&this._northEast.equals(t.getNorthEast())):!1},isValid:function(){return!(!this._southWest||!this._northEast)}},o.latLngBounds=function(t,e){return!t||t instanceof o.LatLngBounds?t:new o.LatLngBounds(t,e)},o.Projection={},o.Projection.SphericalMercator={MAX_LATITUDE:85.0511287798,project:function(t){var e=o.LatLng.DEG_TO_RAD,i=this.MAX_LATITUDE,n=Math.max(Math.min(i,t.lat),-i),s=t.lng*e,a=n*e;return a=Math.log(Math.tan(Math.PI/4+a/2)),new o.Point(s,a)},unproject:function(t){var e=o.LatLng.RAD_TO_DEG,i=t.x*e,n=(2*Math.atan(Math.exp(t.y))-Math.PI/2)*e;return new o.LatLng(n,i)}},o.Projection.LonLat={project:function(t){return new o.Point(t.lng,t.lat)},unproject:function(t){return new o.LatLng(t.y,t.x)}},o.CRS={latLngToPoint:function(t,e){var i=this.projection.project(t),n=this.scale(e);return this.transformation._transform(i,n)},pointToLatLng:function(t,e){var i=this.scale(e),n=this.transformation.untransform(t,i);return this.projection.unproject(n)},project:function(t){return this.projection.project(t)},scale:function(t){return 256*Math.pow(2,t)},getSize:function(t){var e=this.scale(t);return o.point(e,e)}},o.CRS.Simple=o.extend({},o.CRS,{projection:o.Projection.LonLat,transformation:new o.Transformation(1,0,-1,0),scale:function(t){return Math.pow(2,t)}}),o.CRS.EPSG3857=o.extend({},o.CRS,{code:"EPSG:3857",projection:o.Projection.SphericalMercator,transformation:new o.Transformation(.5/Math.PI,.5,-.5/Math.PI,.5),project:function(t){var e=this.projection.project(t),i=6378137;return e.multiplyBy(i)}}),o.CRS.EPSG900913=o.extend({},o.CRS.EPSG3857,{code:"EPSG:900913"}),o.CRS.EPSG4326=o.extend({},o.CRS,{code:"EPSG:4326",projection:o.Projection.LonLat,transformation:new o.Transformation(1/360,.5,-1/360,.5)}),o.Map=o.Class.extend({includes:o.Mixin.Events,options:{crs:o.CRS.EPSG3857,fadeAnimation:o.DomUtil.TRANSITION&&!o.Browser.android23,trackResize:!0,markerZoomAnimation:o.DomUtil.TRANSITION&&o.Browser.any3d},initialize:function(t,e){e=o.setOptions(this,e),this._initContainer(t),this._initLayout(),this._onResize=o.bind(this._onResize,this),this._initEvents(),e.maxBounds&&this.setMaxBounds(e.maxBounds),e.center&&e.zoom!==i&&this.setView(o.latLng(e.center),e.zoom,{reset:!0}),this._handlers=[],this._layers={},this._zoomBoundLayers={},this._tileLayersNum=0,this.callInitHooks(),this._addLayers(e.layers)},setView:function(t,e){return e=e===i?this.getZoom():e,this._resetView(o.latLng(t),this._limitZoom(e)),this},setZoom:function(t,e){return this._loaded?this.setView(this.getCenter(),t,{zoom:e}):(this._zoom=this._limitZoom(t),this)},zoomIn:function(t,e){return this.setZoom(this._zoom+(t||1),e)},zoomOut:function(t,e){return this.setZoom(this._zoom-(t||1),e)},setZoomAround:function(t,e,i){var n=this.getZoomScale(e),s=this.getSize().divideBy(2),a=t instanceof o.Point?t:this.latLngToContainerPoint(t),r=a.subtract(s).multiplyBy(1-1/n),h=this.containerPointToLatLng(s.add(r));return this.setView(h,e,{zoom:i})},fitBounds:function(t,e){e=e||{},t=t.getBounds?t.getBounds():o.latLngBounds(t);var i=o.point(e.paddingTopLeft||e.padding||[0,0]),n=o.point(e.paddingBottomRight||e.padding||[0,0]),s=this.getBoundsZoom(t,!1,i.add(n)),a=n.subtract(i).divideBy(2),r=this.project(t.getSouthWest(),s),h=this.project(t.getNorthEast(),s),l=this.unproject(r.add(h).divideBy(2).add(a),s);return s=e&&e.maxZoom?Math.min(e.maxZoom,s):s,this.setView(l,s,e)},fitWorld:function(t){return this.fitBounds([[-90,-180],[90,180]],t)},panTo:function(t,e){return this.setView(t,this._zoom,{pan:e})},panBy:function(t){return this.fire("movestart"),this._rawPanBy(o.point(t)),this.fire("move"),this.fire("moveend")},setMaxBounds:function(t){return t=o.latLngBounds(t),this.options.maxBounds=t,t?(this._loaded&&this._panInsideMaxBounds(),this.on("moveend",this._panInsideMaxBounds,this)):this.off("moveend",this._panInsideMaxBounds,this)},panInsideBounds:function(t,e){var i=this.getCenter(),n=this._limitCenter(i,this._zoom,t);return i.equals(n)?this:this.panTo(n,e)},addLayer:function(t){var e=o.stamp(t);return this._layers[e]?this:(this._layers[e]=t,!t.options||isNaN(t.options.maxZoom)&&isNaN(t.options.minZoom)||(this._zoomBoundLayers[e]=t,this._updateZoomLevels()),this.options.zoomAnimation&&o.TileLayer&&t instanceof o.TileLayer&&(this._tileLayersNum++,this._tileLayersToLoad++,t.on("load",this._onTileLayerLoad,this)),this._loaded&&this._layerAdd(t),this)},removeLayer:function(t){var e=o.stamp(t);return this._layers[e]?(this._loaded&&t.onRemove(this),delete this._layers[e],this._loaded&&this.fire("layerremove",{layer:t}),this._zoomBoundLayers[e]&&(delete this._zoomBoundLayers[e],this._updateZoomLevels()),this.options.zoomAnimation&&o.TileLayer&&t instanceof o.TileLayer&&(this._tileLayersNum--,this._tileLayersToLoad--,t.off("load",this._onTileLayerLoad,this)),this):this},hasLayer:function(t){return t?o.stamp(t)in this._layers:!1},eachLayer:function(t,e){for(var i in this._layers)t.call(e,this._layers[i]);return this},invalidateSize:function(t){if(!this._loaded)return this;t=o.extend({animate:!1,pan:!0},t===!0?{animate:!0}:t);var e=this.getSize();this._sizeChanged=!0,this._initialCenter=null;var i=this.getSize(),n=e.divideBy(2).round(),s=i.divideBy(2).round(),a=n.subtract(s);return a.x||a.y?(t.animate&&t.pan?this.panBy(a):(t.pan&&this._rawPanBy(a),this.fire("move"),t.debounceMoveend?(clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(o.bind(this.fire,this,"moveend"),200)):this.fire("moveend")),this.fire("resize",{oldSize:e,newSize:i})):this},addHandler:function(t,e){if(!e)return this;var i=this[t]=new e(this);return this._handlers.push(i),this.options[t]&&i.enable(),this},remove:function(){this._loaded&&this.fire("unload"),this._initEvents("off");try{delete this._container._leaflet}catch(t){this._container._leaflet=i}return this._clearPanes(),this._clearControlPos&&this._clearControlPos(),this._clearHandlers(),this},getCenter:function(){return this._checkIfLoaded(),this._initialCenter&&!this._moved()?this._initialCenter:this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds(),e=this.unproject(t.getBottomLeft()),i=this.unproject(t.getTopRight());return new o.LatLngBounds(e,i)},getMinZoom:function(){return this.options.minZoom===i?this._layersMinZoom===i?0:this._layersMinZoom:this.options.minZoom},getMaxZoom:function(){return this.options.maxZoom===i?this._layersMaxZoom===i?1/0:this._layersMaxZoom:this.options.maxZoom},getBoundsZoom:function(t,e,i){t=o.latLngBounds(t);var n,s=this.getMinZoom()-(e?1:0),a=this.getMaxZoom(),r=this.getSize(),h=t.getNorthWest(),l=t.getSouthEast(),u=!0;i=o.point(i||[0,0]);do s++,n=this.project(l,s).subtract(this.project(h,s)).add(i),u=e?n.x<r.x||n.y<r.y:r.contains(n);while(u&&a>=s);return u&&e?null:e?s:s-1},getSize:function(){return(!this._size||this._sizeChanged)&&(this._size=new o.Point(this._container.clientWidth,this._container.clientHeight),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(){var t=this._getTopLeftPoint();return new o.Bounds(t,t.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._initialTopLeftPoint},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t){var e=this.options.crs;return e.scale(t)/e.scale(this._zoom)},getScaleZoom:function(t){return this._zoom+Math.log(t)/Math.LN2},project:function(t,e){return e=e===i?this._zoom:e,this.options.crs.latLngToPoint(o.latLng(t),e)},unproject:function(t,e){return e=e===i?this._zoom:e,this.options.crs.pointToLatLng(o.point(t),e)},layerPointToLatLng:function(t){var e=o.point(t).add(this.getPixelOrigin());return this.unproject(e)},latLngToLayerPoint:function(t){var e=this.project(o.latLng(t))._round();return e._subtract(this.getPixelOrigin())},containerPointToLayerPoint:function(t){return o.point(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return o.point(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){var e=this.containerPointToLayerPoint(o.point(t));return this.layerPointToLatLng(e)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(o.latLng(t)))},mouseEventToContainerPoint:function(t){return o.DomEvent.getMousePosition(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){var e=this._container=o.DomUtil.get(t);if(!e)throw new Error("Map container not found.");if(e._leaflet)throw new Error("Map container is already initialized.");e._leaflet=!0},_initLayout:function(){var t=this._container;o.DomUtil.addClass(t,"leaflet-container"+(o.Browser.touch?" leaflet-touch":"")+(o.Browser.retina?" leaflet-retina":"")+(o.Browser.ielt9?" leaflet-oldie":"")+(this.options.fadeAnimation?" leaflet-fade-anim":""));var e=o.DomUtil.getStyle(t,"position");"absolute"!==e&&"relative"!==e&&"fixed"!==e&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._mapPane=t.mapPane=this._createPane("leaflet-map-pane",this._container),this._tilePane=t.tilePane=this._createPane("leaflet-tile-pane",this._mapPane),t.objectsPane=this._createPane("leaflet-objects-pane",this._mapPane),t.shadowPane=this._createPane("leaflet-shadow-pane"),t.overlayPane=this._createPane("leaflet-overlay-pane"),t.markerPane=this._createPane("leaflet-marker-pane"),t.popupPane=this._createPane("leaflet-popup-pane");var e=" leaflet-zoom-hide";this.options.markerZoomAnimation||(o.DomUtil.addClass(t.markerPane,e),o.DomUtil.addClass(t.shadowPane,e),o.DomUtil.addClass(t.popupPane,e))},_createPane:function(t,e){return o.DomUtil.create("div",t,e||this._panes.objectsPane)},_clearPanes:function(){this._container.removeChild(this._mapPane)},_addLayers:function(t){t=t?o.Util.isArray(t)?t:[t]:[];for(var e=0,i=t.length;i>e;e++)this.addLayer(t[e])},_resetView:function(t,e,i,n){var s=this._zoom!==e;n||(this.fire("movestart"),s&&this.fire("zoomstart")),this._zoom=e,this._initialCenter=t,this._initialTopLeftPoint=this._getNewTopLeftPoint(t),i?this._initialTopLeftPoint._add(this._getMapPanePos()):o.DomUtil.setPosition(this._mapPane,new o.Point(0,0)),this._tileLayersToLoad=this._tileLayersNum;var a=!this._loaded;this._loaded=!0,this.fire("viewreset",{hard:!i}),a&&(this.fire("load"),this.eachLayer(this._layerAdd,this)),this.fire("move"),(s||n)&&this.fire("zoomend"),this.fire("moveend",{hard:!i})},_rawPanBy:function(t){o.DomUtil.setPosition(this._mapPane,this._getMapPanePos().subtract(t))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_updateZoomLevels:function(){var t,e=1/0,n=-1/0,o=this._getZoomSpan();for(t in this._zoomBoundLayers){var s=this._zoomBoundLayers[t];isNaN(s.options.minZoom)||(e=Math.min(e,s.options.minZoom)),isNaN(s.options.maxZoom)||(n=Math.max(n,s.options.maxZoom))}t===i?this._layersMaxZoom=this._layersMinZoom=i:(this._layersMaxZoom=n,this._layersMinZoom=e),o!==this._getZoomSpan()&&this.fire("zoomlevelschange")},_panInsideMaxBounds:function(){this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error("Set map center and zoom first.")},_initEvents:function(e){if(o.DomEvent){e=e||"on",o.DomEvent[e](this._container,"click",this._onMouseClick,this);var i,n,s=["dblclick","mousedown","mouseup","mouseenter","mouseleave","mousemove","contextmenu"];for(i=0,n=s.length;n>i;i++)o.DomEvent[e](this._container,s[i],this._fireMouseEvent,this);this.options.trackResize&&o.DomEvent[e](t,"resize",this._onResize,this)}},_onResize:function(){o.Util.cancelAnimFrame(this._resizeRequest),this._resizeRequest=o.Util.requestAnimFrame(function(){this.invalidateSize({debounceMoveend:!0})},this,!1,this._container)},_onMouseClick:function(t){!this._loaded||!t._simulated&&(this.dragging&&this.dragging.moved()||this.boxZoom&&this.boxZoom.moved())||o.DomEvent._skipped(t)||(this.fire("preclick"),this._fireMouseEvent(t))},_fireMouseEvent:function(t){if(this._loaded&&!o.DomEvent._skipped(t)){var e=t.type;if(e="mouseenter"===e?"mouseover":"mouseleave"===e?"mouseout":e,this.hasEventListeners(e)){"contextmenu"===e&&o.DomEvent.preventDefault(t);var i=this.mouseEventToContainerPoint(t),n=this.containerPointToLayerPoint(i),s=this.layerPointToLatLng(n);this.fire(e,{latlng:s,layerPoint:n,containerPoint:i,originalEvent:t})}}},_onTileLayerLoad:function(){this._tileLayersToLoad--,this._tileLayersNum&&!this._tileLayersToLoad&&this.fire("tilelayersload")},_clearHandlers:function(){for(var t=0,e=this._handlers.length;e>t;t++)this._handlers[t].disable()},whenReady:function(t,e){return this._loaded?t.call(e||this,this):this.on("load",t,e),this},_layerAdd:function(t){t.onAdd(this),this.fire("layeradd",{layer:t})},_getMapPanePos:function(){return o.DomUtil.getPosition(this._mapPane)},_moved:function(){var t=this._getMapPanePos();return t&&!t.equals([0,0])},_getTopLeftPoint:function(){return this.getPixelOrigin().subtract(this._getMapPanePos())},_getNewTopLeftPoint:function(t,e){var i=this.getSize()._divideBy(2);return this.project(t,e)._subtract(i)._round()},_latLngToNewLayerPoint:function(t,e,i){var n=this._getNewTopLeftPoint(i,e).add(this._getMapPanePos());return this.project(t,e)._subtract(n)},_getCenterLayerPoint:function(){return this.containerPointToLayerPoint(this.getSize()._divideBy(2))},_getCenterOffset:function(t){return this.latLngToLayerPoint(t).subtract(this._getCenterLayerPoint())},_limitCenter:function(t,e,i){if(!i)return t;var n=this.project(t,e),s=this.getSize().divideBy(2),a=new o.Bounds(n.subtract(s),n.add(s)),r=this._getBoundsOffset(a,i,e);return this.unproject(n.add(r),e)},_limitOffset:function(t,e){if(!e)return t;var i=this.getPixelBounds(),n=new o.Bounds(i.min.add(t),i.max.add(t));return t.add(this._getBoundsOffset(n,e))},_getBoundsOffset:function(t,e,i){var n=this.project(e.getNorthWest(),i).subtract(t.min),s=this.project(e.getSouthEast(),i).subtract(t.max),a=this._rebound(n.x,-s.x),r=this._rebound(n.y,-s.y);return new o.Point(a,r)},_rebound:function(t,e){return t+e>0?Math.round(t-e)/2:Math.max(0,Math.ceil(t))-Math.max(0,Math.floor(e))},_limitZoom:function(t){var e=this.getMinZoom(),i=this.getMaxZoom();return Math.max(e,Math.min(i,t))}}),o.map=function(t,e){return new o.Map(t,e)},o.Projection.Mercator={MAX_LATITUDE:85.0840591556,R_MINOR:6356752.314245179,R_MAJOR:6378137,project:function(t){var e=o.LatLng.DEG_TO_RAD,i=this.MAX_LATITUDE,n=Math.max(Math.min(i,t.lat),-i),s=this.R_MAJOR,a=this.R_MINOR,r=t.lng*e*s,h=n*e,l=a/s,u=Math.sqrt(1-l*l),c=u*Math.sin(h);c=Math.pow((1-c)/(1+c),.5*u);var d=Math.tan(.5*(.5*Math.PI-h))/c;return h=-s*Math.log(d),new o.Point(r,h)},unproject:function(t){for(var e,i=o.LatLng.RAD_TO_DEG,n=this.R_MAJOR,s=this.R_MINOR,a=t.x*i/n,r=s/n,h=Math.sqrt(1-r*r),l=Math.exp(-t.y/n),u=Math.PI/2-2*Math.atan(l),c=15,d=1e-7,p=c,_=.1;Math.abs(_)>d&&--p>0;)e=h*Math.sin(u),_=Math.PI/2-2*Math.atan(l*Math.pow((1-e)/(1+e),.5*h))-u,u+=_;
+return new o.LatLng(u*i,a)}},o.CRS.EPSG3395=o.extend({},o.CRS,{code:"EPSG:3395",projection:o.Projection.Mercator,transformation:function(){var t=o.Projection.Mercator,e=t.R_MAJOR,i=.5/(Math.PI*e);return new o.Transformation(i,.5,-i,.5)}()}),o.TileLayer=o.Class.extend({includes:o.Mixin.Events,options:{minZoom:0,maxZoom:18,tileSize:256,subdomains:"abc",errorTileUrl:"",attribution:"",zoomOffset:0,opacity:1,unloadInvisibleTiles:o.Browser.mobile,updateWhenIdle:o.Browser.mobile},initialize:function(t,e){e=o.setOptions(this,e),e.detectRetina&&o.Browser.retina&&e.maxZoom>0&&(e.tileSize=Math.floor(e.tileSize/2),e.zoomOffset++,e.minZoom>0&&e.minZoom--,this.options.maxZoom--),e.bounds&&(e.bounds=o.latLngBounds(e.bounds)),this._url=t;var i=this.options.subdomains;"string"==typeof i&&(this.options.subdomains=i.split(""))},onAdd:function(t){this._map=t,this._animated=t._zoomAnimated,this._initContainer(),t.on({viewreset:this._reset,moveend:this._update},this),this._animated&&t.on({zoomanim:this._animateZoom,zoomend:this._endZoomAnim},this),this.options.updateWhenIdle||(this._limitedUpdate=o.Util.limitExecByInterval(this._update,150,this),t.on("move",this._limitedUpdate,this)),this._reset(),this._update()},addTo:function(t){return t.addLayer(this),this},onRemove:function(t){this._container.parentNode.removeChild(this._container),t.off({viewreset:this._reset,moveend:this._update},this),this._animated&&t.off({zoomanim:this._animateZoom,zoomend:this._endZoomAnim},this),this.options.updateWhenIdle||t.off("move",this._limitedUpdate,this),this._container=null,this._map=null},bringToFront:function(){var t=this._map._panes.tilePane;return this._container&&(t.appendChild(this._container),this._setAutoZIndex(t,Math.max)),this},bringToBack:function(){var t=this._map._panes.tilePane;return this._container&&(t.insertBefore(this._container,t.firstChild),this._setAutoZIndex(t,Math.min)),this},getAttribution:function(){return this.options.attribution},getContainer:function(){return this._container},setOpacity:function(t){return this.options.opacity=t,this._map&&this._updateOpacity(),this},setZIndex:function(t){return this.options.zIndex=t,this._updateZIndex(),this},setUrl:function(t,e){return this._url=t,e||this.redraw(),this},redraw:function(){return this._map&&(this._reset({hard:!0}),this._update()),this},_updateZIndex:function(){this._container&&this.options.zIndex!==i&&(this._container.style.zIndex=this.options.zIndex)},_setAutoZIndex:function(t,e){var i,n,o,s=t.children,a=-e(1/0,-1/0);for(n=0,o=s.length;o>n;n++)s[n]!==this._container&&(i=parseInt(s[n].style.zIndex,10),isNaN(i)||(a=e(a,i)));this.options.zIndex=this._container.style.zIndex=(isFinite(a)?a:0)+e(1,-1)},_updateOpacity:function(){var t,e=this._tiles;if(o.Browser.ielt9)for(t in e)o.DomUtil.setOpacity(e[t],this.options.opacity);else o.DomUtil.setOpacity(this._container,this.options.opacity)},_initContainer:function(){var t=this._map._panes.tilePane;if(!this._container){if(this._container=o.DomUtil.create("div","leaflet-layer"),this._updateZIndex(),this._animated){var e="leaflet-tile-container";this._bgBuffer=o.DomUtil.create("div",e,this._container),this._tileContainer=o.DomUtil.create("div",e,this._container)}else this._tileContainer=this._container;t.appendChild(this._container),this.options.opacity<1&&this._updateOpacity()}},_reset:function(t){for(var e in this._tiles)this.fire("tileunload",{tile:this._tiles[e]});this._tiles={},this._tilesToLoad=0,this.options.reuseTiles&&(this._unusedTiles=[]),this._tileContainer.innerHTML="",this._animated&&t&&t.hard&&this._clearBgBuffer(),this._initContainer()},_getTileSize:function(){var t=this._map,e=t.getZoom()+this.options.zoomOffset,i=this.options.maxNativeZoom,n=this.options.tileSize;return i&&e>i&&(n=Math.round(t.getZoomScale(e)/t.getZoomScale(i)*n)),n},_update:function(){if(this._map){var t=this._map,e=t.getPixelBounds(),i=t.getZoom(),n=this._getTileSize();if(!(i>this.options.maxZoom||i<this.options.minZoom)){var s=o.bounds(e.min.divideBy(n)._floor(),e.max.divideBy(n)._floor());this._addTilesFromCenterOut(s),(this.options.unloadInvisibleTiles||this.options.reuseTiles)&&this._removeOtherTiles(s)}}},_addTilesFromCenterOut:function(t){var i,n,s,a=[],r=t.getCenter();for(i=t.min.y;i<=t.max.y;i++)for(n=t.min.x;n<=t.max.x;n++)s=new o.Point(n,i),this._tileShouldBeLoaded(s)&&a.push(s);var h=a.length;if(0!==h){a.sort(function(t,e){return t.distanceTo(r)-e.distanceTo(r)});var l=e.createDocumentFragment();for(this._tilesToLoad||this.fire("loading"),this._tilesToLoad+=h,n=0;h>n;n++)this._addTile(a[n],l);this._tileContainer.appendChild(l)}},_tileShouldBeLoaded:function(t){if(t.x+":"+t.y in this._tiles)return!1;var e=this.options;if(!e.continuousWorld){var i=this._getWrapTileNum();if(e.noWrap&&(t.x<0||t.x>=i.x)||t.y<0||t.y>=i.y)return!1}if(e.bounds){var n=e.tileSize,o=t.multiplyBy(n),s=o.add([n,n]),a=this._map.unproject(o),r=this._map.unproject(s);if(e.continuousWorld||e.noWrap||(a=a.wrap(),r=r.wrap()),!e.bounds.intersects([a,r]))return!1}return!0},_removeOtherTiles:function(t){var e,i,n,o;for(o in this._tiles)e=o.split(":"),i=parseInt(e[0],10),n=parseInt(e[1],10),(i<t.min.x||i>t.max.x||n<t.min.y||n>t.max.y)&&this._removeTile(o)},_removeTile:function(t){var e=this._tiles[t];this.fire("tileunload",{tile:e,url:e.src}),this.options.reuseTiles?(o.DomUtil.removeClass(e,"leaflet-tile-loaded"),this._unusedTiles.push(e)):e.parentNode===this._tileContainer&&this._tileContainer.removeChild(e),o.Browser.android||(e.onload=null,e.src=o.Util.emptyImageUrl),delete this._tiles[t]},_addTile:function(t,e){var i=this._getTilePos(t),n=this._getTile();o.DomUtil.setPosition(n,i,o.Browser.chrome),this._tiles[t.x+":"+t.y]=n,this._loadTile(n,t),n.parentNode!==this._tileContainer&&e.appendChild(n)},_getZoomForUrl:function(){var t=this.options,e=this._map.getZoom();return t.zoomReverse&&(e=t.maxZoom-e),e+=t.zoomOffset,t.maxNativeZoom?Math.min(e,t.maxNativeZoom):e},_getTilePos:function(t){var e=this._map.getPixelOrigin(),i=this._getTileSize();return t.multiplyBy(i).subtract(e)},getTileUrl:function(t){return o.Util.template(this._url,o.extend({s:this._getSubdomain(t),z:t.z,x:t.x,y:t.y},this.options))},_getWrapTileNum:function(){var t=this._map.options.crs,e=t.getSize(this._map.getZoom());return e.divideBy(this._getTileSize())._floor()},_adjustTilePoint:function(t){var e=this._getWrapTileNum();this.options.continuousWorld||this.options.noWrap||(t.x=(t.x%e.x+e.x)%e.x),this.options.tms&&(t.y=e.y-t.y-1),t.z=this._getZoomForUrl()},_getSubdomain:function(t){var e=Math.abs(t.x+t.y)%this.options.subdomains.length;return this.options.subdomains[e]},_getTile:function(){if(this.options.reuseTiles&&this._unusedTiles.length>0){var t=this._unusedTiles.pop();return this._resetTile(t),t}return this._createTile()},_resetTile:function(){},_createTile:function(){var t=o.DomUtil.create("img","leaflet-tile");return t.style.width=t.style.height=this._getTileSize()+"px",t.galleryimg="no",t.onselectstart=t.onmousemove=o.Util.falseFn,o.Browser.ielt9&&this.options.opacity!==i&&o.DomUtil.setOpacity(t,this.options.opacity),o.Browser.mobileWebkit3d&&(t.style.WebkitBackfaceVisibility="hidden"),t},_loadTile:function(t,e){t._layer=this,t.onload=this._tileOnLoad,t.onerror=this._tileOnError,this._adjustTilePoint(e),t.src=this.getTileUrl(e),this.fire("tileloadstart",{tile:t,url:t.src})},_tileLoaded:function(){this._tilesToLoad--,this._animated&&o.DomUtil.addClass(this._tileContainer,"leaflet-zoom-animated"),this._tilesToLoad||(this.fire("load"),this._animated&&(clearTimeout(this._clearBgBufferTimer),this._clearBgBufferTimer=setTimeout(o.bind(this._clearBgBuffer,this),500)))},_tileOnLoad:function(){var t=this._layer;this.src!==o.Util.emptyImageUrl&&(o.DomUtil.addClass(this,"leaflet-tile-loaded"),t.fire("tileload",{tile:this,url:this.src})),t._tileLoaded()},_tileOnError:function(){var t=this._layer;t.fire("tileerror",{tile:this,url:this.src});var e=t.options.errorTileUrl;e&&(this.src=e),t._tileLoaded()}}),o.tileLayer=function(t,e){return new o.TileLayer(t,e)},o.TileLayer.WMS=o.TileLayer.extend({defaultWmsParams:{service:"WMS",request:"GetMap",version:"1.1.1",layers:"",styles:"",format:"image/jpeg",transparent:!1},initialize:function(t,e){this._url=t;var i=o.extend({},this.defaultWmsParams),n=e.tileSize||this.options.tileSize;i.width=i.height=e.detectRetina&&o.Browser.retina?2*n:n;for(var s in e)this.options.hasOwnProperty(s)||"crs"===s||(i[s]=e[s]);this.wmsParams=i,o.setOptions(this,e)},onAdd:function(t){this._crs=this.options.crs||t.options.crs,this._wmsVersion=parseFloat(this.wmsParams.version);var e=this._wmsVersion>=1.3?"crs":"srs";this.wmsParams[e]=this._crs.code,o.TileLayer.prototype.onAdd.call(this,t)},getTileUrl:function(t){var e=this._map,i=this.options.tileSize,n=t.multiplyBy(i),s=n.add([i,i]),a=this._crs.project(e.unproject(n,t.z)),r=this._crs.project(e.unproject(s,t.z)),h=this._wmsVersion>=1.3&&this._crs===o.CRS.EPSG4326?[r.y,a.x,a.y,r.x].join(","):[a.x,r.y,r.x,a.y].join(","),l=o.Util.template(this._url,{s:this._getSubdomain(t)});return l+o.Util.getParamString(this.wmsParams,l,!0)+"&BBOX="+h},setParams:function(t,e){return o.extend(this.wmsParams,t),e||this.redraw(),this}}),o.tileLayer.wms=function(t,e){return new o.TileLayer.WMS(t,e)},o.TileLayer.Canvas=o.TileLayer.extend({options:{async:!1},initialize:function(t){o.setOptions(this,t)},redraw:function(){this._map&&(this._reset({hard:!0}),this._update());for(var t in this._tiles)this._redrawTile(this._tiles[t]);return this},_redrawTile:function(t){this.drawTile(t,t._tilePoint,this._map._zoom)},_createTile:function(){var t=o.DomUtil.create("canvas","leaflet-tile");return t.width=t.height=this.options.tileSize,t.onselectstart=t.onmousemove=o.Util.falseFn,t},_loadTile:function(t,e){t._layer=this,t._tilePoint=e,this._redrawTile(t),this.options.async||this.tileDrawn(t)},drawTile:function(){},tileDrawn:function(t){this._tileOnLoad.call(t)}}),o.tileLayer.canvas=function(t){return new o.TileLayer.Canvas(t)},o.ImageOverlay=o.Class.extend({includes:o.Mixin.Events,options:{opacity:1},initialize:function(t,e,i){this._url=t,this._bounds=o.latLngBounds(e),o.setOptions(this,i)},onAdd:function(t){this._map=t,this._image||this._initImage(),t._panes.overlayPane.appendChild(this._image),t.on("viewreset",this._reset,this),t.options.zoomAnimation&&o.Browser.any3d&&t.on("zoomanim",this._animateZoom,this),this._reset()},onRemove:function(t){t.getPanes().overlayPane.removeChild(this._image),t.off("viewreset",this._reset,this),t.options.zoomAnimation&&t.off("zoomanim",this._animateZoom,this)},addTo:function(t){return t.addLayer(this),this},setOpacity:function(t){return this.options.opacity=t,this._updateOpacity(),this},bringToFront:function(){return this._image&&this._map._panes.overlayPane.appendChild(this._image),this},bringToBack:function(){var t=this._map._panes.overlayPane;return this._image&&t.insertBefore(this._image,t.firstChild),this},setUrl:function(t){this._url=t,this._image.src=this._url},getAttribution:function(){return this.options.attribution},_initImage:function(){this._image=o.DomUtil.create("img","leaflet-image-layer"),this._map.options.zoomAnimation&&o.Browser.any3d?o.DomUtil.addClass(this._image,"leaflet-zoom-animated"):o.DomUtil.addClass(this._image,"leaflet-zoom-hide"),this._updateOpacity(),o.extend(this._image,{galleryimg:"no",onselectstart:o.Util.falseFn,onmousemove:o.Util.falseFn,onload:o.bind(this._onImageLoad,this),src:this._url})},_animateZoom:function(t){var e=this._map,i=this._image,n=e.getZoomScale(t.zoom),s=this._bounds.getNorthWest(),a=this._bounds.getSouthEast(),r=e._latLngToNewLayerPoint(s,t.zoom,t.center),h=e._latLngToNewLayerPoint(a,t.zoom,t.center)._subtract(r),l=r._add(h._multiplyBy(.5*(1-1/n)));i.style[o.DomUtil.TRANSFORM]=o.DomUtil.getTranslateString(l)+" scale("+n+") "},_reset:function(){var t=this._image,e=this._map.latLngToLayerPoint(this._bounds.getNorthWest()),i=this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(e);o.DomUtil.setPosition(t,e),t.style.width=i.x+"px",t.style.height=i.y+"px"},_onImageLoad:function(){this.fire("load")},_updateOpacity:function(){o.DomUtil.setOpacity(this._image,this.options.opacity)}}),o.imageOverlay=function(t,e,i){return new o.ImageOverlay(t,e,i)},o.Icon=o.Class.extend({options:{className:""},initialize:function(t){o.setOptions(this,t)},createIcon:function(t){return this._createIcon("icon",t)},createShadow:function(t){return this._createIcon("shadow",t)},_createIcon:function(t,e){var i=this._getIconUrl(t);if(!i){if("icon"===t)throw new Error("iconUrl not set in Icon options (see the docs).");return null}var n;return n=e&&"IMG"===e.tagName?this._createImg(i,e):this._createImg(i),this._setIconStyles(n,t),n},_setIconStyles:function(t,e){var i,n=this.options,s=o.point(n[e+"Size"]);i=o.point("shadow"===e?n.shadowAnchor||n.iconAnchor:n.iconAnchor),!i&&s&&(i=s.divideBy(2,!0)),t.className="leaflet-marker-"+e+" "+n.className,i&&(t.style.marginLeft=-i.x+"px",t.style.marginTop=-i.y+"px"),s&&(t.style.width=s.x+"px",t.style.height=s.y+"px")},_createImg:function(t,i){return i=i||e.createElement("img"),i.src=t,i},_getIconUrl:function(t){return o.Browser.retina&&this.options[t+"RetinaUrl"]?this.options[t+"RetinaUrl"]:this.options[t+"Url"]}}),o.icon=function(t){return new o.Icon(t)},o.Icon.Default=o.Icon.extend({options:{iconSize:[25,41],iconAnchor:[12,41],popupAnchor:[1,-34],shadowSize:[41,41]},_getIconUrl:function(t){var e=t+"Url";if(this.options[e])return this.options[e];o.Browser.retina&&"icon"===t&&(t+="-2x");var i=o.Icon.Default.imagePath;if(!i)throw new Error("Couldn't autodetect L.Icon.Default.imagePath, set it manually.");return i+"/marker-"+t+".png"}}),o.Icon.Default.imagePath=function(){var t,i,n,o,s,a=e.getElementsByTagName("script"),r=/[\/^]leaflet[\-\._]?([\w\-\._]*)\.js\??/;for(t=0,i=a.length;i>t;t++)if(n=a[t].src,o=n.match(r))return s=n.split(r)[0],(s?s+"/":"")+"images"}(),o.Marker=o.Class.extend({includes:o.Mixin.Events,options:{icon:new o.Icon.Default,title:"",alt:"",clickable:!0,draggable:!1,keyboard:!0,zIndexOffset:0,opacity:1,riseOnHover:!1,riseOffset:250},initialize:function(t,e){o.setOptions(this,e),this._latlng=o.latLng(t)},onAdd:function(t){this._map=t,t.on("viewreset",this.update,this),this._initIcon(),this.update(),this.fire("add"),t.options.zoomAnimation&&t.options.markerZoomAnimation&&t.on("zoomanim",this._animateZoom,this)},addTo:function(t){return t.addLayer(this),this},onRemove:function(t){this.dragging&&this.dragging.disable(),this._removeIcon(),this._removeShadow(),this.fire("remove"),t.off({viewreset:this.update,zoomanim:this._animateZoom},this),this._map=null},getLatLng:function(){return this._latlng},setLatLng:function(t){return this._latlng=o.latLng(t),this.update(),this.fire("move",{latlng:this._latlng})},setZIndexOffset:function(t){return this.options.zIndexOffset=t,this.update(),this},setIcon:function(t){return this.options.icon=t,this._map&&(this._initIcon(),this.update()),this._popup&&this.bindPopup(this._popup),this},update:function(){if(this._icon){var t=this._map.latLngToLayerPoint(this._latlng).round();this._setPos(t)}return this},_initIcon:function(){var t=this.options,e=this._map,i=e.options.zoomAnimation&&e.options.markerZoomAnimation,n=i?"leaflet-zoom-animated":"leaflet-zoom-hide",s=t.icon.createIcon(this._icon),a=!1;s!==this._icon&&(this._icon&&this._removeIcon(),a=!0,t.title&&(s.title=t.title),t.alt&&(s.alt=t.alt)),o.DomUtil.addClass(s,n),t.keyboard&&(s.tabIndex="0"),this._icon=s,this._initInteraction(),t.riseOnHover&&o.DomEvent.on(s,"mouseover",this._bringToFront,this).on(s,"mouseout",this._resetZIndex,this);var r=t.icon.createShadow(this._shadow),h=!1;r!==this._shadow&&(this._removeShadow(),h=!0),r&&o.DomUtil.addClass(r,n),this._shadow=r,t.opacity<1&&this._updateOpacity();var l=this._map._panes;a&&l.markerPane.appendChild(this._icon),r&&h&&l.shadowPane.appendChild(this._shadow)},_removeIcon:function(){this.options.riseOnHover&&o.DomEvent.off(this._icon,"mouseover",this._bringToFront).off(this._icon,"mouseout",this._resetZIndex),this._map._panes.markerPane.removeChild(this._icon),this._icon=null},_removeShadow:function(){this._shadow&&this._map._panes.shadowPane.removeChild(this._shadow),this._shadow=null},_setPos:function(t){o.DomUtil.setPosition(this._icon,t),this._shadow&&o.DomUtil.setPosition(this._shadow,t),this._zIndex=t.y+this.options.zIndexOffset,this._resetZIndex()},_updateZIndex:function(t){this._icon.style.zIndex=this._zIndex+t},_animateZoom:function(t){var e=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center).round();this._setPos(e)},_initInteraction:function(){if(this.options.clickable){var t=this._icon,e=["dblclick","mousedown","mouseover","mouseout","contextmenu"];o.DomUtil.addClass(t,"leaflet-clickable"),o.DomEvent.on(t,"click",this._onMouseClick,this),o.DomEvent.on(t,"keypress",this._onKeyPress,this);for(var i=0;i<e.length;i++)o.DomEvent.on(t,e[i],this._fireMouseEvent,this);o.Handler.MarkerDrag&&(this.dragging=new o.Handler.MarkerDrag(this),this.options.draggable&&this.dragging.enable())}},_onMouseClick:function(t){var e=this.dragging&&this.dragging.moved();(this.hasEventListeners(t.type)||e)&&o.DomEvent.stopPropagation(t),e||(this.dragging&&this.dragging._enabled||!this._map.dragging||!this._map.dragging.moved())&&this.fire(t.type,{originalEvent:t,latlng:this._latlng})},_onKeyPress:function(t){13===t.keyCode&&this.fire("click",{originalEvent:t,latlng:this._latlng})},_fireMouseEvent:function(t){this.fire(t.type,{originalEvent:t,latlng:this._latlng}),"contextmenu"===t.type&&this.hasEventListeners(t.type)&&o.DomEvent.preventDefault(t),"mousedown"!==t.type?o.DomEvent.stopPropagation(t):o.DomEvent.preventDefault(t)},setOpacity:function(t){return this.options.opacity=t,this._map&&this._updateOpacity(),this},_updateOpacity:function(){o.DomUtil.setOpacity(this._icon,this.options.opacity),this._shadow&&o.DomUtil.setOpacity(this._shadow,this.options.opacity)},_bringToFront:function(){this._updateZIndex(this.options.riseOffset)},_resetZIndex:function(){this._updateZIndex(0)}}),o.marker=function(t,e){return new o.Marker(t,e)},o.DivIcon=o.Icon.extend({options:{iconSize:[12,12],className:"leaflet-div-icon",html:!1},createIcon:function(t){var i=t&&"DIV"===t.tagName?t:e.createElement("div"),n=this.options;return i.innerHTML=n.html!==!1?n.html:"",n.bgPos&&(i.style.backgroundPosition=-n.bgPos.x+"px "+-n.bgPos.y+"px"),this._setIconStyles(i,"icon"),i},createShadow:function(){return null}}),o.divIcon=function(t){return new o.DivIcon(t)},o.Map.mergeOptions({closePopupOnClick:!0}),o.Popup=o.Class.extend({includes:o.Mixin.Events,options:{minWidth:50,maxWidth:300,autoPan:!0,closeButton:!0,offset:[0,7],autoPanPadding:[5,5],keepInView:!1,className:"",zoomAnimation:!0},initialize:function(t,e){o.setOptions(this,t),this._source=e,this._animated=o.Browser.any3d&&this.options.zoomAnimation,this._isOpen=!1},onAdd:function(t){this._map=t,this._container||this._initLayout();var e=t.options.fadeAnimation;e&&o.DomUtil.setOpacity(this._container,0),t._panes.popupPane.appendChild(this._container),t.on(this._getEvents(),this),this.update(),e&&o.DomUtil.setOpacity(this._container,1),this.fire("open"),t.fire("popupopen",{popup:this}),this._source&&this._source.fire("popupopen",{popup:this})},addTo:function(t){return t.addLayer(this),this},openOn:function(t){return t.openPopup(this),this},onRemove:function(t){t._panes.popupPane.removeChild(this._container),o.Util.falseFn(this._container.offsetWidth),t.off(this._getEvents(),this),t.options.fadeAnimation&&o.DomUtil.setOpacity(this._container,0),this._map=null,this.fire("close"),t.fire("popupclose",{popup:this}),this._source&&this._source.fire("popupclose",{popup:this})},getLatLng:function(){return this._latlng},setLatLng:function(t){return this._latlng=o.latLng(t),this._map&&(this._updatePosition(),this._adjustPan()),this},getContent:function(){return this._content},setContent:function(t){return this._content=t,this.update(),this},update:function(){this._map&&(this._container.style.visibility="hidden",this._updateContent(),this._updateLayout(),this._updatePosition(),this._container.style.visibility="",this._adjustPan())},_getEvents:function(){var t={viewreset:this._updatePosition};return this._animated&&(t.zoomanim=this._zoomAnimation),("closeOnClick"in this.options?this.options.closeOnClick:this._map.options.closePopupOnClick)&&(t.preclick=this._close),this.options.keepInView&&(t.moveend=this._adjustPan),t},_close:function(){this._map&&this._map.closePopup(this)},_initLayout:function(){var t,e="leaflet-popup",i=e+" "+this.options.className+" leaflet-zoom-"+(this._animated?"animated":"hide"),n=this._container=o.DomUtil.create("div",i);this.options.closeButton&&(t=this._closeButton=o.DomUtil.create("a",e+"-close-button",n),t.href="#close",t.innerHTML="&#215;",o.DomEvent.disableClickPropagation(t),o.DomEvent.on(t,"click",this._onCloseButtonClick,this));var s=this._wrapper=o.DomUtil.create("div",e+"-content-wrapper",n);o.DomEvent.disableClickPropagation(s),this._contentNode=o.DomUtil.create("div",e+"-content",s),o.DomEvent.disableScrollPropagation(this._contentNode),o.DomEvent.on(s,"contextmenu",o.DomEvent.stopPropagation),this._tipContainer=o.DomUtil.create("div",e+"-tip-container",n),this._tip=o.DomUtil.create("div",e+"-tip",this._tipContainer)},_updateContent:function(){if(this._content){if("string"==typeof this._content)this._contentNode.innerHTML=this._content;else{for(;this._contentNode.hasChildNodes();)this._contentNode.removeChild(this._contentNode.firstChild);this._contentNode.appendChild(this._content)}this.fire("contentupdate")}},_updateLayout:function(){var t=this._contentNode,e=t.style;e.width="",e.whiteSpace="nowrap";var i=t.offsetWidth;i=Math.min(i,this.options.maxWidth),i=Math.max(i,this.options.minWidth),e.width=i+1+"px",e.whiteSpace="",e.height="";var n=t.offsetHeight,s=this.options.maxHeight,a="leaflet-popup-scrolled";s&&n>s?(e.height=s+"px",o.DomUtil.addClass(t,a)):o.DomUtil.removeClass(t,a),this._containerWidth=this._container.offsetWidth},_updatePosition:function(){if(this._map){var t=this._map.latLngToLayerPoint(this._latlng),e=this._animated,i=o.point(this.options.offset);e&&o.DomUtil.setPosition(this._container,t),this._containerBottom=-i.y-(e?0:t.y),this._containerLeft=-Math.round(this._containerWidth/2)+i.x+(e?0:t.x),this._container.style.bottom=this._containerBottom+"px",this._container.style.left=this._containerLeft+"px"}},_zoomAnimation:function(t){var e=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center);o.DomUtil.setPosition(this._container,e)},_adjustPan:function(){if(this.options.autoPan){var t=this._map,e=this._container.offsetHeight,i=this._containerWidth,n=new o.Point(this._containerLeft,-e-this._containerBottom);this._animated&&n._add(o.DomUtil.getPosition(this._container));var s=t.layerPointToContainerPoint(n),a=o.point(this.options.autoPanPadding),r=o.point(this.options.autoPanPaddingTopLeft||a),h=o.point(this.options.autoPanPaddingBottomRight||a),l=t.getSize(),u=0,c=0;s.x+i+h.x>l.x&&(u=s.x+i-l.x+h.x),s.x-u-r.x<0&&(u=s.x-r.x),s.y+e+h.y>l.y&&(c=s.y+e-l.y+h.y),s.y-c-r.y<0&&(c=s.y-r.y),(u||c)&&t.fire("autopanstart").panBy([u,c])}},_onCloseButtonClick:function(t){this._close(),o.DomEvent.stop(t)}}),o.popup=function(t,e){return new o.Popup(t,e)},o.Map.include({openPopup:function(t,e,i){if(this.closePopup(),!(t instanceof o.Popup)){var n=t;t=new o.Popup(i).setLatLng(e).setContent(n)}return t._isOpen=!0,this._popup=t,this.addLayer(t)},closePopup:function(t){return t&&t!==this._popup||(t=this._popup,this._popup=null),t&&(this.removeLayer(t),t._isOpen=!1),this}}),o.Marker.include({openPopup:function(){return this._popup&&this._map&&!this._map.hasLayer(this._popup)&&(this._popup.setLatLng(this._latlng),this._map.openPopup(this._popup)),this},closePopup:function(){return this._popup&&this._popup._close(),this},togglePopup:function(){return this._popup&&(this._popup._isOpen?this.closePopup():this.openPopup()),this},bindPopup:function(t,e){var i=o.point(this.options.icon.options.popupAnchor||[0,0]);return i=i.add(o.Popup.prototype.options.offset),e&&e.offset&&(i=i.add(e.offset)),e=o.extend({offset:i},e),this._popupHandlersAdded||(this.on("click",this.togglePopup,this).on("remove",this.closePopup,this).on("move",this._movePopup,this),this._popupHandlersAdded=!0),t instanceof o.Popup?(o.setOptions(t,e),this._popup=t):this._popup=new o.Popup(e,this).setContent(t),this},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},unbindPopup:function(){return this._popup&&(this._popup=null,this.off("click",this.togglePopup,this).off("remove",this.closePopup,this).off("move",this._movePopup,this),this._popupHandlersAdded=!1),this},getPopup:function(){return this._popup},_movePopup:function(t){this._popup.setLatLng(t.latlng)}}),o.LayerGroup=o.Class.extend({initialize:function(t){this._layers={};var e,i;if(t)for(e=0,i=t.length;i>e;e++)this.addLayer(t[e])},addLayer:function(t){var e=this.getLayerId(t);return this._layers[e]=t,this._map&&this._map.addLayer(t),this},removeLayer:function(t){var e=t in this._layers?t:this.getLayerId(t);return this._map&&this._layers[e]&&this._map.removeLayer(this._layers[e]),delete this._layers[e],this},hasLayer:function(t){return t?t in this._layers||this.getLayerId(t)in this._layers:!1},clearLayers:function(){return this.eachLayer(this.removeLayer,this),this},invoke:function(t){var e,i,n=Array.prototype.slice.call(arguments,1);for(e in this._layers)i=this._layers[e],i[t]&&i[t].apply(i,n);return this},onAdd:function(t){this._map=t,this.eachLayer(t.addLayer,t)},onRemove:function(t){this.eachLayer(t.removeLayer,t),this._map=null},addTo:function(t){return t.addLayer(this),this},eachLayer:function(t,e){for(var i in this._layers)t.call(e,this._layers[i]);return this},getLayer:function(t){return this._layers[t]},getLayers:function(){var t=[];for(var e in this._layers)t.push(this._layers[e]);return t},setZIndex:function(t){return this.invoke("setZIndex",t)},getLayerId:function(t){return o.stamp(t)}}),o.layerGroup=function(t){return new o.LayerGroup(t)},o.FeatureGroup=o.LayerGroup.extend({includes:o.Mixin.Events,statics:{EVENTS:"click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose"},addLayer:function(t){return this.hasLayer(t)?this:("on"in t&&t.on(o.FeatureGroup.EVENTS,this._propagateEvent,this),o.LayerGroup.prototype.addLayer.call(this,t),this._popupContent&&t.bindPopup&&t.bindPopup(this._popupContent,this._popupOptions),this.fire("layeradd",{layer:t}))},removeLayer:function(t){return this.hasLayer(t)?(t in this._layers&&(t=this._layers[t]),t.off(o.FeatureGroup.EVENTS,this._propagateEvent,this),o.LayerGroup.prototype.removeLayer.call(this,t),this._popupContent&&this.invoke("unbindPopup"),this.fire("layerremove",{layer:t})):this},bindPopup:function(t,e){return this._popupContent=t,this._popupOptions=e,this.invoke("bindPopup",t,e)},openPopup:function(t){for(var e in this._layers){this._layers[e].openPopup(t);break}return this},setStyle:function(t){return this.invoke("setStyle",t)},bringToFront:function(){return this.invoke("bringToFront")},bringToBack:function(){return this.invoke("bringToBack")},getBounds:function(){var t=new o.LatLngBounds;return this.eachLayer(function(e){t.extend(e instanceof o.Marker?e.getLatLng():e.getBounds())}),t},_propagateEvent:function(t){t=o.extend({layer:t.target,target:this},t),this.fire(t.type,t)}}),o.featureGroup=function(t){return new o.FeatureGroup(t)},o.Path=o.Class.extend({includes:[o.Mixin.Events],statics:{CLIP_PADDING:function(){var e=o.Browser.mobile?1280:2e3,i=(e/Math.max(t.outerWidth,t.outerHeight)-1)/2;return Math.max(0,Math.min(.5,i))}()},options:{stroke:!0,color:"#0033ff",dashArray:null,lineCap:null,lineJoin:null,weight:5,opacity:.5,fill:!1,fillColor:null,fillOpacity:.2,clickable:!0},initialize:function(t){o.setOptions(this,t)},onAdd:function(t){this._map=t,this._container||(this._initElements(),this._initEvents()),this.projectLatlngs(),this._updatePath(),this._container&&this._map._pathRoot.appendChild(this._container),this.fire("add"),t.on({viewreset:this.projectLatlngs,moveend:this._updatePath},this)},addTo:function(t){return t.addLayer(this),this},onRemove:function(t){t._pathRoot.removeChild(this._container),this.fire("remove"),this._map=null,o.Browser.vml&&(this._container=null,this._stroke=null,this._fill=null),t.off({viewreset:this.projectLatlngs,moveend:this._updatePath},this)},projectLatlngs:function(){},setStyle:function(t){return o.setOptions(this,t),this._container&&this._updateStyle(),this},redraw:function(){return this._map&&(this.projectLatlngs(),this._updatePath()),this}}),o.Map.include({_updatePathViewport:function(){var t=o.Path.CLIP_PADDING,e=this.getSize(),i=o.DomUtil.getPosition(this._mapPane),n=i.multiplyBy(-1)._subtract(e.multiplyBy(t)._round()),s=n.add(e.multiplyBy(1+2*t)._round());this._pathViewport=new o.Bounds(n,s)}}),o.Path.SVG_NS="http://www.w3.org/2000/svg",o.Browser.svg=!(!e.createElementNS||!e.createElementNS(o.Path.SVG_NS,"svg").createSVGRect),o.Path=o.Path.extend({statics:{SVG:o.Browser.svg},bringToFront:function(){var t=this._map._pathRoot,e=this._container;return e&&t.lastChild!==e&&t.appendChild(e),this},bringToBack:function(){var t=this._map._pathRoot,e=this._container,i=t.firstChild;return e&&i!==e&&t.insertBefore(e,i),this},getPathString:function(){},_createElement:function(t){return e.createElementNS(o.Path.SVG_NS,t)},_initElements:function(){this._map._initPathRoot(),this._initPath(),this._initStyle()},_initPath:function(){this._container=this._createElement("g"),this._path=this._createElement("path"),this.options.className&&o.DomUtil.addClass(this._path,this.options.className),this._container.appendChild(this._path)},_initStyle:function(){this.options.stroke&&(this._path.setAttribute("stroke-linejoin","round"),this._path.setAttribute("stroke-linecap","round")),this.options.fill&&this._path.setAttribute("fill-rule","evenodd"),this.options.pointerEvents&&this._path.setAttribute("pointer-events",this.options.pointerEvents),this.options.clickable||this.options.pointerEvents||this._path.setAttribute("pointer-events","none"),this._updateStyle()},_updateStyle:function(){this.options.stroke?(this._path.setAttribute("stroke",this.options.color),this._path.setAttribute("stroke-opacity",this.options.opacity),this._path.setAttribute("stroke-width",this.options.weight),this.options.dashArray?this._path.setAttribute("stroke-dasharray",this.options.dashArray):this._path.removeAttribute("stroke-dasharray"),this.options.lineCap&&this._path.setAttribute("stroke-linecap",this.options.lineCap),this.options.lineJoin&&this._path.setAttribute("stroke-linejoin",this.options.lineJoin)):this._path.setAttribute("stroke","none"),this.options.fill?(this._path.setAttribute("fill",this.options.fillColor||this.options.color),this._path.setAttribute("fill-opacity",this.options.fillOpacity)):this._path.setAttribute("fill","none")},_updatePath:function(){var t=this.getPathString();t||(t="M0 0"),this._path.setAttribute("d",t)},_initEvents:function(){if(this.options.clickable){(o.Browser.svg||!o.Browser.vml)&&o.DomUtil.addClass(this._path,"leaflet-clickable"),o.DomEvent.on(this._container,"click",this._onMouseClick,this);for(var t=["dblclick","mousedown","mouseover","mouseout","mousemove","contextmenu"],e=0;e<t.length;e++)o.DomEvent.on(this._container,t[e],this._fireMouseEvent,this)}},_onMouseClick:function(t){this._map.dragging&&this._map.dragging.moved()||this._fireMouseEvent(t)},_fireMouseEvent:function(t){if(this.hasEventListeners(t.type)){var e=this._map,i=e.mouseEventToContainerPoint(t),n=e.containerPointToLayerPoint(i),s=e.layerPointToLatLng(n);this.fire(t.type,{latlng:s,layerPoint:n,containerPoint:i,originalEvent:t}),"contextmenu"===t.type&&o.DomEvent.preventDefault(t),"mousemove"!==t.type&&o.DomEvent.stopPropagation(t)}}}),o.Map.include({_initPathRoot:function(){this._pathRoot||(this._pathRoot=o.Path.prototype._createElement("svg"),this._panes.overlayPane.appendChild(this._pathRoot),this.options.zoomAnimation&&o.Browser.any3d?(o.DomUtil.addClass(this._pathRoot,"leaflet-zoom-animated"),this.on({zoomanim:this._animatePathZoom,zoomend:this._endPathZoom})):o.DomUtil.addClass(this._pathRoot,"leaflet-zoom-hide"),this.on("moveend",this._updateSvgViewport),this._updateSvgViewport())
+},_animatePathZoom:function(t){var e=this.getZoomScale(t.zoom),i=this._getCenterOffset(t.center)._multiplyBy(-e)._add(this._pathViewport.min);this._pathRoot.style[o.DomUtil.TRANSFORM]=o.DomUtil.getTranslateString(i)+" scale("+e+") ",this._pathZooming=!0},_endPathZoom:function(){this._pathZooming=!1},_updateSvgViewport:function(){if(!this._pathZooming){this._updatePathViewport();var t=this._pathViewport,e=t.min,i=t.max,n=i.x-e.x,s=i.y-e.y,a=this._pathRoot,r=this._panes.overlayPane;o.Browser.mobileWebkit&&r.removeChild(a),o.DomUtil.setPosition(a,e),a.setAttribute("width",n),a.setAttribute("height",s),a.setAttribute("viewBox",[e.x,e.y,n,s].join(" ")),o.Browser.mobileWebkit&&r.appendChild(a)}}}),o.Path.include({bindPopup:function(t,e){return t instanceof o.Popup?this._popup=t:((!this._popup||e)&&(this._popup=new o.Popup(e,this)),this._popup.setContent(t)),this._popupHandlersAdded||(this.on("click",this._openPopup,this).on("remove",this.closePopup,this),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this._popup=null,this.off("click",this._openPopup).off("remove",this.closePopup),this._popupHandlersAdded=!1),this},openPopup:function(t){return this._popup&&(t=t||this._latlng||this._latlngs[Math.floor(this._latlngs.length/2)],this._openPopup({latlng:t})),this},closePopup:function(){return this._popup&&this._popup._close(),this},_openPopup:function(t){this._popup.setLatLng(t.latlng),this._map.openPopup(this._popup)}}),o.Browser.vml=!o.Browser.svg&&function(){try{var t=e.createElement("div");t.innerHTML='<v:shape adj="1"/>';var i=t.firstChild;return i.style.behavior="url(#default#VML)",i&&"object"==typeof i.adj}catch(n){return!1}}(),o.Path=o.Browser.svg||!o.Browser.vml?o.Path:o.Path.extend({statics:{VML:!0,CLIP_PADDING:.02},_createElement:function(){try{return e.namespaces.add("lvml","urn:schemas-microsoft-com:vml"),function(t){return e.createElement("<lvml:"+t+' class="lvml">')}}catch(t){return function(t){return e.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}}(),_initPath:function(){var t=this._container=this._createElement("shape");o.DomUtil.addClass(t,"leaflet-vml-shape"+(this.options.className?" "+this.options.className:"")),this.options.clickable&&o.DomUtil.addClass(t,"leaflet-clickable"),t.coordsize="1 1",this._path=this._createElement("path"),t.appendChild(this._path),this._map._pathRoot.appendChild(t)},_initStyle:function(){this._updateStyle()},_updateStyle:function(){var t=this._stroke,e=this._fill,i=this.options,n=this._container;n.stroked=i.stroke,n.filled=i.fill,i.stroke?(t||(t=this._stroke=this._createElement("stroke"),t.endcap="round",n.appendChild(t)),t.weight=i.weight+"px",t.color=i.color,t.opacity=i.opacity,t.dashStyle=i.dashArray?o.Util.isArray(i.dashArray)?i.dashArray.join(" "):i.dashArray.replace(/( *, *)/g," "):"",i.lineCap&&(t.endcap=i.lineCap.replace("butt","flat")),i.lineJoin&&(t.joinstyle=i.lineJoin)):t&&(n.removeChild(t),this._stroke=null),i.fill?(e||(e=this._fill=this._createElement("fill"),n.appendChild(e)),e.color=i.fillColor||i.color,e.opacity=i.fillOpacity):e&&(n.removeChild(e),this._fill=null)},_updatePath:function(){var t=this._container.style;t.display="none",this._path.v=this.getPathString()+" ",t.display=""}}),o.Map.include(o.Browser.svg||!o.Browser.vml?{}:{_initPathRoot:function(){if(!this._pathRoot){var t=this._pathRoot=e.createElement("div");t.className="leaflet-vml-container",this._panes.overlayPane.appendChild(t),this.on("moveend",this._updatePathViewport),this._updatePathViewport()}}}),o.Browser.canvas=function(){return!!e.createElement("canvas").getContext}(),o.Path=o.Path.SVG&&!t.L_PREFER_CANVAS||!o.Browser.canvas?o.Path:o.Path.extend({statics:{CANVAS:!0,SVG:!1},redraw:function(){return this._map&&(this.projectLatlngs(),this._requestUpdate()),this},setStyle:function(t){return o.setOptions(this,t),this._map&&(this._updateStyle(),this._requestUpdate()),this},onRemove:function(t){t.off("viewreset",this.projectLatlngs,this).off("moveend",this._updatePath,this),this.options.clickable&&(this._map.off("click",this._onClick,this),this._map.off("mousemove",this._onMouseMove,this)),this._requestUpdate(),this.fire("remove"),this._map=null},_requestUpdate:function(){this._map&&!o.Path._updateRequest&&(o.Path._updateRequest=o.Util.requestAnimFrame(this._fireMapMoveEnd,this._map))},_fireMapMoveEnd:function(){o.Path._updateRequest=null,this.fire("moveend")},_initElements:function(){this._map._initPathRoot(),this._ctx=this._map._canvasCtx},_updateStyle:function(){var t=this.options;t.stroke&&(this._ctx.lineWidth=t.weight,this._ctx.strokeStyle=t.color),t.fill&&(this._ctx.fillStyle=t.fillColor||t.color)},_drawPath:function(){var t,e,i,n,s,a;for(this._ctx.beginPath(),t=0,i=this._parts.length;i>t;t++){for(e=0,n=this._parts[t].length;n>e;e++)s=this._parts[t][e],a=(0===e?"move":"line")+"To",this._ctx[a](s.x,s.y);this instanceof o.Polygon&&this._ctx.closePath()}},_checkIfEmpty:function(){return!this._parts.length},_updatePath:function(){if(!this._checkIfEmpty()){var t=this._ctx,e=this.options;this._drawPath(),t.save(),this._updateStyle(),e.fill&&(t.globalAlpha=e.fillOpacity,t.fill()),e.stroke&&(t.globalAlpha=e.opacity,t.stroke()),t.restore()}},_initEvents:function(){this.options.clickable&&(this._map.on("mousemove",this._onMouseMove,this),this._map.on("click",this._onClick,this))},_onClick:function(t){this._containsPoint(t.layerPoint)&&this.fire("click",t)},_onMouseMove:function(t){this._map&&!this._map._animatingZoom&&(this._containsPoint(t.layerPoint)?(this._ctx.canvas.style.cursor="pointer",this._mouseInside=!0,this.fire("mouseover",t)):this._mouseInside&&(this._ctx.canvas.style.cursor="",this._mouseInside=!1,this.fire("mouseout",t)))}}),o.Map.include(o.Path.SVG&&!t.L_PREFER_CANVAS||!o.Browser.canvas?{}:{_initPathRoot:function(){var t,i=this._pathRoot;i||(i=this._pathRoot=e.createElement("canvas"),i.style.position="absolute",t=this._canvasCtx=i.getContext("2d"),t.lineCap="round",t.lineJoin="round",this._panes.overlayPane.appendChild(i),this.options.zoomAnimation&&(this._pathRoot.className="leaflet-zoom-animated",this.on("zoomanim",this._animatePathZoom),this.on("zoomend",this._endPathZoom)),this.on("moveend",this._updateCanvasViewport),this._updateCanvasViewport())},_updateCanvasViewport:function(){if(!this._pathZooming){this._updatePathViewport();var t=this._pathViewport,e=t.min,i=t.max.subtract(e),n=this._pathRoot;o.DomUtil.setPosition(n,e),n.width=i.x,n.height=i.y,n.getContext("2d").translate(-e.x,-e.y)}}}),o.LineUtil={simplify:function(t,e){if(!e||!t.length)return t.slice();var i=e*e;return t=this._reducePoints(t,i),t=this._simplifyDP(t,i)},pointToSegmentDistance:function(t,e,i){return Math.sqrt(this._sqClosestPointOnSegment(t,e,i,!0))},closestPointOnSegment:function(t,e,i){return this._sqClosestPointOnSegment(t,e,i)},_simplifyDP:function(t,e){var n=t.length,o=typeof Uint8Array!=i+""?Uint8Array:Array,s=new o(n);s[0]=s[n-1]=1,this._simplifyDPStep(t,s,e,0,n-1);var a,r=[];for(a=0;n>a;a++)s[a]&&r.push(t[a]);return r},_simplifyDPStep:function(t,e,i,n,o){var s,a,r,h=0;for(a=n+1;o-1>=a;a++)r=this._sqClosestPointOnSegment(t[a],t[n],t[o],!0),r>h&&(s=a,h=r);h>i&&(e[s]=1,this._simplifyDPStep(t,e,i,n,s),this._simplifyDPStep(t,e,i,s,o))},_reducePoints:function(t,e){for(var i=[t[0]],n=1,o=0,s=t.length;s>n;n++)this._sqDist(t[n],t[o])>e&&(i.push(t[n]),o=n);return s-1>o&&i.push(t[s-1]),i},clipSegment:function(t,e,i,n){var o,s,a,r=n?this._lastCode:this._getBitCode(t,i),h=this._getBitCode(e,i);for(this._lastCode=h;;){if(!(r|h))return[t,e];if(r&h)return!1;o=r||h,s=this._getEdgeIntersection(t,e,o,i),a=this._getBitCode(s,i),o===r?(t=s,r=a):(e=s,h=a)}},_getEdgeIntersection:function(t,e,i,n){var s=e.x-t.x,a=e.y-t.y,r=n.min,h=n.max;return 8&i?new o.Point(t.x+s*(h.y-t.y)/a,h.y):4&i?new o.Point(t.x+s*(r.y-t.y)/a,r.y):2&i?new o.Point(h.x,t.y+a*(h.x-t.x)/s):1&i?new o.Point(r.x,t.y+a*(r.x-t.x)/s):void 0},_getBitCode:function(t,e){var i=0;return t.x<e.min.x?i|=1:t.x>e.max.x&&(i|=2),t.y<e.min.y?i|=4:t.y>e.max.y&&(i|=8),i},_sqDist:function(t,e){var i=e.x-t.x,n=e.y-t.y;return i*i+n*n},_sqClosestPointOnSegment:function(t,e,i,n){var s,a=e.x,r=e.y,h=i.x-a,l=i.y-r,u=h*h+l*l;return u>0&&(s=((t.x-a)*h+(t.y-r)*l)/u,s>1?(a=i.x,r=i.y):s>0&&(a+=h*s,r+=l*s)),h=t.x-a,l=t.y-r,n?h*h+l*l:new o.Point(a,r)}},o.Polyline=o.Path.extend({initialize:function(t,e){o.Path.prototype.initialize.call(this,e),this._latlngs=this._convertLatLngs(t)},options:{smoothFactor:1,noClip:!1},projectLatlngs:function(){this._originalPoints=[];for(var t=0,e=this._latlngs.length;e>t;t++)this._originalPoints[t]=this._map.latLngToLayerPoint(this._latlngs[t])},getPathString:function(){for(var t=0,e=this._parts.length,i="";e>t;t++)i+=this._getPathPartStr(this._parts[t]);return i},getLatLngs:function(){return this._latlngs},setLatLngs:function(t){return this._latlngs=this._convertLatLngs(t),this.redraw()},addLatLng:function(t){return this._latlngs.push(o.latLng(t)),this.redraw()},spliceLatLngs:function(){var t=[].splice.apply(this._latlngs,arguments);return this._convertLatLngs(this._latlngs,!0),this.redraw(),t},closestLayerPoint:function(t){for(var e,i,n=1/0,s=this._parts,a=null,r=0,h=s.length;h>r;r++)for(var l=s[r],u=1,c=l.length;c>u;u++){e=l[u-1],i=l[u];var d=o.LineUtil._sqClosestPointOnSegment(t,e,i,!0);n>d&&(n=d,a=o.LineUtil._sqClosestPointOnSegment(t,e,i))}return a&&(a.distance=Math.sqrt(n)),a},getBounds:function(){return new o.LatLngBounds(this.getLatLngs())},_convertLatLngs:function(t,e){var i,n,s=e?t:[];for(i=0,n=t.length;n>i;i++){if(o.Util.isArray(t[i])&&"number"!=typeof t[i][0])return;s[i]=o.latLng(t[i])}return s},_initEvents:function(){o.Path.prototype._initEvents.call(this)},_getPathPartStr:function(t){for(var e,i=o.Path.VML,n=0,s=t.length,a="";s>n;n++)e=t[n],i&&e._round(),a+=(n?"L":"M")+e.x+" "+e.y;return a},_clipPoints:function(){var t,e,i,n=this._originalPoints,s=n.length;if(this.options.noClip)return void(this._parts=[n]);this._parts=[];var a=this._parts,r=this._map._pathViewport,h=o.LineUtil;for(t=0,e=0;s-1>t;t++)i=h.clipSegment(n[t],n[t+1],r,t),i&&(a[e]=a[e]||[],a[e].push(i[0]),(i[1]!==n[t+1]||t===s-2)&&(a[e].push(i[1]),e++))},_simplifyPoints:function(){for(var t=this._parts,e=o.LineUtil,i=0,n=t.length;n>i;i++)t[i]=e.simplify(t[i],this.options.smoothFactor)},_updatePath:function(){this._map&&(this._clipPoints(),this._simplifyPoints(),o.Path.prototype._updatePath.call(this))}}),o.polyline=function(t,e){return new o.Polyline(t,e)},o.PolyUtil={},o.PolyUtil.clipPolygon=function(t,e){var i,n,s,a,r,h,l,u,c,d=[1,4,2,8],p=o.LineUtil;for(n=0,l=t.length;l>n;n++)t[n]._code=p._getBitCode(t[n],e);for(a=0;4>a;a++){for(u=d[a],i=[],n=0,l=t.length,s=l-1;l>n;s=n++)r=t[n],h=t[s],r._code&u?h._code&u||(c=p._getEdgeIntersection(h,r,u,e),c._code=p._getBitCode(c,e),i.push(c)):(h._code&u&&(c=p._getEdgeIntersection(h,r,u,e),c._code=p._getBitCode(c,e),i.push(c)),i.push(r));t=i}return t},o.Polygon=o.Polyline.extend({options:{fill:!0},initialize:function(t,e){o.Polyline.prototype.initialize.call(this,t,e),this._initWithHoles(t)},_initWithHoles:function(t){var e,i,n;if(t&&o.Util.isArray(t[0])&&"number"!=typeof t[0][0])for(this._latlngs=this._convertLatLngs(t[0]),this._holes=t.slice(1),e=0,i=this._holes.length;i>e;e++)n=this._holes[e]=this._convertLatLngs(this._holes[e]),n[0].equals(n[n.length-1])&&n.pop();t=this._latlngs,t.length>=2&&t[0].equals(t[t.length-1])&&t.pop()},projectLatlngs:function(){if(o.Polyline.prototype.projectLatlngs.call(this),this._holePoints=[],this._holes){var t,e,i,n;for(t=0,i=this._holes.length;i>t;t++)for(this._holePoints[t]=[],e=0,n=this._holes[t].length;n>e;e++)this._holePoints[t][e]=this._map.latLngToLayerPoint(this._holes[t][e])}},setLatLngs:function(t){return t&&o.Util.isArray(t[0])&&"number"!=typeof t[0][0]?(this._initWithHoles(t),this.redraw()):o.Polyline.prototype.setLatLngs.call(this,t)},_clipPoints:function(){var t=this._originalPoints,e=[];if(this._parts=[t].concat(this._holePoints),!this.options.noClip){for(var i=0,n=this._parts.length;n>i;i++){var s=o.PolyUtil.clipPolygon(this._parts[i],this._map._pathViewport);s.length&&e.push(s)}this._parts=e}},_getPathPartStr:function(t){var e=o.Polyline.prototype._getPathPartStr.call(this,t);return e+(o.Browser.svg?"z":"x")}}),o.polygon=function(t,e){return new o.Polygon(t,e)},function(){function t(t){return o.FeatureGroup.extend({initialize:function(t,e){this._layers={},this._options=e,this.setLatLngs(t)},setLatLngs:function(e){var i=0,n=e.length;for(this.eachLayer(function(t){n>i?t.setLatLngs(e[i++]):this.removeLayer(t)},this);n>i;)this.addLayer(new t(e[i++],this._options));return this},getLatLngs:function(){var t=[];return this.eachLayer(function(e){t.push(e.getLatLngs())}),t}})}o.MultiPolyline=t(o.Polyline),o.MultiPolygon=t(o.Polygon),o.multiPolyline=function(t,e){return new o.MultiPolyline(t,e)},o.multiPolygon=function(t,e){return new o.MultiPolygon(t,e)}}(),o.Rectangle=o.Polygon.extend({initialize:function(t,e){o.Polygon.prototype.initialize.call(this,this._boundsToLatLngs(t),e)},setBounds:function(t){this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return t=o.latLngBounds(t),[t.getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}}),o.rectangle=function(t,e){return new o.Rectangle(t,e)},o.Circle=o.Path.extend({initialize:function(t,e,i){o.Path.prototype.initialize.call(this,i),this._latlng=o.latLng(t),this._mRadius=e},options:{fill:!0},setLatLng:function(t){return this._latlng=o.latLng(t),this.redraw()},setRadius:function(t){return this._mRadius=t,this.redraw()},projectLatlngs:function(){var t=this._getLngRadius(),e=this._latlng,i=this._map.latLngToLayerPoint([e.lat,e.lng-t]);this._point=this._map.latLngToLayerPoint(e),this._radius=Math.max(this._point.x-i.x,1)},getBounds:function(){var t=this._getLngRadius(),e=this._mRadius/40075017*360,i=this._latlng;return new o.LatLngBounds([i.lat-e,i.lng-t],[i.lat+e,i.lng+t])},getLatLng:function(){return this._latlng},getPathString:function(){var t=this._point,e=this._radius;return this._checkIfEmpty()?"":o.Browser.svg?"M"+t.x+","+(t.y-e)+"A"+e+","+e+",0,1,1,"+(t.x-.1)+","+(t.y-e)+" z":(t._round(),e=Math.round(e),"AL "+t.x+","+t.y+" "+e+","+e+" 0,23592600")},getRadius:function(){return this._mRadius},_getLatRadius:function(){return this._mRadius/40075017*360},_getLngRadius:function(){return this._getLatRadius()/Math.cos(o.LatLng.DEG_TO_RAD*this._latlng.lat)},_checkIfEmpty:function(){if(!this._map)return!1;var t=this._map._pathViewport,e=this._radius,i=this._point;return i.x-e>t.max.x||i.y-e>t.max.y||i.x+e<t.min.x||i.y+e<t.min.y}}),o.circle=function(t,e,i){return new o.Circle(t,e,i)},o.CircleMarker=o.Circle.extend({options:{radius:10,weight:2},initialize:function(t,e){o.Circle.prototype.initialize.call(this,t,null,e),this._radius=this.options.radius},projectLatlngs:function(){this._point=this._map.latLngToLayerPoint(this._latlng)},_updateStyle:function(){o.Circle.prototype._updateStyle.call(this),this.setRadius(this.options.radius)},setLatLng:function(t){return o.Circle.prototype.setLatLng.call(this,t),this._popup&&this._popup._isOpen&&this._popup.setLatLng(t),this},setRadius:function(t){return this.options.radius=this._radius=t,this.redraw()},getRadius:function(){return this._radius}}),o.circleMarker=function(t,e){return new o.CircleMarker(t,e)},o.Polyline.include(o.Path.CANVAS?{_containsPoint:function(t,e){var i,n,s,a,r,h,l,u=this.options.weight/2;for(o.Browser.touch&&(u+=10),i=0,a=this._parts.length;a>i;i++)for(l=this._parts[i],n=0,r=l.length,s=r-1;r>n;s=n++)if((e||0!==n)&&(h=o.LineUtil.pointToSegmentDistance(t,l[s],l[n]),u>=h))return!0;return!1}}:{}),o.Polygon.include(o.Path.CANVAS?{_containsPoint:function(t){var e,i,n,s,a,r,h,l,u=!1;if(o.Polyline.prototype._containsPoint.call(this,t,!0))return!0;for(s=0,h=this._parts.length;h>s;s++)for(e=this._parts[s],a=0,l=e.length,r=l-1;l>a;r=a++)i=e[a],n=e[r],i.y>t.y!=n.y>t.y&&t.x<(n.x-i.x)*(t.y-i.y)/(n.y-i.y)+i.x&&(u=!u);return u}}:{}),o.Circle.include(o.Path.CANVAS?{_drawPath:function(){var t=this._point;this._ctx.beginPath(),this._ctx.arc(t.x,t.y,this._radius,0,2*Math.PI,!1)},_containsPoint:function(t){var e=this._point,i=this.options.stroke?this.options.weight/2:0;return t.distanceTo(e)<=this._radius+i}}:{}),o.CircleMarker.include(o.Path.CANVAS?{_updateStyle:function(){o.Path.prototype._updateStyle.call(this)}}:{}),o.GeoJSON=o.FeatureGroup.extend({initialize:function(t,e){o.setOptions(this,e),this._layers={},t&&this.addData(t)},addData:function(t){var e,i,n,s=o.Util.isArray(t)?t:t.features;if(s){for(e=0,i=s.length;i>e;e++)n=s[e],(n.geometries||n.geometry||n.features||n.coordinates)&&this.addData(s[e]);return this}var a=this.options;if(!a.filter||a.filter(t)){var r=o.GeoJSON.geometryToLayer(t,a.pointToLayer,a.coordsToLatLng,a);return r.feature=o.GeoJSON.asFeature(t),r.defaultOptions=r.options,this.resetStyle(r),a.onEachFeature&&a.onEachFeature(t,r),this.addLayer(r)}},resetStyle:function(t){var e=this.options.style;e&&(o.Util.extend(t.options,t.defaultOptions),this._setLayerStyle(t,e))},setStyle:function(t){this.eachLayer(function(e){this._setLayerStyle(e,t)},this)},_setLayerStyle:function(t,e){"function"==typeof e&&(e=e(t.feature)),t.setStyle&&t.setStyle(e)}}),o.extend(o.GeoJSON,{geometryToLayer:function(t,e,i,n){var s,a,r,h,l="Feature"===t.type?t.geometry:t,u=l.coordinates,c=[];switch(i=i||this.coordsToLatLng,l.type){case"Point":return s=i(u),e?e(t,s):new o.Marker(s);case"MultiPoint":for(r=0,h=u.length;h>r;r++)s=i(u[r]),c.push(e?e(t,s):new o.Marker(s));return new o.FeatureGroup(c);case"LineString":return a=this.coordsToLatLngs(u,0,i),new o.Polyline(a,n);case"Polygon":if(2===u.length&&!u[1].length)throw new Error("Invalid GeoJSON object.");return a=this.coordsToLatLngs(u,1,i),new o.Polygon(a,n);case"MultiLineString":return a=this.coordsToLatLngs(u,1,i),new o.MultiPolyline(a,n);case"MultiPolygon":return a=this.coordsToLatLngs(u,2,i),new o.MultiPolygon(a,n);case"GeometryCollection":for(r=0,h=l.geometries.length;h>r;r++)c.push(this.geometryToLayer({geometry:l.geometries[r],type:"Feature",properties:t.properties},e,i,n));return new o.FeatureGroup(c);default:throw new Error("Invalid GeoJSON object.")}},coordsToLatLng:function(t){return new o.LatLng(t[1],t[0],t[2])},coordsToLatLngs:function(t,e,i){var n,o,s,a=[];for(o=0,s=t.length;s>o;o++)n=e?this.coordsToLatLngs(t[o],e-1,i):(i||this.coordsToLatLng)(t[o]),a.push(n);return a},latLngToCoords:function(t){var e=[t.lng,t.lat];return t.alt!==i&&e.push(t.alt),e},latLngsToCoords:function(t){for(var e=[],i=0,n=t.length;n>i;i++)e.push(o.GeoJSON.latLngToCoords(t[i]));return e},getFeature:function(t,e){return t.feature?o.extend({},t.feature,{geometry:e}):o.GeoJSON.asFeature(e)},asFeature:function(t){return"Feature"===t.type?t:{type:"Feature",properties:{},geometry:t}}});var a={toGeoJSON:function(){return o.GeoJSON.getFeature(this,{type:"Point",coordinates:o.GeoJSON.latLngToCoords(this.getLatLng())})}};o.Marker.include(a),o.Circle.include(a),o.CircleMarker.include(a),o.Polyline.include({toGeoJSON:function(){return o.GeoJSON.getFeature(this,{type:"LineString",coordinates:o.GeoJSON.latLngsToCoords(this.getLatLngs())})}}),o.Polygon.include({toGeoJSON:function(){var t,e,i,n=[o.GeoJSON.latLngsToCoords(this.getLatLngs())];if(n[0].push(n[0][0]),this._holes)for(t=0,e=this._holes.length;e>t;t++)i=o.GeoJSON.latLngsToCoords(this._holes[t]),i.push(i[0]),n.push(i);return o.GeoJSON.getFeature(this,{type:"Polygon",coordinates:n})}}),function(){function t(t){return function(){var e=[];return this.eachLayer(function(t){e.push(t.toGeoJSON().geometry.coordinates)}),o.GeoJSON.getFeature(this,{type:t,coordinates:e})}}o.MultiPolyline.include({toGeoJSON:t("MultiLineString")}),o.MultiPolygon.include({toGeoJSON:t("MultiPolygon")}),o.LayerGroup.include({toGeoJSON:function(){var e,i=this.feature&&this.feature.geometry,n=[];if(i&&"MultiPoint"===i.type)return t("MultiPoint").call(this);var s=i&&"GeometryCollection"===i.type;return this.eachLayer(function(t){t.toGeoJSON&&(e=t.toGeoJSON(),n.push(s?e.geometry:o.GeoJSON.asFeature(e)))}),s?o.GeoJSON.getFeature(this,{geometries:n,type:"GeometryCollection"}):{type:"FeatureCollection",features:n}}})}(),o.geoJson=function(t,e){return new o.GeoJSON(t,e)},o.DomEvent={addListener:function(t,e,i,n){var s,a,r,h=o.stamp(i),l="_leaflet_"+e+h;return t[l]?this:(s=function(e){return i.call(n||t,e||o.DomEvent._getEvent())},o.Browser.pointer&&0===e.indexOf("touch")?this.addPointerListener(t,e,s,h):(o.Browser.touch&&"dblclick"===e&&this.addDoubleTapListener&&this.addDoubleTapListener(t,s,h),"addEventListener"in t?"mousewheel"===e?(t.addEventListener("DOMMouseScroll",s,!1),t.addEventListener(e,s,!1)):"mouseenter"===e||"mouseleave"===e?(a=s,r="mouseenter"===e?"mouseover":"mouseout",s=function(e){return o.DomEvent._checkMouse(t,e)?a(e):void 0},t.addEventListener(r,s,!1)):"click"===e&&o.Browser.android?(a=s,s=function(t){return o.DomEvent._filterClick(t,a)},t.addEventListener(e,s,!1)):t.addEventListener(e,s,!1):"attachEvent"in t&&t.attachEvent("on"+e,s),t[l]=s,this))},removeListener:function(t,e,i){var n=o.stamp(i),s="_leaflet_"+e+n,a=t[s];return a?(o.Browser.pointer&&0===e.indexOf("touch")?this.removePointerListener(t,e,n):o.Browser.touch&&"dblclick"===e&&this.removeDoubleTapListener?this.removeDoubleTapListener(t,n):"removeEventListener"in t?"mousewheel"===e?(t.removeEventListener("DOMMouseScroll",a,!1),t.removeEventListener(e,a,!1)):"mouseenter"===e||"mouseleave"===e?t.removeEventListener("mouseenter"===e?"mouseover":"mouseout",a,!1):t.removeEventListener(e,a,!1):"detachEvent"in t&&t.detachEvent("on"+e,a),t[s]=null,this):this},stopPropagation:function(t){return t.stopPropagation?t.stopPropagation():t.cancelBubble=!0,o.DomEvent._skipped(t),this},disableScrollPropagation:function(t){var e=o.DomEvent.stopPropagation;return o.DomEvent.on(t,"mousewheel",e).on(t,"MozMousePixelScroll",e)},disableClickPropagation:function(t){for(var e=o.DomEvent.stopPropagation,i=o.Draggable.START.length-1;i>=0;i--)o.DomEvent.on(t,o.Draggable.START[i],e);return o.DomEvent.on(t,"click",o.DomEvent._fakeStop).on(t,"dblclick",e)},preventDefault:function(t){return t.preventDefault?t.preventDefault():t.returnValue=!1,this},stop:function(t){return o.DomEvent.preventDefault(t).stopPropagation(t)},getMousePosition:function(t,e){if(!e)return new o.Point(t.clientX,t.clientY);var i=e.getBoundingClientRect();return new o.Point(t.clientX-i.left-e.clientLeft,t.clientY-i.top-e.clientTop)},getWheelDelta:function(t){var e=0;return t.wheelDelta&&(e=t.wheelDelta/120),t.detail&&(e=-t.detail/3),e},_skipEvents:{},_fakeStop:function(t){o.DomEvent._skipEvents[t.type]=!0},_skipped:function(t){var e=this._skipEvents[t.type];return this._skipEvents[t.type]=!1,e},_checkMouse:function(t,e){var i=e.relatedTarget;if(!i)return!0;try{for(;i&&i!==t;)i=i.parentNode}catch(n){return!1}return i!==t},_getEvent:function(){var e=t.event;if(!e)for(var i=arguments.callee.caller;i&&(e=i.arguments[0],!e||t.Event!==e.constructor);)i=i.caller;return e},_filterClick:function(t,e){var i=t.timeStamp||t.originalEvent.timeStamp,n=o.DomEvent._lastClick&&i-o.DomEvent._lastClick;return n&&n>100&&500>n||t.target._simulatedClick&&!t._simulated?void o.DomEvent.stop(t):(o.DomEvent._lastClick=i,e(t))}},o.DomEvent.on=o.DomEvent.addListener,o.DomEvent.off=o.DomEvent.removeListener,o.Draggable=o.Class.extend({includes:o.Mixin.Events,statics:{START:o.Browser.touch?["touchstart","mousedown"]:["mousedown"],END:{mousedown:"mouseup",touchstart:"touchend",pointerdown:"touchend",MSPointerDown:"touchend"},MOVE:{mousedown:"mousemove",touchstart:"touchmove",pointerdown:"touchmove",MSPointerDown:"touchmove"}},initialize:function(t,e){this._element=t,this._dragStartTarget=e||t},enable:function(){if(!this._enabled){for(var t=o.Draggable.START.length-1;t>=0;t--)o.DomEvent.on(this._dragStartTarget,o.Draggable.START[t],this._onDown,this);this._enabled=!0}},disable:function(){if(this._enabled){for(var t=o.Draggable.START.length-1;t>=0;t--)o.DomEvent.off(this._dragStartTarget,o.Draggable.START[t],this._onDown,this);this._enabled=!1,this._moved=!1}},_onDown:function(t){if(this._moved=!1,!(t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||(o.DomEvent.stopPropagation(t),o.Draggable._disabled||(o.DomUtil.disableImageDrag(),o.DomUtil.disableTextSelection(),this._moving)))){var i=t.touches?t.touches[0]:t;this._startPoint=new o.Point(i.clientX,i.clientY),this._startPos=this._newPos=o.DomUtil.getPosition(this._element),o.DomEvent.on(e,o.Draggable.MOVE[t.type],this._onMove,this).on(e,o.Draggable.END[t.type],this._onUp,this)}},_onMove:function(t){if(t.touches&&t.touches.length>1)return void(this._moved=!0);var i=t.touches&&1===t.touches.length?t.touches[0]:t,n=new o.Point(i.clientX,i.clientY),s=n.subtract(this._startPoint);(s.x||s.y)&&(o.Browser.touch&&Math.abs(s.x)+Math.abs(s.y)<3||(o.DomEvent.preventDefault(t),this._moved||(this.fire("dragstart"),this._moved=!0,this._startPos=o.DomUtil.getPosition(this._element).subtract(s),o.DomUtil.addClass(e.body,"leaflet-dragging"),this._lastTarget=t.target||t.srcElement,o.DomUtil.addClass(this._lastTarget,"leaflet-drag-target")),this._newPos=this._startPos.add(s),this._moving=!0,o.Util.cancelAnimFrame(this._animRequest),this._animRequest=o.Util.requestAnimFrame(this._updatePosition,this,!0,this._dragStartTarget)))},_updatePosition:function(){this.fire("predrag"),o.DomUtil.setPosition(this._element,this._newPos),this.fire("drag")},_onUp:function(){o.DomUtil.removeClass(e.body,"leaflet-dragging"),this._lastTarget&&(o.DomUtil.removeClass(this._lastTarget,"leaflet-drag-target"),this._lastTarget=null);for(var t in o.Draggable.MOVE)o.DomEvent.off(e,o.Draggable.MOVE[t],this._onMove).off(e,o.Draggable.END[t],this._onUp);o.DomUtil.enableImageDrag(),o.DomUtil.enableTextSelection(),this._moved&&this._moving&&(o.Util.cancelAnimFrame(this._animRequest),this.fire("dragend",{distance:this._newPos.distanceTo(this._startPos)})),this._moving=!1}}),o.Handler=o.Class.extend({initialize:function(t){this._map=t},enable:function(){this._enabled||(this._enabled=!0,this.addHooks())},disable:function(){this._enabled&&(this._enabled=!1,this.removeHooks())},enabled:function(){return!!this._enabled}}),o.Map.mergeOptions({dragging:!0,inertia:!o.Browser.android23,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,inertiaThreshold:o.Browser.touch?32:18,easeLinearity:.25,worldCopyJump:!1}),o.Map.Drag=o.Handler.extend({addHooks:function(){if(!this._draggable){var t=this._map;this._draggable=new o.Draggable(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDrag,this),t.on("viewreset",this._onViewReset,this),t.whenReady(this._onViewReset,this))}this._draggable.enable()},removeHooks:function(){this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},_onDragStart:function(){var t=this._map;t._panAnim&&t._panAnim.stop(),t.fire("movestart").fire("dragstart"),t.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(){if(this._map.options.inertia){var t=this._lastTime=+new Date,e=this._lastPos=this._draggable._newPos;this._positions.push(e),this._times.push(t),t-this._times[0]>200&&(this._positions.shift(),this._times.shift())}this._map.fire("move").fire("drag")},_onViewReset:function(){var t=this._map.getSize()._divideBy(2),e=this._map.latLngToLayerPoint([0,0]);this._initialWorldOffset=e.subtract(t).x,this._worldWidth=this._map.project([0,180]).x},_onPreDrag:function(){var t=this._worldWidth,e=Math.round(t/2),i=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-e+i)%t+e-i,s=(n+e+i)%t-e-i,a=Math.abs(o+i)<Math.abs(s+i)?o:s;this._draggable._newPos.x=a},_onDragEnd:function(t){var e=this._map,i=e.options,n=+new Date-this._lastTime,s=!i.inertia||n>i.inertiaThreshold||!this._positions[0];if(e.fire("dragend",t),s)e.fire("moveend");else{var a=this._lastPos.subtract(this._positions[0]),r=(this._lastTime+n-this._times[0])/1e3,h=i.easeLinearity,l=a.multiplyBy(h/r),u=l.distanceTo([0,0]),c=Math.min(i.inertiaMaxSpeed,u),d=l.multiplyBy(c/u),p=c/(i.inertiaDeceleration*h),_=d.multiplyBy(-p/2).round();_.x&&_.y?(_=e._limitOffset(_,e.options.maxBounds),o.Util.requestAnimFrame(function(){e.panBy(_,{duration:p,easeLinearity:h,noMoveStart:!0})})):e.fire("moveend")}}}),o.Map.addInitHook("addHandler","dragging",o.Map.Drag),o.Map.mergeOptions({doubleClickZoom:!0}),o.Map.DoubleClickZoom=o.Handler.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick,this)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick,this)},_onDoubleClick:function(t){var e=this._map,i=e.getZoom()+(t.originalEvent.shiftKey?-1:1);"center"===e.options.doubleClickZoom?e.setZoom(i):e.setZoomAround(t.containerPoint,i)}}),o.Map.addInitHook("addHandler","doubleClickZoom",o.Map.DoubleClickZoom),o.Map.mergeOptions({scrollWheelZoom:!0}),o.Map.ScrollWheelZoom=o.Handler.extend({addHooks:function(){o.DomEvent.on(this._map._container,"mousewheel",this._onWheelScroll,this),o.DomEvent.on(this._map._container,"MozMousePixelScroll",o.DomEvent.preventDefault),this._delta=0},removeHooks:function(){o.DomEvent.off(this._map._container,"mousewheel",this._onWheelScroll),o.DomEvent.off(this._map._container,"MozMousePixelScroll",o.DomEvent.preventDefault)},_onWheelScroll:function(t){var e=o.DomEvent.getWheelDelta(t);this._delta+=e,this._lastMousePos=this._map.mouseEventToContainerPoint(t),this._startTime||(this._startTime=+new Date);var i=Math.max(40-(+new Date-this._startTime),0);clearTimeout(this._timer),this._timer=setTimeout(o.bind(this._performZoom,this),i),o.DomEvent.preventDefault(t),o.DomEvent.stopPropagation(t)},_performZoom:function(){var t=this._map,e=this._delta,i=t.getZoom();e=e>0?Math.ceil(e):Math.floor(e),e=Math.max(Math.min(e,4),-4),e=t._limitZoom(i+e)-i,this._delta=0,this._startTime=null,e&&("center"===t.options.scrollWheelZoom?t.setZoom(i+e):t.setZoomAround(this._lastMousePos,i+e))}}),o.Map.addInitHook("addHandler","scrollWheelZoom",o.Map.ScrollWheelZoom),o.extend(o.DomEvent,{_touchstart:o.Browser.msPointer?"MSPointerDown":o.Browser.pointer?"pointerdown":"touchstart",_touchend:o.Browser.msPointer?"MSPointerUp":o.Browser.pointer?"pointerup":"touchend",addDoubleTapListener:function(t,i,n){function s(t){var e;if(o.Browser.pointer?(_.push(t.pointerId),e=_.length):e=t.touches.length,!(e>1)){var i=Date.now(),n=i-(r||i);h=t.touches?t.touches[0]:t,l=n>0&&u>=n,r=i}}function a(t){if(o.Browser.pointer){var e=_.indexOf(t.pointerId);if(-1===e)return;_.splice(e,1)}if(l){if(o.Browser.pointer){var n,s={};for(var a in h)n=h[a],s[a]="function"==typeof n?n.bind(h):n;h=s}h.type="dblclick",i(h),r=null}}var r,h,l=!1,u=250,c="_leaflet_",d=this._touchstart,p=this._touchend,_=[];t[c+d+n]=s,t[c+p+n]=a;var m=o.Browser.pointer?e.documentElement:t;return t.addEventListener(d,s,!1),m.addEventListener(p,a,!1),o.Browser.pointer&&m.addEventListener(o.DomEvent.POINTER_CANCEL,a,!1),this},removeDoubleTapListener:function(t,i){var n="_leaflet_";return t.removeEventListener(this._touchstart,t[n+this._touchstart+i],!1),(o.Browser.pointer?e.documentElement:t).removeEventListener(this._touchend,t[n+this._touchend+i],!1),o.Browser.pointer&&e.documentElement.removeEventListener(o.DomEvent.POINTER_CANCEL,t[n+this._touchend+i],!1),this}}),o.extend(o.DomEvent,{POINTER_DOWN:o.Browser.msPointer?"MSPointerDown":"pointerdown",POINTER_MOVE:o.Browser.msPointer?"MSPointerMove":"pointermove",POINTER_UP:o.Browser.msPointer?"MSPointerUp":"pointerup",POINTER_CANCEL:o.Browser.msPointer?"MSPointerCancel":"pointercancel",_pointers:[],_pointerDocumentListener:!1,addPointerListener:function(t,e,i,n){switch(e){case"touchstart":return this.addPointerListenerStart(t,e,i,n);case"touchend":return this.addPointerListenerEnd(t,e,i,n);case"touchmove":return this.addPointerListenerMove(t,e,i,n);default:throw"Unknown touch event type"}},addPointerListenerStart:function(t,i,n,s){var a="_leaflet_",r=this._pointers,h=function(t){o.DomEvent.preventDefault(t);for(var e=!1,i=0;i<r.length;i++)if(r[i].pointerId===t.pointerId){e=!0;
+break}e||r.push(t),t.touches=r.slice(),t.changedTouches=[t],n(t)};if(t[a+"touchstart"+s]=h,t.addEventListener(this.POINTER_DOWN,h,!1),!this._pointerDocumentListener){var l=function(t){for(var e=0;e<r.length;e++)if(r[e].pointerId===t.pointerId){r.splice(e,1);break}};e.documentElement.addEventListener(this.POINTER_UP,l,!1),e.documentElement.addEventListener(this.POINTER_CANCEL,l,!1),this._pointerDocumentListener=!0}return this},addPointerListenerMove:function(t,e,i,n){function o(t){if(t.pointerType!==t.MSPOINTER_TYPE_MOUSE&&"mouse"!==t.pointerType||0!==t.buttons){for(var e=0;e<a.length;e++)if(a[e].pointerId===t.pointerId){a[e]=t;break}t.touches=a.slice(),t.changedTouches=[t],i(t)}}var s="_leaflet_",a=this._pointers;return t[s+"touchmove"+n]=o,t.addEventListener(this.POINTER_MOVE,o,!1),this},addPointerListenerEnd:function(t,e,i,n){var o="_leaflet_",s=this._pointers,a=function(t){for(var e=0;e<s.length;e++)if(s[e].pointerId===t.pointerId){s.splice(e,1);break}t.touches=s.slice(),t.changedTouches=[t],i(t)};return t[o+"touchend"+n]=a,t.addEventListener(this.POINTER_UP,a,!1),t.addEventListener(this.POINTER_CANCEL,a,!1),this},removePointerListener:function(t,e,i){var n="_leaflet_",o=t[n+e+i];switch(e){case"touchstart":t.removeEventListener(this.POINTER_DOWN,o,!1);break;case"touchmove":t.removeEventListener(this.POINTER_MOVE,o,!1);break;case"touchend":t.removeEventListener(this.POINTER_UP,o,!1),t.removeEventListener(this.POINTER_CANCEL,o,!1)}return this}}),o.Map.mergeOptions({touchZoom:o.Browser.touch&&!o.Browser.android23,bounceAtZoomLimits:!0}),o.Map.TouchZoom=o.Handler.extend({addHooks:function(){o.DomEvent.on(this._map._container,"touchstart",this._onTouchStart,this)},removeHooks:function(){o.DomEvent.off(this._map._container,"touchstart",this._onTouchStart,this)},_onTouchStart:function(t){var i=this._map;if(t.touches&&2===t.touches.length&&!i._animatingZoom&&!this._zooming){var n=i.mouseEventToLayerPoint(t.touches[0]),s=i.mouseEventToLayerPoint(t.touches[1]),a=i._getCenterLayerPoint();this._startCenter=n.add(s)._divideBy(2),this._startDist=n.distanceTo(s),this._moved=!1,this._zooming=!0,this._centerOffset=a.subtract(this._startCenter),i._panAnim&&i._panAnim.stop(),o.DomEvent.on(e,"touchmove",this._onTouchMove,this).on(e,"touchend",this._onTouchEnd,this),o.DomEvent.preventDefault(t)}},_onTouchMove:function(t){var e=this._map;if(t.touches&&2===t.touches.length&&this._zooming){var i=e.mouseEventToLayerPoint(t.touches[0]),n=e.mouseEventToLayerPoint(t.touches[1]);this._scale=i.distanceTo(n)/this._startDist,this._delta=i._add(n)._divideBy(2)._subtract(this._startCenter),1!==this._scale&&(e.options.bounceAtZoomLimits||!(e.getZoom()===e.getMinZoom()&&this._scale<1||e.getZoom()===e.getMaxZoom()&&this._scale>1))&&(this._moved||(o.DomUtil.addClass(e._mapPane,"leaflet-touching"),e.fire("movestart").fire("zoomstart"),this._moved=!0),o.Util.cancelAnimFrame(this._animRequest),this._animRequest=o.Util.requestAnimFrame(this._updateOnMove,this,!0,this._map._container),o.DomEvent.preventDefault(t))}},_updateOnMove:function(){var t=this._map,e=this._getScaleOrigin(),i=t.layerPointToLatLng(e),n=t.getScaleZoom(this._scale);t._animateZoom(i,n,this._startCenter,this._scale,this._delta,!1,!0)},_onTouchEnd:function(){if(!this._moved||!this._zooming)return void(this._zooming=!1);var t=this._map;this._zooming=!1,o.DomUtil.removeClass(t._mapPane,"leaflet-touching"),o.Util.cancelAnimFrame(this._animRequest),o.DomEvent.off(e,"touchmove",this._onTouchMove).off(e,"touchend",this._onTouchEnd);var i=this._getScaleOrigin(),n=t.layerPointToLatLng(i),s=t.getZoom(),a=t.getScaleZoom(this._scale)-s,r=a>0?Math.ceil(a):Math.floor(a),h=t._limitZoom(s+r),l=t.getZoomScale(h)/this._scale;t._animateZoom(n,h,i,l)},_getScaleOrigin:function(){var t=this._centerOffset.subtract(this._delta).divideBy(this._scale);return this._startCenter.add(t)}}),o.Map.addInitHook("addHandler","touchZoom",o.Map.TouchZoom),o.Map.mergeOptions({tap:!0,tapTolerance:15}),o.Map.Tap=o.Handler.extend({addHooks:function(){o.DomEvent.on(this._map._container,"touchstart",this._onDown,this)},removeHooks:function(){o.DomEvent.off(this._map._container,"touchstart",this._onDown,this)},_onDown:function(t){if(t.touches){if(o.DomEvent.preventDefault(t),this._fireClick=!0,t.touches.length>1)return this._fireClick=!1,void clearTimeout(this._holdTimeout);var i=t.touches[0],n=i.target;this._startPos=this._newPos=new o.Point(i.clientX,i.clientY),n.tagName&&"a"===n.tagName.toLowerCase()&&o.DomUtil.addClass(n,"leaflet-active"),this._holdTimeout=setTimeout(o.bind(function(){this._isTapValid()&&(this._fireClick=!1,this._onUp(),this._simulateEvent("contextmenu",i))},this),1e3),o.DomEvent.on(e,"touchmove",this._onMove,this).on(e,"touchend",this._onUp,this)}},_onUp:function(t){if(clearTimeout(this._holdTimeout),o.DomEvent.off(e,"touchmove",this._onMove,this).off(e,"touchend",this._onUp,this),this._fireClick&&t&&t.changedTouches){var i=t.changedTouches[0],n=i.target;n&&n.tagName&&"a"===n.tagName.toLowerCase()&&o.DomUtil.removeClass(n,"leaflet-active"),this._isTapValid()&&this._simulateEvent("click",i)}},_isTapValid:function(){return this._newPos.distanceTo(this._startPos)<=this._map.options.tapTolerance},_onMove:function(t){var e=t.touches[0];this._newPos=new o.Point(e.clientX,e.clientY)},_simulateEvent:function(i,n){var o=e.createEvent("MouseEvents");o._simulated=!0,n.target._simulatedClick=!0,o.initMouseEvent(i,!0,!0,t,1,n.screenX,n.screenY,n.clientX,n.clientY,!1,!1,!1,!1,0,null),n.target.dispatchEvent(o)}}),o.Browser.touch&&!o.Browser.pointer&&o.Map.addInitHook("addHandler","tap",o.Map.Tap),o.Map.mergeOptions({boxZoom:!0}),o.Map.BoxZoom=o.Handler.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._moved=!1},addHooks:function(){o.DomEvent.on(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){o.DomEvent.off(this._container,"mousedown",this._onMouseDown),this._moved=!1},moved:function(){return this._moved},_onMouseDown:function(t){return this._moved=!1,!t.shiftKey||1!==t.which&&1!==t.button?!1:(o.DomUtil.disableTextSelection(),o.DomUtil.disableImageDrag(),this._startLayerPoint=this._map.mouseEventToLayerPoint(t),void o.DomEvent.on(e,"mousemove",this._onMouseMove,this).on(e,"mouseup",this._onMouseUp,this).on(e,"keydown",this._onKeyDown,this))},_onMouseMove:function(t){this._moved||(this._box=o.DomUtil.create("div","leaflet-zoom-box",this._pane),o.DomUtil.setPosition(this._box,this._startLayerPoint),this._container.style.cursor="crosshair",this._map.fire("boxzoomstart"));var e=this._startLayerPoint,i=this._box,n=this._map.mouseEventToLayerPoint(t),s=n.subtract(e),a=new o.Point(Math.min(n.x,e.x),Math.min(n.y,e.y));o.DomUtil.setPosition(i,a),this._moved=!0,i.style.width=Math.max(0,Math.abs(s.x)-4)+"px",i.style.height=Math.max(0,Math.abs(s.y)-4)+"px"},_finish:function(){this._moved&&(this._pane.removeChild(this._box),this._container.style.cursor=""),o.DomUtil.enableTextSelection(),o.DomUtil.enableImageDrag(),o.DomEvent.off(e,"mousemove",this._onMouseMove).off(e,"mouseup",this._onMouseUp).off(e,"keydown",this._onKeyDown)},_onMouseUp:function(t){this._finish();var e=this._map,i=e.mouseEventToLayerPoint(t);if(!this._startLayerPoint.equals(i)){var n=new o.LatLngBounds(e.layerPointToLatLng(this._startLayerPoint),e.layerPointToLatLng(i));e.fitBounds(n),e.fire("boxzoomend",{boxZoomBounds:n})}},_onKeyDown:function(t){27===t.keyCode&&this._finish()}}),o.Map.addInitHook("addHandler","boxZoom",o.Map.BoxZoom),o.Map.mergeOptions({keyboard:!0,keyboardPanOffset:80,keyboardZoomOffset:1}),o.Map.Keyboard=o.Handler.extend({keyCodes:{left:[37],right:[39],down:[40],up:[38],zoomIn:[187,107,61,171],zoomOut:[189,109,173]},initialize:function(t){this._map=t,this._setPanOffset(t.options.keyboardPanOffset),this._setZoomOffset(t.options.keyboardZoomOffset)},addHooks:function(){var t=this._map._container;-1===t.tabIndex&&(t.tabIndex="0"),o.DomEvent.on(t,"focus",this._onFocus,this).on(t,"blur",this._onBlur,this).on(t,"mousedown",this._onMouseDown,this),this._map.on("focus",this._addHooks,this).on("blur",this._removeHooks,this)},removeHooks:function(){this._removeHooks();var t=this._map._container;o.DomEvent.off(t,"focus",this._onFocus,this).off(t,"blur",this._onBlur,this).off(t,"mousedown",this._onMouseDown,this),this._map.off("focus",this._addHooks,this).off("blur",this._removeHooks,this)},_onMouseDown:function(){if(!this._focused){var i=e.body,n=e.documentElement,o=i.scrollTop||n.scrollTop,s=i.scrollLeft||n.scrollLeft;this._map._container.focus(),t.scrollTo(s,o)}},_onFocus:function(){this._focused=!0,this._map.fire("focus")},_onBlur:function(){this._focused=!1,this._map.fire("blur")},_setPanOffset:function(t){var e,i,n=this._panKeys={},o=this.keyCodes;for(e=0,i=o.left.length;i>e;e++)n[o.left[e]]=[-1*t,0];for(e=0,i=o.right.length;i>e;e++)n[o.right[e]]=[t,0];for(e=0,i=o.down.length;i>e;e++)n[o.down[e]]=[0,t];for(e=0,i=o.up.length;i>e;e++)n[o.up[e]]=[0,-1*t]},_setZoomOffset:function(t){var e,i,n=this._zoomKeys={},o=this.keyCodes;for(e=0,i=o.zoomIn.length;i>e;e++)n[o.zoomIn[e]]=t;for(e=0,i=o.zoomOut.length;i>e;e++)n[o.zoomOut[e]]=-t},_addHooks:function(){o.DomEvent.on(e,"keydown",this._onKeyDown,this)},_removeHooks:function(){o.DomEvent.off(e,"keydown",this._onKeyDown,this)},_onKeyDown:function(t){var e=t.keyCode,i=this._map;if(e in this._panKeys){if(i._panAnim&&i._panAnim._inProgress)return;i.panBy(this._panKeys[e]),i.options.maxBounds&&i.panInsideBounds(i.options.maxBounds)}else{if(!(e in this._zoomKeys))return;i.setZoom(i.getZoom()+this._zoomKeys[e])}o.DomEvent.stop(t)}}),o.Map.addInitHook("addHandler","keyboard",o.Map.Keyboard),o.Handler.MarkerDrag=o.Handler.extend({initialize:function(t){this._marker=t},addHooks:function(){var t=this._marker._icon;this._draggable||(this._draggable=new o.Draggable(t,t)),this._draggable.on("dragstart",this._onDragStart,this).on("drag",this._onDrag,this).on("dragend",this._onDragEnd,this),this._draggable.enable(),o.DomUtil.addClass(this._marker._icon,"leaflet-marker-draggable")},removeHooks:function(){this._draggable.off("dragstart",this._onDragStart,this).off("drag",this._onDrag,this).off("dragend",this._onDragEnd,this),this._draggable.disable(),o.DomUtil.removeClass(this._marker._icon,"leaflet-marker-draggable")},moved:function(){return this._draggable&&this._draggable._moved},_onDragStart:function(){this._marker.closePopup().fire("movestart").fire("dragstart")},_onDrag:function(){var t=this._marker,e=t._shadow,i=o.DomUtil.getPosition(t._icon),n=t._map.layerPointToLatLng(i);e&&o.DomUtil.setPosition(e,i),t._latlng=n,t.fire("move",{latlng:n}).fire("drag")},_onDragEnd:function(t){this._marker.fire("moveend").fire("dragend",t)}}),o.Control=o.Class.extend({options:{position:"topright"},initialize:function(t){o.setOptions(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var e=this._map;return e&&e.removeControl(this),this.options.position=t,e&&e.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this._map=t;var e=this._container=this.onAdd(t),i=this.getPosition(),n=t._controlCorners[i];return o.DomUtil.addClass(e,"leaflet-control"),-1!==i.indexOf("bottom")?n.insertBefore(e,n.firstChild):n.appendChild(e),this},removeFrom:function(t){var e=this.getPosition(),i=t._controlCorners[e];return i.removeChild(this._container),this._map=null,this.onRemove&&this.onRemove(t),this},_refocusOnMap:function(){this._map&&this._map.getContainer().focus()}}),o.control=function(t){return new o.Control(t)},o.Map.include({addControl:function(t){return t.addTo(this),this},removeControl:function(t){return t.removeFrom(this),this},_initControlPos:function(){function t(t,s){var a=i+t+" "+i+s;e[t+s]=o.DomUtil.create("div",a,n)}var e=this._controlCorners={},i="leaflet-",n=this._controlContainer=o.DomUtil.create("div",i+"control-container",this._container);t("top","left"),t("top","right"),t("bottom","left"),t("bottom","right")},_clearControlPos:function(){this._container.removeChild(this._controlContainer)}}),o.Control.Zoom=o.Control.extend({options:{position:"topleft",zoomInText:"+",zoomInTitle:"Zoom in",zoomOutText:"-",zoomOutTitle:"Zoom out"},onAdd:function(t){var e="leaflet-control-zoom",i=o.DomUtil.create("div",e+" leaflet-bar");return this._map=t,this._zoomInButton=this._createButton(this.options.zoomInText,this.options.zoomInTitle,e+"-in",i,this._zoomIn,this),this._zoomOutButton=this._createButton(this.options.zoomOutText,this.options.zoomOutTitle,e+"-out",i,this._zoomOut,this),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),i},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},_zoomIn:function(t){this._map.zoomIn(t.shiftKey?3:1)},_zoomOut:function(t){this._map.zoomOut(t.shiftKey?3:1)},_createButton:function(t,e,i,n,s,a){var r=o.DomUtil.create("a",i,n);r.innerHTML=t,r.href="#",r.title=e;var h=o.DomEvent.stopPropagation;return o.DomEvent.on(r,"click",h).on(r,"mousedown",h).on(r,"dblclick",h).on(r,"click",o.DomEvent.preventDefault).on(r,"click",s,a).on(r,"click",this._refocusOnMap,a),r},_updateDisabled:function(){var t=this._map,e="leaflet-disabled";o.DomUtil.removeClass(this._zoomInButton,e),o.DomUtil.removeClass(this._zoomOutButton,e),t._zoom===t.getMinZoom()&&o.DomUtil.addClass(this._zoomOutButton,e),t._zoom===t.getMaxZoom()&&o.DomUtil.addClass(this._zoomInButton,e)}}),o.Map.mergeOptions({zoomControl:!0}),o.Map.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new o.Control.Zoom,this.addControl(this.zoomControl))}),o.control.zoom=function(t){return new o.Control.Zoom(t)},o.Control.Attribution=o.Control.extend({options:{position:"bottomright",prefix:'<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'},initialize:function(t){o.setOptions(this,t),this._attributions={}},onAdd:function(t){this._container=o.DomUtil.create("div","leaflet-control-attribution"),o.DomEvent.disableClickPropagation(this._container);for(var e in t._layers)t._layers[e].getAttribution&&this.addAttribution(t._layers[e].getAttribution());return t.on("layeradd",this._onLayerAdd,this).on("layerremove",this._onLayerRemove,this),this._update(),this._container},onRemove:function(t){t.off("layeradd",this._onLayerAdd).off("layerremove",this._onLayerRemove)},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t?(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update(),this):void 0},removeAttribution:function(t){return t?(this._attributions[t]&&(this._attributions[t]--,this._update()),this):void 0},_update:function(){if(this._map){var t=[];for(var e in this._attributions)this._attributions[e]&&t.push(e);var i=[];this.options.prefix&&i.push(this.options.prefix),t.length&&i.push(t.join(", ")),this._container.innerHTML=i.join(" | ")}},_onLayerAdd:function(t){t.layer.getAttribution&&this.addAttribution(t.layer.getAttribution())},_onLayerRemove:function(t){t.layer.getAttribution&&this.removeAttribution(t.layer.getAttribution())}}),o.Map.mergeOptions({attributionControl:!0}),o.Map.addInitHook(function(){this.options.attributionControl&&(this.attributionControl=(new o.Control.Attribution).addTo(this))}),o.control.attribution=function(t){return new o.Control.Attribution(t)},o.Control.Scale=o.Control.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0,updateWhenIdle:!1},onAdd:function(t){this._map=t;var e="leaflet-control-scale",i=o.DomUtil.create("div",e),n=this.options;return this._addScales(n,e,i),t.on(n.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),i},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,e,i){t.metric&&(this._mScale=o.DomUtil.create("div",e+"-line",i)),t.imperial&&(this._iScale=o.DomUtil.create("div",e+"-line",i))},_update:function(){var t=this._map.getBounds(),e=t.getCenter().lat,i=6378137*Math.PI*Math.cos(e*Math.PI/180),n=i*(t.getNorthEast().lng-t.getSouthWest().lng)/180,o=this._map.getSize(),s=this.options,a=0;o.x>0&&(a=n*(s.maxWidth/o.x)),this._updateScales(s,a)},_updateScales:function(t,e){t.metric&&e&&this._updateMetric(e),t.imperial&&e&&this._updateImperial(e)},_updateMetric:function(t){var e=this._getRoundNum(t);this._mScale.style.width=this._getScaleWidth(e/t)+"px",this._mScale.innerHTML=1e3>e?e+" m":e/1e3+" km"},_updateImperial:function(t){var e,i,n,o=3.2808399*t,s=this._iScale;o>5280?(e=o/5280,i=this._getRoundNum(e),s.style.width=this._getScaleWidth(i/e)+"px",s.innerHTML=i+" mi"):(n=this._getRoundNum(o),s.style.width=this._getScaleWidth(n/o)+"px",s.innerHTML=n+" ft")},_getScaleWidth:function(t){return Math.round(this.options.maxWidth*t)-10},_getRoundNum:function(t){var e=Math.pow(10,(Math.floor(t)+"").length-1),i=t/e;return i=i>=10?10:i>=5?5:i>=3?3:i>=2?2:1,e*i}}),o.control.scale=function(t){return new o.Control.Scale(t)},o.Control.Layers=o.Control.extend({options:{collapsed:!0,position:"topright",autoZIndex:!0},initialize:function(t,e,i){o.setOptions(this,i),this._layers={},this._lastZIndex=0,this._handlingClick=!1;for(var n in t)this._addLayer(t[n],n);for(n in e)this._addLayer(e[n],n,!0)},onAdd:function(t){return this._initLayout(),this._update(),t.on("layeradd",this._onLayerChange,this).on("layerremove",this._onLayerChange,this),this._container},onRemove:function(t){t.off("layeradd",this._onLayerChange,this).off("layerremove",this._onLayerChange,this)},addBaseLayer:function(t,e){return this._addLayer(t,e),this._update(),this},addOverlay:function(t,e){return this._addLayer(t,e,!0),this._update(),this},removeLayer:function(t){var e=o.stamp(t);return delete this._layers[e],this._update(),this},_initLayout:function(){var t="leaflet-control-layers",e=this._container=o.DomUtil.create("div",t);e.setAttribute("aria-haspopup",!0),o.Browser.touch?o.DomEvent.on(e,"click",o.DomEvent.stopPropagation):o.DomEvent.disableClickPropagation(e).disableScrollPropagation(e);var i=this._form=o.DomUtil.create("form",t+"-list");if(this.options.collapsed){o.Browser.android||o.DomEvent.on(e,"mouseover",this._expand,this).on(e,"mouseout",this._collapse,this);var n=this._layersLink=o.DomUtil.create("a",t+"-toggle",e);n.href="#",n.title="Layers",o.Browser.touch?o.DomEvent.on(n,"click",o.DomEvent.stop).on(n,"click",this._expand,this):o.DomEvent.on(n,"focus",this._expand,this),o.DomEvent.on(i,"click",function(){setTimeout(o.bind(this._onInputClick,this),0)},this),this._map.on("click",this._collapse,this)}else this._expand();this._baseLayersList=o.DomUtil.create("div",t+"-base",i),this._separator=o.DomUtil.create("div",t+"-separator",i),this._overlaysList=o.DomUtil.create("div",t+"-overlays",i),e.appendChild(i)},_addLayer:function(t,e,i){var n=o.stamp(t);this._layers[n]={layer:t,name:e,overlay:i},this.options.autoZIndex&&t.setZIndex&&(this._lastZIndex++,t.setZIndex(this._lastZIndex))},_update:function(){if(this._container){this._baseLayersList.innerHTML="",this._overlaysList.innerHTML="";var t,e,i=!1,n=!1;for(t in this._layers)e=this._layers[t],this._addItem(e),n=n||e.overlay,i=i||!e.overlay;this._separator.style.display=n&&i?"":"none"}},_onLayerChange:function(t){var e=this._layers[o.stamp(t.layer)];if(e){this._handlingClick||this._update();var i=e.overlay?"layeradd"===t.type?"overlayadd":"overlayremove":"layeradd"===t.type?"baselayerchange":null;i&&this._map.fire(i,e)}},_createRadioElement:function(t,i){var n='<input type="radio" class="leaflet-control-layers-selector" name="'+t+'"';i&&(n+=' checked="checked"'),n+="/>";var o=e.createElement("div");return o.innerHTML=n,o.firstChild},_addItem:function(t){var i,n=e.createElement("label"),s=this._map.hasLayer(t.layer);t.overlay?(i=e.createElement("input"),i.type="checkbox",i.className="leaflet-control-layers-selector",i.defaultChecked=s):i=this._createRadioElement("leaflet-base-layers",s),i.layerId=o.stamp(t.layer),o.DomEvent.on(i,"click",this._onInputClick,this);var a=e.createElement("span");a.innerHTML=" "+t.name,n.appendChild(i),n.appendChild(a);var r=t.overlay?this._overlaysList:this._baseLayersList;return r.appendChild(n),n},_onInputClick:function(){var t,e,i,n=this._form.getElementsByTagName("input"),o=n.length;for(this._handlingClick=!0,t=0;o>t;t++)e=n[t],i=this._layers[e.layerId],e.checked&&!this._map.hasLayer(i.layer)?this._map.addLayer(i.layer):!e.checked&&this._map.hasLayer(i.layer)&&this._map.removeLayer(i.layer);this._handlingClick=!1,this._refocusOnMap()},_expand:function(){o.DomUtil.addClass(this._container,"leaflet-control-layers-expanded")},_collapse:function(){this._container.className=this._container.className.replace(" leaflet-control-layers-expanded","")}}),o.control.layers=function(t,e,i){return new o.Control.Layers(t,e,i)},o.PosAnimation=o.Class.extend({includes:o.Mixin.Events,run:function(t,e,i,n){this.stop(),this._el=t,this._inProgress=!0,this._newPos=e,this.fire("start"),t.style[o.DomUtil.TRANSITION]="all "+(i||.25)+"s cubic-bezier(0,0,"+(n||.5)+",1)",o.DomEvent.on(t,o.DomUtil.TRANSITION_END,this._onTransitionEnd,this),o.DomUtil.setPosition(t,e),o.Util.falseFn(t.offsetWidth),this._stepTimer=setInterval(o.bind(this._onStep,this),50)},stop:function(){this._inProgress&&(o.DomUtil.setPosition(this._el,this._getPos()),this._onTransitionEnd(),o.Util.falseFn(this._el.offsetWidth))},_onStep:function(){var t=this._getPos();return t?(this._el._leaflet_pos=t,void this.fire("step")):void this._onTransitionEnd()},_transformRe:/([-+]?(?:\d*\.)?\d+)\D*, ([-+]?(?:\d*\.)?\d+)\D*\)/,_getPos:function(){var e,i,n,s=this._el,a=t.getComputedStyle(s);if(o.Browser.any3d){if(n=a[o.DomUtil.TRANSFORM].match(this._transformRe),!n)return;e=parseFloat(n[1]),i=parseFloat(n[2])}else e=parseFloat(a.left),i=parseFloat(a.top);return new o.Point(e,i,!0)},_onTransitionEnd:function(){o.DomEvent.off(this._el,o.DomUtil.TRANSITION_END,this._onTransitionEnd,this),this._inProgress&&(this._inProgress=!1,this._el.style[o.DomUtil.TRANSITION]="",this._el._leaflet_pos=this._newPos,clearInterval(this._stepTimer),this.fire("step").fire("end"))}}),o.Map.include({setView:function(t,e,n){if(e=e===i?this._zoom:this._limitZoom(e),t=this._limitCenter(o.latLng(t),e,this.options.maxBounds),n=n||{},this._panAnim&&this._panAnim.stop(),this._loaded&&!n.reset&&n!==!0){n.animate!==i&&(n.zoom=o.extend({animate:n.animate},n.zoom),n.pan=o.extend({animate:n.animate},n.pan));var s=this._zoom!==e?this._tryAnimatedZoom&&this._tryAnimatedZoom(t,e,n.zoom):this._tryAnimatedPan(t,n.pan);if(s)return clearTimeout(this._sizeTimer),this}return this._resetView(t,e),this},panBy:function(t,e){if(t=o.point(t).round(),e=e||{},!t.x&&!t.y)return this;if(this._panAnim||(this._panAnim=new o.PosAnimation,this._panAnim.on({step:this._onPanTransitionStep,end:this._onPanTransitionEnd},this)),e.noMoveStart||this.fire("movestart"),e.animate!==!1){o.DomUtil.addClass(this._mapPane,"leaflet-pan-anim");var i=this._getMapPanePos().subtract(t);this._panAnim.run(this._mapPane,i,e.duration||.25,e.easeLinearity)}else this._rawPanBy(t),this.fire("move").fire("moveend");return this},_onPanTransitionStep:function(){this.fire("move")},_onPanTransitionEnd:function(){o.DomUtil.removeClass(this._mapPane,"leaflet-pan-anim"),this.fire("moveend")},_tryAnimatedPan:function(t,e){var i=this._getCenterOffset(t)._floor();return(e&&e.animate)===!0||this.getSize().contains(i)?(this.panBy(i,e),!0):!1}}),o.PosAnimation=o.DomUtil.TRANSITION?o.PosAnimation:o.PosAnimation.extend({run:function(t,e,i,n){this.stop(),this._el=t,this._inProgress=!0,this._duration=i||.25,this._easeOutPower=1/Math.max(n||.5,.2),this._startPos=o.DomUtil.getPosition(t),this._offset=e.subtract(this._startPos),this._startTime=+new Date,this.fire("start"),this._animate()},stop:function(){this._inProgress&&(this._step(),this._complete())},_animate:function(){this._animId=o.Util.requestAnimFrame(this._animate,this),this._step()},_step:function(){var t=+new Date-this._startTime,e=1e3*this._duration;e>t?this._runFrame(this._easeOut(t/e)):(this._runFrame(1),this._complete())},_runFrame:function(t){var e=this._startPos.add(this._offset.multiplyBy(t));o.DomUtil.setPosition(this._el,e),this.fire("step")},_complete:function(){o.Util.cancelAnimFrame(this._animId),this._inProgress=!1,this.fire("end")},_easeOut:function(t){return 1-Math.pow(1-t,this._easeOutPower)}}),o.Map.mergeOptions({zoomAnimation:!0,zoomAnimationThreshold:4}),o.DomUtil.TRANSITION&&o.Map.addInitHook(function(){this._zoomAnimated=this.options.zoomAnimation&&o.DomUtil.TRANSITION&&o.Browser.any3d&&!o.Browser.android23&&!o.Browser.mobileOpera,this._zoomAnimated&&o.DomEvent.on(this._mapPane,o.DomUtil.TRANSITION_END,this._catchTransitionEnd,this)}),o.Map.include(o.DomUtil.TRANSITION?{_catchTransitionEnd:function(t){this._animatingZoom&&t.propertyName.indexOf("transform")>=0&&this._onZoomTransitionEnd()},_nothingToAnimate:function(){return!this._container.getElementsByClassName("leaflet-zoom-animated").length},_tryAnimatedZoom:function(t,e,i){if(this._animatingZoom)return!0;if(i=i||{},!this._zoomAnimated||i.animate===!1||this._nothingToAnimate()||Math.abs(e-this._zoom)>this.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(e),o=this._getCenterOffset(t)._divideBy(1-1/n),s=this._getCenterLayerPoint()._add(o);return i.animate===!0||this.getSize().contains(o)?(this.fire("movestart").fire("zoomstart"),this._animateZoom(t,e,s,n,null,!0),!0):!1},_animateZoom:function(t,e,i,n,s,a,r){r||(this._animatingZoom=!0),o.DomUtil.addClass(this._mapPane,"leaflet-zoom-anim"),this._animateToCenter=t,this._animateToZoom=e,o.Draggable&&(o.Draggable._disabled=!0),o.Util.requestAnimFrame(function(){this.fire("zoomanim",{center:t,zoom:e,origin:i,scale:n,delta:s,backwards:a})},this)},_onZoomTransitionEnd:function(){this._animatingZoom=!1,o.DomUtil.removeClass(this._mapPane,"leaflet-zoom-anim"),this._resetView(this._animateToCenter,this._animateToZoom,!0,!0),o.Draggable&&(o.Draggable._disabled=!1)}}:{}),o.TileLayer.include({_animateZoom:function(t){this._animating||(this._animating=!0,this._prepareBgBuffer());var e=this._bgBuffer,i=o.DomUtil.TRANSFORM,n=t.delta?o.DomUtil.getTranslateString(t.delta):e.style[i],s=o.DomUtil.getScaleString(t.scale,t.origin);e.style[i]=t.backwards?s+" "+n:n+" "+s},_endZoomAnim:function(){var t=this._tileContainer,e=this._bgBuffer;t.style.visibility="",t.parentNode.appendChild(t),o.Util.falseFn(e.offsetWidth),this._animating=!1},_clearBgBuffer:function(){var t=this._map;!t||t._animatingZoom||t.touchZoom._zooming||(this._bgBuffer.innerHTML="",this._bgBuffer.style[o.DomUtil.TRANSFORM]="")},_prepareBgBuffer:function(){var t=this._tileContainer,e=this._bgBuffer,i=this._getLoadedTilesPercentage(e),n=this._getLoadedTilesPercentage(t);return e&&i>.5&&.5>n?(t.style.visibility="hidden",void this._stopLoadingImages(t)):(e.style.visibility="hidden",e.style[o.DomUtil.TRANSFORM]="",this._tileContainer=e,e=this._bgBuffer=t,this._stopLoadingImages(e),void clearTimeout(this._clearBgBufferTimer))},_getLoadedTilesPercentage:function(t){var e,i,n=t.getElementsByTagName("img"),o=0;for(e=0,i=n.length;i>e;e++)n[e].complete&&o++;return o/i},_stopLoadingImages:function(t){var e,i,n,s=Array.prototype.slice.call(t.getElementsByTagName("img"));for(e=0,i=s.length;i>e;e++)n=s[e],n.complete||(n.onload=o.Util.falseFn,n.onerror=o.Util.falseFn,n.src=o.Util.emptyImageUrl,n.parentNode.removeChild(n))}}),o.Map.include({_defaultLocateOptions:{watch:!1,setView:!1,maxZoom:1/0,timeout:1e4,maximumAge:0,enableHighAccuracy:!1},locate:function(t){if(t=this._locateOptions=o.extend(this._defaultLocateOptions,t),!navigator.geolocation)return this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this;var e=o.bind(this._handleGeolocationResponse,this),i=o.bind(this._handleGeolocationError,this);return t.watch?this._locationWatchId=navigator.geolocation.watchPosition(e,i,t):navigator.geolocation.getCurrentPosition(e,i,t),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(t){var e=t.code,i=t.message||(1===e?"permission denied":2===e?"position unavailable":"timeout");this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:e,message:"Geolocation error: "+i+"."})},_handleGeolocationResponse:function(t){var e=t.coords.latitude,i=t.coords.longitude,n=new o.LatLng(e,i),s=180*t.coords.accuracy/40075017,a=s/Math.cos(o.LatLng.DEG_TO_RAD*e),r=o.latLngBounds([e-s,i-a],[e+s,i+a]),h=this._locateOptions;if(h.setView){var l=Math.min(this.getBoundsZoom(r),h.maxZoom);this.setView(n,l)}var u={latlng:n,bounds:r,timestamp:t.timestamp};for(var c in t.coords)"number"==typeof t.coords[c]&&(u[c]=t.coords[c]);this.fire("locationfound",u)}})}(window,document);
\ No newline at end of file
diff --git a/www/plugins/gis/lib/leaflet/plugins/Bing.js b/www/plugins/gis/lib/leaflet/plugins/Bing.js
new file mode 100755 (executable)
index 0000000..b28b6d3
--- /dev/null
@@ -0,0 +1,125 @@
+/* global console: true */
+L.BingLayer = L.TileLayer.extend({
+       options: {
+               subdomains: [0, 1, 2, 3],
+               type: 'Aerial',
+               attribution: 'Bing',
+               culture: ''
+       },
+
+       initialize: function(key, options) {
+               L.Util.setOptions(this, options);
+
+               this._key = key;
+               this._url = null;
+               this.meta = {};
+               this.loadMetadata();
+       },
+
+       tile2quad: function(x, y, z) {
+               var quad = '';
+               for (var i = z; i > 0; i--) {
+                       var digit = 0;
+                       var mask = 1 << (i - 1);
+                       if ((x & mask) !== 0) digit += 1;
+                       if ((y & mask) !== 0) digit += 2;
+                       quad = quad + digit;
+               }
+               return quad;
+       },
+
+       getTileUrl: function(p, z) {
+               var zoom = this._getZoomForUrl();
+               var subdomains = this.options.subdomains,
+                       s = this.options.subdomains[Math.abs((p.x + p.y) % subdomains.length)];
+               return this._url.replace('{subdomain}', s)
+                               .replace('{quadkey}', this.tile2quad(p.x, p.y, zoom))
+                               .replace('{culture}', this.options.culture);
+       },
+
+       loadMetadata: function() {
+               var _this = this;
+               var cbid = '_bing_metadata_' + L.Util.stamp(this);
+               window[cbid] = function (meta) {
+                       _this.meta = meta;
+                       window[cbid] = undefined;
+                       var e = document.getElementById(cbid);
+                       e.parentNode.removeChild(e);
+                       if (meta.errorDetails) {
+                               if (window.console) console.log('Leaflet Bing Plugin Error - Got metadata: ' + meta.errorDetails);
+                               return;
+                       }
+                       _this.initMetadata();
+               };
+               var url = document.location.protocol + '//dev.virtualearth.net/REST/v1/Imagery/Metadata/' + this.options.type + '?include=ImageryProviders&jsonp=' + cbid +
+                         '&key=' + this._key + '&UriScheme=' + document.location.protocol.slice(0, -1);
+               var script = document.createElement('script');
+               script.type = 'text/javascript';
+               script.src = url;
+               script.id = cbid;
+               document.getElementsByTagName('head')[0].appendChild(script);
+       },
+
+       initMetadata: function() {
+               var r = this.meta.resourceSets[0].resources[0];
+               this.options.subdomains = r.imageUrlSubdomains;
+               this._url = r.imageUrl;
+               this._providers = [];
+               if (r.imageryProviders) {
+                       for (var i = 0; i < r.imageryProviders.length; i++) {
+                               var p = r.imageryProviders[i];
+                               for (var j = 0; j < p.coverageAreas.length; j++) {
+                                       var c = p.coverageAreas[j];
+                                       var coverage = {zoomMin: c.zoomMin, zoomMax: c.zoomMax, active: false};
+                                       var bounds = new L.LatLngBounds(
+                                                       new L.LatLng(c.bbox[0]+0.01, c.bbox[1]+0.01),
+                                                       new L.LatLng(c.bbox[2]-0.01, c.bbox[3]-0.01)
+                                       );
+                                       coverage.bounds = bounds;
+                                       coverage.attrib = p.attribution;
+                                       this._providers.push(coverage);
+                               }
+                       }
+               }
+               this._update();
+       },
+
+       _update: function() {
+               if (this._url === null || !this._map) return;
+               this._update_attribution();
+               L.TileLayer.prototype._update.apply(this, []);
+       },
+
+       _update_attribution: function() {
+               var bounds = this._map.getBounds();
+               var zoom = this._map.getZoom();
+               for (var i = 0; i < this._providers.length; i++) {
+                       var p = this._providers[i];
+                       if ((zoom <= p.zoomMax && zoom >= p.zoomMin) &&
+                                       bounds.intersects(p.bounds)) {
+                               if (!p.active && this._map.attributionControl)
+                                       this._map.attributionControl.addAttribution(p.attrib);
+                               p.active = true;
+                       } else {
+                               if (p.active && this._map.attributionControl)
+                                       this._map.attributionControl.removeAttribution(p.attrib);
+                               p.active = false;
+                       }
+               }
+       },
+
+       onRemove: function(map) {
+               for (var i = 0; i < this._providers.length; i++) {
+                       var p = this._providers[i];
+                       if (p.active && this._map.attributionControl) {
+                               this._map.attributionControl.removeAttribution(p.attrib);
+                               p.active = false;
+                       }
+               }
+               L.TileLayer.prototype.onRemove.apply(this, [map]);
+       }
+});
+
+L.bingLayer = function (key, options) {
+    return new L.BingLayer(key, options);
+};
diff --git a/www/plugins/gis/lib/leaflet/plugins/Control.FullScreen.js b/www/plugins/gis/lib/leaflet/plugins/Control.FullScreen.js
new file mode 100644 (file)
index 0000000..f1cd7cc
--- /dev/null
@@ -0,0 +1,164 @@
+(function() {
+
+L.Control.FullScreen = L.Control.extend({
+       options: {
+               position: 'topleft',
+               title: 'Full Screen',
+               forceSeparateButton: false,
+               forcePseudoFullscreen: false
+       },
+       
+       onAdd: function (map) {
+               var className = 'leaflet-control-zoom-fullscreen', container;
+               
+               if (map.zoomControl && !this.options.forceSeparateButton) {
+                       container = map.zoomControl._container;
+               } else {
+                       container = L.DomUtil.create('div', 'leaflet-bar');
+               }
+               
+               this._createButton(this.options.title, className, container, this.toggleFullScreen, this);
+
+               return container;
+       },
+       
+       _createButton: function (title, className, container, fn, context) {
+               var link = L.DomUtil.create('a', className, container);
+               link.href = '#';
+               link.title = title;
+
+               L.DomEvent
+                       .addListener(link, 'click', L.DomEvent.stopPropagation)
+                       .addListener(link, 'click', L.DomEvent.preventDefault)
+                       .addListener(link, 'click', fn, context);
+               
+               L.DomEvent
+                       .addListener(container, fullScreenApi.fullScreenEventName, L.DomEvent.stopPropagation)
+                       .addListener(container, fullScreenApi.fullScreenEventName, L.DomEvent.preventDefault)
+                       .addListener(container, fullScreenApi.fullScreenEventName, this._handleEscKey, context);
+               
+               L.DomEvent
+                       .addListener(document, fullScreenApi.fullScreenEventName, L.DomEvent.stopPropagation)
+                       .addListener(document, fullScreenApi.fullScreenEventName, L.DomEvent.preventDefault)
+                       .addListener(document, fullScreenApi.fullScreenEventName, this._handleEscKey, context);
+
+               return link;
+       },
+       
+       toggleFullScreen: function () {
+               var map = this._map;
+               map._exitFired = false;
+               if (map._isFullscreen) {
+                       if (fullScreenApi.supportsFullScreen && !this.options.forcePseudoFullscreen) {
+                               fullScreenApi.cancelFullScreen(map._container);
+                       } else {
+                               L.DomUtil.removeClass(map._container, 'leaflet-pseudo-fullscreen');
+                       }
+                       map.invalidateSize();
+                       map.fire('exitFullscreen');
+                       map._exitFired = true;
+                       map._isFullscreen = false;
+               }
+               else {
+                       if (fullScreenApi.supportsFullScreen && !this.options.forcePseudoFullscreen) {
+                               fullScreenApi.requestFullScreen(map._container);
+                       } else {
+                               L.DomUtil.addClass(map._container, 'leaflet-pseudo-fullscreen');
+                       }
+                       map.invalidateSize();
+                       map.fire('enterFullscreen');
+                       map._isFullscreen = true;
+               }
+       },
+       
+       _handleEscKey: function () {
+               var map = this._map;
+               if (!fullScreenApi.isFullScreen(map) && !map._exitFired) {
+                       map.fire('exitFullscreen');
+                       map._exitFired = true;
+                       map._isFullscreen = false;
+               }
+       }
+});
+
+L.Map.addInitHook(function () {
+       if (this.options.fullscreenControl) {
+               this.fullscreenControl = L.control.fullscreen(this.options.fullscreenControlOptions);
+               this.addControl(this.fullscreenControl);
+       }
+});
+
+L.control.fullscreen = function (options) {
+       return new L.Control.FullScreen(options);
+};
+
+/* 
+Native FullScreen JavaScript API
+-------------
+Assumes Mozilla naming conventions instead of W3C for now
+
+source : http://johndyer.name/native-fullscreen-javascript-api-plus-jquery-plugin/
+
+*/
+
+       var 
+               fullScreenApi = { 
+                       supportsFullScreen: false,
+                       isFullScreen: function() { return false; }, 
+                       requestFullScreen: function() {}, 
+                       cancelFullScreen: function() {},
+                       fullScreenEventName: '',
+                       prefix: ''
+               },
+               browserPrefixes = 'webkit moz o ms khtml'.split(' ');
+       
+       // check for native support
+       if (typeof document.exitFullscreen !== 'undefined') {
+               fullScreenApi.supportsFullScreen = true;
+       } else {
+               // check for fullscreen support by vendor prefix
+               for (var i = 0, il = browserPrefixes.length; i < il; i++ ) {
+                       fullScreenApi.prefix = browserPrefixes[i];
+                       if (typeof document[fullScreenApi.prefix + 'CancelFullScreen' ] !== 'undefined' ) {
+                               fullScreenApi.supportsFullScreen = true;
+                               break;
+                       }
+               }
+       }
+       
+       // update methods to do something useful
+       if (fullScreenApi.supportsFullScreen) {
+               fullScreenApi.fullScreenEventName = fullScreenApi.prefix + 'fullscreenchange';
+               fullScreenApi.isFullScreen = function() {
+                       switch (this.prefix) {  
+                               case '':
+                                       return document.fullScreen;
+                               case 'webkit':
+                                       return document.webkitIsFullScreen;
+                               default:
+                                       return document[this.prefix + 'FullScreen'];
+                       }
+               };
+               fullScreenApi.requestFullScreen = function(el) {
+                       return (this.prefix === '') ? el.requestFullscreen() : el[this.prefix + 'RequestFullScreen']();
+               };
+               fullScreenApi.cancelFullScreen = function(el) {
+                       return (this.prefix === '') ? document.exitFullscreen() : document[this.prefix + 'CancelFullScreen']();
+               };
+       }
+
+       // jQuery plugin
+       if (typeof jQuery !== 'undefined') {
+               jQuery.fn.requestFullScreen = function() {
+                       return this.each(function() {
+                               var el = jQuery(this);
+                               if (fullScreenApi.supportsFullScreen) {
+                                       fullScreenApi.requestFullScreen(el);
+                               }
+                       });
+               };
+       }
+
+       // export api
+       window.fullScreenApi = fullScreenApi;
+})();
diff --git a/www/plugins/gis/lib/leaflet/plugins/Control.MiniMap.js b/www/plugins/gis/lib/leaflet/plugins/Control.MiniMap.js
new file mode 100644 (file)
index 0000000..2baef61
--- /dev/null
@@ -0,0 +1,270 @@
+L.Control.MiniMap = L.Control.extend({
+       options: {
+               position: 'bottomright',
+               toggleDisplay: false,
+               zoomLevelOffset: -5,
+               zoomLevelFixed: false,
+               zoomAnimation: false,
+               autoToggleDisplay: false,
+               width: 150,
+               height: 150,
+               aimingRectOptions: {color: "#ff7800", weight: 1, clickable: false},
+               shadowRectOptions: {color: "#000000", weight: 1, clickable: false, opacity:0, fillOpacity:0}
+       },
+       
+       hideText: 'Hide MiniMap',
+       
+       showText: 'Show MiniMap',
+       
+       //layer is the map layer to be shown in the minimap
+       initialize: function (layer, options) {
+               L.Util.setOptions(this, options);
+               //Make sure the aiming rects are non-clickable even if the user tries to set them clickable (most likely by forgetting to specify them false)
+               this.options.aimingRectOptions.clickable = false;
+               this.options.shadowRectOptions.clickable = false;
+               this._layer = layer;
+       },
+       
+       onAdd: function (map) {
+
+               this._mainMap = map;
+
+               //Creating the container and stopping events from spilling through to the main map.
+               this._container = L.DomUtil.create('div', 'leaflet-control-minimap');
+               this._container.style.width = this.options.width + 'px';
+               this._container.style.height = this.options.height + 'px';
+               L.DomEvent.disableClickPropagation(this._container);
+               L.DomEvent.on(this._container, 'mousewheel', L.DomEvent.stopPropagation);
+
+
+               this._miniMap = new L.Map(this._container,
+               {
+                       attributionControl: false,
+                       zoomControl: false,
+                       zoomAnimation: this.options.zoomAnimation,
+                       autoToggleDisplay: this.options.autoToggleDisplay,
+                       touchZoom: !this.options.zoomLevelFixed,
+                       scrollWheelZoom: !this.options.zoomLevelFixed,
+                       doubleClickZoom: !this.options.zoomLevelFixed,
+                       boxZoom: !this.options.zoomLevelFixed,
+                       crs: map.options.crs
+               });
+
+               this._miniMap.addLayer(this._layer);
+
+               //These bools are used to prevent infinite loops of the two maps notifying each other that they've moved.
+               this._mainMapMoving = false;
+               this._miniMapMoving = false;
+
+               //Keep a record of this to prevent auto toggling when the user explicitly doesn't want it.
+               this._userToggledDisplay = false;
+               this._minimized = false;
+
+               if (this.options.toggleDisplay) {
+                       this._addToggleButton();
+               }
+
+               this._miniMap.whenReady(L.Util.bind(function () {
+                       this._aimingRect = L.rectangle(this._mainMap.getBounds(), this.options.aimingRectOptions).addTo(this._miniMap);
+                       this._shadowRect = L.rectangle(this._mainMap.getBounds(), this.options.shadowRectOptions).addTo(this._miniMap);
+                       this._mainMap.on('moveend', this._onMainMapMoved, this);
+                       this._mainMap.on('move', this._onMainMapMoving, this);
+                       this._miniMap.on('movestart', this._onMiniMapMoveStarted, this);
+                       this._miniMap.on('move', this._onMiniMapMoving, this);
+                       this._miniMap.on('moveend', this._onMiniMapMoved, this);
+               }, this));
+
+               return this._container;
+       },
+
+       addTo: function (map) {
+               L.Control.prototype.addTo.call(this, map);
+               this._miniMap.setView(this._mainMap.getCenter(), this._decideZoom(true));
+               this._setDisplay(this._decideMinimized());
+               return this;
+       },
+
+       onRemove: function (map) {
+               this._mainMap.off('moveend', this._onMainMapMoved, this);
+               this._mainMap.off('move', this._onMainMapMoving, this);
+               this._miniMap.off('moveend', this._onMiniMapMoved, this);
+
+               this._miniMap.removeLayer(this._layer);
+       },
+
+       _addToggleButton: function () {
+               this._toggleDisplayButton = this.options.toggleDisplay ? this._createButton(
+                               '', this.hideText, 'leaflet-control-minimap-toggle-display', this._container, this._toggleDisplayButtonClicked, this) : undefined;
+       },
+
+       _createButton: function (html, title, className, container, fn, context) {
+               var link = L.DomUtil.create('a', className, container);
+               link.innerHTML = html;
+               link.href = '#';
+               link.title = title;
+
+               var stop = L.DomEvent.stopPropagation;
+
+               L.DomEvent
+                       .on(link, 'click', stop)
+                       .on(link, 'mousedown', stop)
+                       .on(link, 'dblclick', stop)
+                       .on(link, 'click', L.DomEvent.preventDefault)
+                       .on(link, 'click', fn, context);
+
+               return link;
+       },
+
+       _toggleDisplayButtonClicked: function () {
+               this._userToggledDisplay = true;
+               if (!this._minimized) {
+                       this._minimize();
+                       this._toggleDisplayButton.title = this.showText;
+               }
+               else {
+                       this._restore();
+                       this._toggleDisplayButton.title = this.hideText;
+               }
+       },
+
+       _setDisplay: function (minimize) {
+               if (minimize != this._minimized) {
+                       if (!this._minimized) {
+                               this._minimize();
+                       }
+                       else {
+                               this._restore();
+                       }
+               }
+       },
+
+       _minimize: function () {
+               // hide the minimap
+               if (this.options.toggleDisplay) {
+                       this._container.style.width = '19px';
+                       this._container.style.height = '19px';
+                       this._toggleDisplayButton.className += ' minimized';
+               }
+               else {
+                       this._container.style.display = 'none';
+               }
+               this._minimized = true;
+       },
+
+       _restore: function () {
+               if (this.options.toggleDisplay) {
+                       this._container.style.width = this.options.width + 'px';
+                       this._container.style.height = this.options.height + 'px';
+                       this._toggleDisplayButton.className = this._toggleDisplayButton.className
+                                       .replace(/(?:^|\s)minimized(?!\S)/g, '');
+               }
+               else {
+                       this._container.style.display = 'block';
+               }
+               this._minimized = false;
+       },
+
+       _onMainMapMoved: function (e) {
+               if (!this._miniMapMoving) {
+                       this._mainMapMoving = true;
+                       this._miniMap.setView(this._mainMap.getCenter(), this._decideZoom(true));
+                       this._setDisplay(this._decideMinimized());
+               } else {
+                       this._miniMapMoving = false;
+               }
+               this._aimingRect.setBounds(this._mainMap.getBounds());
+       },
+
+       _onMainMapMoving: function (e) {
+               this._aimingRect.setBounds(this._mainMap.getBounds());
+       },
+
+       _onMiniMapMoveStarted:function (e) {
+               var lastAimingRect = this._aimingRect.getBounds();
+               var sw = this._miniMap.latLngToContainerPoint(lastAimingRect.getSouthWest());
+               var ne = this._miniMap.latLngToContainerPoint(lastAimingRect.getNorthEast());
+               this._lastAimingRectPosition = {sw:sw,ne:ne};
+       },
+
+       _onMiniMapMoving: function (e) {
+               if (!this._mainMapMoving && this._lastAimingRectPosition) {
+                       this._shadowRect.setBounds(new L.LatLngBounds(this._miniMap.containerPointToLatLng(this._lastAimingRectPosition.sw),this._miniMap.containerPointToLatLng(this._lastAimingRectPosition.ne)));
+                       this._shadowRect.setStyle({opacity:1,fillOpacity:0.3});
+               }
+       },
+
+       _onMiniMapMoved: function (e) {
+               if (!this._mainMapMoving) {
+                       this._miniMapMoving = true;
+                       this._mainMap.setView(this._miniMap.getCenter(), this._decideZoom(false));
+                       this._shadowRect.setStyle({opacity:0,fillOpacity:0});
+               } else {
+                       this._mainMapMoving = false;
+               }
+       },
+
+       _decideZoom: function (fromMaintoMini) {
+               if (!this.options.zoomLevelFixed) {
+                       if (fromMaintoMini)
+                               return this._mainMap.getZoom() + this.options.zoomLevelOffset;
+                       else {
+                               var currentDiff = this._miniMap.getZoom() - this._mainMap.getZoom();
+                               var proposedZoom = this._miniMap.getZoom() - this.options.zoomLevelOffset;
+                               var toRet;
+                               
+                               if (currentDiff > this.options.zoomLevelOffset && this._mainMap.getZoom() < this._miniMap.getMinZoom() - this.options.zoomLevelOffset) {
+                                       //This means the miniMap is zoomed out to the minimum zoom level and can't zoom any more.
+                                       if (this._miniMap.getZoom() > this._lastMiniMapZoom) {
+                                               //This means the user is trying to zoom in by using the minimap, zoom the main map.
+                                               toRet = this._mainMap.getZoom() + 1;
+                                               //Also we cheat and zoom the minimap out again to keep it visually consistent.
+                                               this._miniMap.setZoom(this._miniMap.getZoom() -1);
+                                       } else {
+                                               //Either the user is trying to zoom out past the mini map's min zoom or has just panned using it, we can't tell the difference.
+                                               //Therefore, we ignore it!
+                                               toRet = this._mainMap.getZoom();
+                                       }
+                               } else {
+                                       //This is what happens in the majority of cases, and always if you configure the min levels + offset in a sane fashion.
+                                       toRet = proposedZoom;
+                               }
+                               this._lastMiniMapZoom = this._miniMap.getZoom();
+                               return toRet;
+                       }
+               } else {
+                       if (fromMaintoMini)
+                               return this.options.zoomLevelFixed;
+                       else
+                               return this._mainMap.getZoom();
+               }
+       },
+
+       _decideMinimized: function () {
+               if (this._userToggledDisplay) {
+                       return this._minimized;
+               }
+
+               if (this.options.autoToggleDisplay) {
+                       if (this._mainMap.getBounds().contains(this._miniMap.getBounds())) {
+                               return true;
+                       }
+                       return false;
+               }
+
+               return this._minimized;
+       }
+});
+
+L.Map.mergeOptions({
+       miniMapControl: false
+});
+
+L.Map.addInitHook(function () {
+       if (this.options.miniMapControl) {
+               this.miniMapControl = (new L.Control.MiniMap()).addTo(this);
+       }
+});
+
+L.control.minimap = function (options) {
+       return new L.Control.MiniMap(options);
+};
diff --git a/www/plugins/gis/lib/leaflet/plugins/GPX.Speed.js b/www/plugins/gis/lib/leaflet/plugins/GPX.Speed.js
new file mode 100755 (executable)
index 0000000..2b8f6c9
--- /dev/null
@@ -0,0 +1,80 @@
+//#include 'GPX.js'
+
+(function() {
+
+function d2h(d) {
+       var hex = '0123456789ABCDEF';
+       var r = '';
+       d = Math.floor(d);
+       while (d !== 0) {
+               r = hex[d % 16] + r;
+               d = Math.floor(d / 16);
+       }
+       while (r.length < 2) r = '0' + r;
+       return r;
+}
+
+function gradient(color) {
+       // First arc (0, PI) in HSV colorspace
+       function f2h(d) { return d2h(256 * d); }
+       if (color < 0)
+               return '#FF0000';
+       else if (color < 1.0/3)
+               return '#FF' + f2h(3 * color) + '00';
+       else if (color < 2.0/3)
+               return '#' + f2h(2 - 3 * color) + 'FF00';
+       else if (color < 1)
+               return '#00FF' + f2h(3 * color - 2);
+       else
+               return '#00FFFF';
+}
+
+function gpx2time(s) {
+       // 2011-09-24T12:07:53Z
+       if (s.length !== 10 + 1 + 8 + 1)
+               return new Date();
+       return new Date(s);
+}
+
+L.GPX.include({
+       options: {
+               maxSpeed: 110,
+               chunks: 200
+       },
+
+       speedSplitEnable: function(options) {
+               L.Util.setOptions(this, options);
+               return this.on('addline', this.speed_split, this);
+       },
+
+       speedSplitDisable: function() {
+               return this.off('addline', this.speed_split, this);
+       },
+
+       speed_split: function(e) {
+               var l = e.line.pop(), ll = l.getLatLngs();
+               var chunk = Math.floor(ll.length / this.options.chunks);
+               if (chunk < 3) chunk = 3;
+               var p = null;
+               for (var i = 0; i < ll.length; i += chunk) {
+                       var d = 0, t = null;
+                       if (i + chunk > ll.length)
+                               chunk = ll.length - i;
+                       for (var j = 0; j < chunk; j++) {
+                               if (p) d += p.distanceTo(ll[i+j]);
+                               p = ll[i + j];
+                               if (!t) t = gpx2time(p.meta.time);
+                       }
+                       p = ll[i + chunk - 1];
+                       t = (gpx2time(p.meta.time) - t) / (3600 * 1000);
+                       var speed = 0.001 * d / t;
+                       //console.info('Dist: ' + d + '; Speed: ' + speed);
+                       var color = gradient(speed / this.options.maxSpeed);
+                       var poly = new L.Polyline(ll.slice(i, i+chunk+1), {color: color, weight: 2, opacity: 1});
+                       poly.bindPopup('Dist: ' + d.toFixed() + 'm; Speed: ' + speed.toFixed(2) + ' km/h');
+                       e.line.push(poly);
+               }
+       }
+
+});
+})();
diff --git a/www/plugins/gis/lib/leaflet/plugins/GPX.js b/www/plugins/gis/lib/leaflet/plugins/GPX.js
new file mode 100755 (executable)
index 0000000..e2e7d6a
--- /dev/null
@@ -0,0 +1,141 @@
+L.GPX = L.FeatureGroup.extend({
+       initialize: function(gpx, options) {
+               L.Util.setOptions(this, options);
+               this._gpx = gpx;
+               this._layers = {};
+               
+               if (gpx) {
+                       this.addGPX(gpx, options, this.options.async);
+               }
+       },
+       
+       loadXML: function(url, cb, options, async) {
+               if (async === undefined) async = this.options.async;
+               if (options === undefined) options = this.options;
+
+               var req = new window.XMLHttpRequest();
+               req.open('GET', url, async);
+               try {
+                       req.overrideMimeType('text/xml'); // unsupported by IE
+               } catch(e) {}
+               req.onreadystatechange = function() {
+                       if (req.readyState !== 4) return;
+                       if(req.status === 200) cb(req.responseXML, options);
+               };
+               req.send(null);
+       },
+
+       _humanLen: function(l) {
+               if (l < 2000)
+                       return l.toFixed(0) + ' m';
+               else
+                       return (l/1000).toFixed(1) + ' km';
+       },
+       
+       _polylineLen: function(line)//line is a L.Polyline()
+       {
+               var ll = line._latlngs;
+               var d = 0, p = null;
+               for (var i = 0; i < ll.length; i++)
+               {
+                       if(i && p)
+                               d += p.distanceTo(ll[i]);
+                       p = ll[i];
+               }
+               return d;
+       },
+
+       addGPX: function(url, options, async) {
+               var _this = this;
+               var cb = function(gpx, options) { _this._addGPX(gpx, options); };
+               this.loadXML(url, cb, options, async);
+       },
+
+       _addGPX: function(gpx, options) {
+               var layers = this.parseGPX(gpx, options);
+               if (!layers) return;
+               this.addLayer(layers);
+               this.fire('loaded');
+       },      
+
+       parseGPX: function(xml, options) {
+               var j, i, el, layers = [];
+               var named = false, tags = [['rte','rtept'], ['trkseg','trkpt']];
+
+               for (j = 0; j < tags.length; j++) {
+                       el = xml.getElementsByTagName(tags[j][0]);
+                       for (i = 0; i < el.length; i++) {
+                               var l = this.parse_trkseg(el[i], xml, options, tags[j][1]);
+                               for (var k = 0; k < l.length; k++) {
+                                       if (this.parse_name(el[i], l[k])) named = true;
+                                       layers.push(l[k]);
+                               }
+                       }
+               }
+
+               el = xml.getElementsByTagName('wpt');
+               if (options.display_wpt !== false) {
+                       for (i = 0; i < el.length; i++) {
+                               var marker = this.parse_wpt(el[i], xml, options);
+                               if (!marker) continue;
+                               if (this.parse_name(el[i], marker)) named = true;
+                               layers.push(marker);
+                       }
+               }
+
+               if (!layers.length) return;
+               var layer = layers[0];
+               if (layers.length > 1) 
+                       layer = new L.FeatureGroup(layers);
+               if (!named) this.parse_name(xml, layer);
+               return layer;
+       },
+
+       parse_name: function(xml, layer) {
+               var i, el, txt='', name, descr='', len=0;
+               el = xml.getElementsByTagName('name');
+               if (el.length)
+                       name = el[0].childNodes[0].nodeValue;
+               el = xml.getElementsByTagName('desc');
+               for (i = 0; i < el.length; i++) {
+                       for (var j = 0; j < el[i].childNodes.length; j++)
+                               descr = descr + el[i].childNodes[j].nodeValue;
+               }
+
+               if(layer instanceof L.Path)
+                       len = this._polylineLen(layer);
+
+               if (name) txt += '<h2>' + name + '</h2>' + descr;
+               if (len) txt += '<p>' + this._humanLen(len) + '</p>';
+               
+               if (layer && layer._popup === undefined) layer.bindPopup(txt);
+               return txt;
+       },
+
+       parse_trkseg: function(line, xml, options, tag) {
+               var el = line.getElementsByTagName(tag);
+               if (!el.length) return [];
+               var coords = [];
+               for (var i = 0; i < el.length; i++) {
+                       var ll = new L.LatLng(el[i].getAttribute('lat'),
+                                               el[i].getAttribute('lon'));
+                       ll.meta = {};
+                       for (var j in el[i].childNodes) {
+                               var e = el[i].childNodes[j];
+                               if (!e.tagName) continue;
+                               ll.meta[e.tagName] = e.textContent;
+                       }
+                       coords.push(ll);
+               }
+               var l = [new L.Polyline(coords, options)];
+               this.fire('addline', {line:l});
+               return l;
+       },
+
+       parse_wpt: function(e, xml, options) {
+               var m = new L.Marker(new L.LatLng(e.getAttribute('lat'),
+                                               e.getAttribute('lon')), options);
+               this.fire('addpoint', {point:m});
+               return m;
+       }
+});
diff --git a/www/plugins/gis/lib/leaflet/plugins/Google.js b/www/plugins/gis/lib/leaflet/plugins/Google.js
new file mode 100755 (executable)
index 0000000..7b39281
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ * Google layer using Google Maps API
+ */
+
+/* global google: true */
+
+L.Google = L.Class.extend({
+       includes: L.Mixin.Events,
+
+       options: {
+               minZoom: 0,
+               maxZoom: 18,
+               tileSize: 256,
+               subdomains: 'abc',
+               errorTileUrl: '',
+               attribution: '',
+               opacity: 1,
+               continuousWorld: false,
+               noWrap: false,
+               mapOptions: {
+                       backgroundColor: '#dddddd'
+               }
+       },
+
+       // Possible types: SATELLITE, ROADMAP, HYBRID, TERRAIN
+       initialize: function(type, options) {
+               L.Util.setOptions(this, options);
+
+               this._ready = google.maps.Map !== undefined;
+               if (!this._ready) L.Google.asyncWait.push(this);
+
+               this._type = type || 'SATELLITE';
+       },
+
+       onAdd: function(map, insertAtTheBottom) {
+               this._map = map;
+               this._insertAtTheBottom = insertAtTheBottom;
+
+               // create a container div for tiles
+               this._initContainer();
+               this._initMapObject();
+
+               // set up events
+               map.on('viewreset', this._resetCallback, this);
+
+               this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
+               map.on('move', this._update, this);
+
+               map.on('zoomanim', this._handleZoomAnim, this);
+
+               //20px instead of 1em to avoid a slight overlap with google's attribution
+               map._controlCorners.bottomright.style.marginBottom = '20px';
+
+               this._reset();
+               this._update();
+       },
+
+       onRemove: function(map) {
+               map._container.removeChild(this._container);
+
+               map.off('viewreset', this._resetCallback, this);
+
+               map.off('move', this._update, this);
+
+               map.off('zoomanim', this._handleZoomAnim, this);
+
+               map._controlCorners.bottomright.style.marginBottom = '0em';
+       },
+
+       getAttribution: function() {
+               return this.options.attribution;
+       },
+
+       setOpacity: function(opacity) {
+               this.options.opacity = opacity;
+               if (opacity < 1) {
+                       L.DomUtil.setOpacity(this._container, opacity);
+               }
+       },
+
+       setElementSize: function(e, size) {
+               e.style.width = size.x + 'px';
+               e.style.height = size.y + 'px';
+       },
+
+       _initContainer: function() {
+               var tilePane = this._map._container,
+                       first = tilePane.firstChild;
+
+               if (!this._container) {
+                       this._container = L.DomUtil.create('div', 'leaflet-google-layer leaflet-top leaflet-left');
+                       this._container.id = '_GMapContainer_' + L.Util.stamp(this);
+                       this._container.style.zIndex = 'auto';
+               }
+
+               tilePane.insertBefore(this._container, first);
+
+               this.setOpacity(this.options.opacity);
+               this.setElementSize(this._container, this._map.getSize());
+       },
+
+       _initMapObject: function() {
+               if (!this._ready) return;
+               this._google_center = new google.maps.LatLng(0, 0);
+               var map = new google.maps.Map(this._container, {
+                   center: this._google_center,
+                   zoom: 0,
+                   tilt: 0,
+                   mapTypeId: google.maps.MapTypeId[this._type],
+                   disableDefaultUI: true,
+                   keyboardShortcuts: false,
+                   draggable: false,
+                   disableDoubleClickZoom: true,
+                   scrollwheel: false,
+                   streetViewControl: false,
+                   styles: this.options.mapOptions.styles,
+                   backgroundColor: this.options.mapOptions.backgroundColor
+               });
+
+               var _this = this;
+               this._reposition = google.maps.event.addListenerOnce(map, 'center_changed',
+                       function() { _this.onReposition(); });
+               this._google = map;
+
+               google.maps.event.addListenerOnce(map, 'idle',
+                       function() { _this._checkZoomLevels(); });
+               //Reporting that map-object was initialized.
+               this.fire('MapObjectInitialized', { mapObject: map });
+       },
+
+       _checkZoomLevels: function() {
+               //setting the zoom level on the Google map may result in a different zoom level than the one requested
+               //(it won't go beyond the level for which they have data).
+               // verify and make sure the zoom levels on both Leaflet and Google maps are consistent
+               if (this._google.getZoom() !== this._map.getZoom()) {
+                       //zoom levels are out of sync. Set the leaflet zoom level to match the google one
+                       this._map.setZoom( this._google.getZoom() );
+               }
+       },
+
+       _resetCallback: function(e) {
+               this._reset(e.hard);
+       },
+
+       _reset: function(clearOldContainer) {
+               this._initContainer();
+       },
+
+       _update: function(e) {
+               if (!this._google) return;
+               this._resize();
+
+               var center = this._map.getCenter();
+               var _center = new google.maps.LatLng(center.lat, center.lng);
+
+               this._google.setCenter(_center);
+               this._google.setZoom(Math.round(this._map.getZoom()));
+
+               this._checkZoomLevels();
+       },
+
+       _resize: function() {
+               var size = this._map.getSize();
+               if (this._container.style.width === size.x &&
+                   this._container.style.height === size.y)
+                       return;
+               this.setElementSize(this._container, size);
+               this.onReposition();
+       },
+
+
+       _handleZoomAnim: function (e) {
+               var center = e.center;
+               var _center = new google.maps.LatLng(center.lat, center.lng);
+
+               this._google.setCenter(_center);
+               this._google.setZoom(Math.round(e.zoom));
+       },
+
+
+       onReposition: function() {
+               if (!this._google) return;
+               google.maps.event.trigger(this._google, 'resize');
+       }
+});
+
+L.Google.asyncWait = [];
+L.Google.asyncInitialize = function() {
+       var i;
+       for (i = 0; i < L.Google.asyncWait.length; i++) {
+               var o = L.Google.asyncWait[i];
+               o._ready = true;
+               if (o._container) {
+                       o._initMapObject();
+                       o._update();
+               }
+       }
+       L.Google.asyncWait = [];
+};
diff --git a/www/plugins/gis/lib/leaflet/plugins/KML.js b/www/plugins/gis/lib/leaflet/plugins/KML.js
new file mode 100755 (executable)
index 0000000..2a7839d
--- /dev/null
@@ -0,0 +1,350 @@
+L.KML = L.FeatureGroup.extend({
+       options: {
+               async: true
+       },
+
+       initialize: function(kml, options) {
+               L.Util.setOptions(this, options);
+               this._kml = kml;
+               this._layers = {};
+
+               if (kml) {
+                       this.addKML(kml, options, this.options.async);
+               }
+       },
+
+       loadXML: function(url, cb, options, async) {
+               if (async === undefined) async = this.options.async;
+               if (options === undefined) options = this.options;
+
+               var req = new window.XMLHttpRequest();
+               req.open('GET', url, async);
+               try {
+                       req.overrideMimeType('text/xml'); // unsupported by IE
+               } catch(e) {}
+               req.onreadystatechange = function() {
+                       if (req.readyState !== 4) return;
+                       if (req.status === 200) cb(req.responseXML, options);
+               };
+               req.send(null);
+       },
+
+       addKML: function(url, options, async) {
+               var _this = this;
+               var cb = function(gpx, options) { _this._addKML(gpx, options); };
+               this.loadXML(url, cb, options, async);
+       },
+
+       _addKML: function(xml, options) {
+               var layers = L.KML.parseKML(xml);
+               if (!layers || !layers.length) return;
+               for (var i = 0; i < layers.length; i++) {
+                       this.fire('addlayer', {
+                               layer: layers[i]
+                       });
+                       this.addLayer(layers[i]);
+               }
+               this.latLngs = L.KML.getLatLngs(xml);
+               this.fire('loaded');
+       },
+
+       latLngs: []
+});
+
+L.Util.extend(L.KML, {
+
+       parseKML: function (xml) {
+               var style = this.parseStyle(xml);
+               this.parseStyleMap(xml, style);
+               var el = xml.getElementsByTagName('Folder');
+               var layers = [], l;
+               for (var i = 0; i < el.length; i++) {
+                       if (!this._check_folder(el[i])) { continue; }
+                       l = this.parseFolder(el[i], style);
+                       if (l) { layers.push(l); }
+               }
+               el = xml.getElementsByTagName('Placemark');
+               for (var j = 0; j < el.length; j++) {
+                       if (!this._check_folder(el[j])) { continue; }
+                       l = this.parsePlacemark(el[j], xml, style);
+                       if (l) { layers.push(l); }
+               }
+               return layers;
+       },
+
+       // Return false if e's first parent Folder is not [folder]
+       // - returns true if no parent Folders
+       _check_folder: function (e, folder) {
+               e = e.parentElement;
+               while (e && e.tagName !== 'Folder')
+               {
+                       e = e.parentElement;
+               }
+               return !e || e === folder;
+       },
+
+       parseStyle: function (xml) {
+               var style = {};
+               var sl = xml.getElementsByTagName('Style');
+
+               //for (var i = 0; i < sl.length; i++) {
+               var attributes = {color: true, width: true, Icon: true, href: true,
+                                                 hotSpot: true};
+
+               function _parse(xml) {
+                       var options = {};
+                       for (var i = 0; i < xml.childNodes.length; i++) {
+                               var e = xml.childNodes[i];
+                               var key = e.tagName;
+                               if (!attributes[key]) { continue; }
+                               if (key === 'hotSpot')
+                               {
+                                       for (var j = 0; j < e.attributes.length; j++) {
+                                               options[e.attributes[j].name] = e.attributes[j].nodeValue;
+                                       }
+                               } else {
+                                       var value = e.childNodes[0].nodeValue;
+                                       if (key === 'color') {
+                                               options.opacity = parseInt(value.substring(0, 2), 16) / 255.0;
+                                               options.color = '#' + value.substring(6, 8) + value.substring(4, 6) + value.substring(2, 4);
+                                       } else if (key === 'width') {
+                                               options.weight = value;
+                                       } else if (key === 'Icon') {
+                                               ioptions = _parse(e);
+                                               if (ioptions.href) { options.href = ioptions.href; }
+                                       } else if (key === 'href') {
+                                               options.href = value;
+                                       }
+                               }
+                       }
+                       return options;
+               }
+
+               for (var i = 0; i < sl.length; i++) {
+                       var e = sl[i], el;
+                       var options = {}, poptions = {}, ioptions = {};
+                       el = e.getElementsByTagName('LineStyle');
+                       if (el && el[0]) { options = _parse(el[0]); }
+                       el = e.getElementsByTagName('PolyStyle');
+                       if (el && el[0]) { poptions = _parse(el[0]); }
+                       if (poptions.color) { options.fillColor = poptions.color; }
+                       if (poptions.opacity) { options.fillOpacity = poptions.opacity; }
+                       el = e.getElementsByTagName('IconStyle');
+                       if (el && el[0]) { ioptions = _parse(el[0]); }
+                       if (ioptions.href) {
+                               // save anchor info until the image is loaded
+                               options.icon = new L.KMLIcon({
+                                       iconUrl: ioptions.href,
+                                       shadowUrl: null,
+                                       iconAnchorRef: {x: ioptions.x, y: ioptions.y},
+                                       iconAnchorType: {x: ioptions.xunits, y: ioptions.yunits}
+                               });
+                       }
+                       style['#' + e.getAttribute('id')] = options;
+               }
+               return style;
+       },
+       
+       parseStyleMap: function (xml, existingStyles) {
+               var sl = xml.getElementsByTagName('StyleMap');
+               
+               for (var i = 0; i < sl.length; i++) {
+                       var e = sl[i], el;
+                       var smKey, smStyleUrl;
+                       
+                       el = e.getElementsByTagName('key');
+                       if (el && el[0]) { smKey = el[0].textContent; }
+                       el = e.getElementsByTagName('styleUrl');
+                       if (el && el[0]) { smStyleUrl = el[0].textContent; }
+                       
+                       if (smKey === 'normal')
+                       {
+                               existingStyles['#' + e.getAttribute('id')] = existingStyles[smStyleUrl];
+                       }
+               }
+               
+               return;
+       },
+
+       parseFolder: function (xml, style) {
+               var el, layers = [], l;
+               el = xml.getElementsByTagName('Folder');
+               for (var i = 0; i < el.length; i++) {
+                       if (!this._check_folder(el[i], xml)) { continue; }
+                       l = this.parseFolder(el[i], style);
+                       if (l) { layers.push(l); }
+               }
+               el = xml.getElementsByTagName('Placemark');
+               for (var j = 0; j < el.length; j++) {
+                       if (!this._check_folder(el[j], xml)) { continue; }
+                       l = this.parsePlacemark(el[j], xml, style);
+                       if (l) { layers.push(l); }
+               }
+               if (!layers.length) { return; }
+               if (layers.length === 1) { return layers[0]; }
+               return new L.FeatureGroup(layers);
+       },
+
+       parsePlacemark: function (place, xml, style) {
+               var i, j, el, options = {};
+               el = place.getElementsByTagName('styleUrl');
+               for (i = 0; i < el.length; i++) {
+                       var url = el[i].childNodes[0].nodeValue;
+                       for (var a in style[url]) {
+                               options[a] = style[url][a];
+                       }
+               }
+               var layers = [];
+
+               var parse = ['LineString', 'Polygon', 'Point'];
+               for (j in parse) {
+                       // for jshint
+                       if (true)
+                       {
+                               var tag = parse[j];
+                               el = place.getElementsByTagName(tag);
+                               for (i = 0; i < el.length; i++) {
+                                       var l = this['parse' + tag](el[i], xml, options);
+                                       if (l) { layers.push(l); }
+                               }
+                       }
+               }
+
+               if (!layers.length) {
+                       return;
+               }
+               var layer = layers[0];
+               if (layers.length > 1) {
+                       layer = new L.FeatureGroup(layers);
+               }
+
+               var name, descr = '';
+               el = place.getElementsByTagName('name');
+               if (el.length && el[0].childNodes.length) {
+                       name = el[0].childNodes[0].nodeValue;
+               }
+               el = place.getElementsByTagName('description');
+               for (i = 0; i < el.length; i++) {
+                       for (j = 0; j < el[i].childNodes.length; j++) {
+                               descr = descr + el[i].childNodes[j].nodeValue;
+                       }
+               }
+
+               if (name) {
+                       layer.bindPopup('<h2>' + name + '</h2>' + descr);
+               }
+
+               return layer;
+       },
+
+       parseCoords: function (xml) {
+               var el = xml.getElementsByTagName('coordinates');
+               return this._read_coords(el[0]);
+       },
+
+       parseLineString: function (line, xml, options) {
+               var coords = this.parseCoords(line);
+               if (!coords.length) { return; }
+               return new L.Polyline(coords, options);
+       },
+
+       parsePoint: function (line, xml, options) {
+               var el = line.getElementsByTagName('coordinates');
+               if (!el.length) {
+                       return;
+               }
+               var ll = el[0].childNodes[0].nodeValue.split(',');
+               return new L.KMLMarker(new L.LatLng(ll[1], ll[0]), options);
+       },
+
+       parsePolygon: function (line, xml, options) {
+               var el, polys = [], inner = [], i, coords;
+               el = line.getElementsByTagName('outerBoundaryIs');
+               for (i = 0; i < el.length; i++) {
+                       coords = this.parseCoords(el[i]);
+                       if (coords) {
+                               polys.push(coords);
+                       }
+               }
+               el = line.getElementsByTagName('innerBoundaryIs');
+               for (i = 0; i < el.length; i++) {
+                       coords = this.parseCoords(el[i]);
+                       if (coords) {
+                               inner.push(coords);
+                       }
+               }
+               if (!polys.length) {
+                       return;
+               }
+               if (options.fillColor) {
+                       options.fill = true;
+               }
+               if (polys.length === 1) {
+                       return new L.Polygon(polys.concat(inner), options);
+               }
+               return new L.MultiPolygon(polys, options);
+       },
+
+       getLatLngs: function (xml) {
+               var el = xml.getElementsByTagName('coordinates');
+               var coords = [];
+               for (var j = 0; j < el.length; j++) {
+                       // text might span many childNodes
+                       coords = coords.concat(this._read_coords(el[j]));
+               }
+               return coords;
+       },
+
+       _read_coords: function (el) {
+               var text = '', coords = [], i;
+               for (i = 0; i < el.childNodes.length; i++) {
+                       text = text + el.childNodes[i].nodeValue;
+               }
+               text = text.split(/[\s\n]+/);
+               for (i = 0; i < text.length; i++) {
+                       var ll = text[i].split(',');
+                       if (ll.length < 2) {
+                               continue;
+                       }
+                       coords.push(new L.LatLng(ll[1], ll[0]));
+               }
+               return coords;
+       }
+
+});
+
+L.KMLIcon = L.Icon.extend({
+
+       createIcon: function () {
+               var img = this._createIcon('icon');
+               img.onload = function () {
+                       var i = img;
+                       this.style.width = i.width + 'px';
+                       this.style.height = i.height + 'px';
+
+                       if (this.anchorType.x === 'UNITS_FRACTION' || this.anchorType.x === 'fraction') {
+                               img.style.marginLeft = (-this.anchor.x * i.width) + 'px';
+                       }
+                       if (this.anchorType.y === 'UNITS_FRACTION' || this.anchorType.x === 'fraction') {
+                               img.style.marginTop  = (-(1 - this.anchor.y) * i.height) + 'px';
+                       }
+                       this.style.display = '';
+               };
+               return img;
+       },
+
+       _setIconStyles: function (img, name) {
+               L.Icon.prototype._setIconStyles.apply(this, [img, name]);
+               // save anchor information to the image
+               img.anchor = this.options.iconAnchorRef;
+               img.anchorType = this.options.iconAnchorType;
+       }
+});
+
+
+L.KMLMarker = L.Marker.extend({
+       options: {
+               icon: new L.KMLIcon.Default()
+       }
+});
+
diff --git a/www/plugins/gis/lib/leaflet/plugins/Marker.Rotate.js b/www/plugins/gis/lib/leaflet/plugins/Marker.Rotate.js
new file mode 100755 (executable)
index 0000000..32a8741
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Based on comments by @runanet and @coomsie 
+ * https://github.com/CloudMade/Leaflet/issues/386
+ *
+ * Wrapping function is needed to preserve L.Marker.update function
+ */
+(function () {
+       var _old__setPos = L.Marker.prototype._setPos;
+       L.Marker.include({
+               _updateImg: function(i, a, s) {
+                       a = L.point(s).divideBy(2)._subtract(L.point(a));
+                       var transform = '';
+                       transform += ' translate(' + -a.x + 'px, ' + -a.y + 'px)';
+                       transform += ' rotate(' + this.options.iconAngle + 'deg)';
+                       transform += ' translate(' + a.x + 'px, ' + a.y + 'px)';
+                       i.style[L.DomUtil.TRANSFORM] += transform;
+               },
+
+               setIconAngle: function (iconAngle) {
+                       this.options.iconAngle = iconAngle;
+                       if (this._map)
+                               this.update();
+               },
+
+               _setPos: function (pos) {
+                       if (this._icon)
+                               this._icon.style[L.DomUtil.TRANSFORM] = '';
+                       if (this._shadow)
+                               this._shadow.style[L.DomUtil.TRANSFORM] = '';
+
+                       _old__setPos.apply(this,[pos]);
+
+                       if (this.options.iconAngle) {
+                               var a = this.options.icon.options.iconAnchor;
+                               var s = this.options.icon.options.iconSize;
+                               var i;
+                               if (this._icon) {
+                                       i = this._icon;
+                                       this._updateImg(i, a, s);
+                               }
+                               if (this._shadow) {
+                                       if (this.options.icon.options.shadowAnchor)
+                                               a = this.options.icon.options.shadowAnchor;
+                                       s = this.options.icon.options.shadowSize;
+                                       i = this._shadow;
+                                       this._updateImg(i, a, s);
+                               }
+                       }
+               }
+       });
+}());
diff --git a/www/plugins/gis/lib/leaflet/plugins/images/icon-fullscreen-2x.png b/www/plugins/gis/lib/leaflet/plugins/images/icon-fullscreen-2x.png
new file mode 100644 (file)
index 0000000..7320d95
Binary files /dev/null and b/www/plugins/gis/lib/leaflet/plugins/images/icon-fullscreen-2x.png differ
diff --git a/www/plugins/gis/lib/leaflet/plugins/images/icon-fullscreen.png b/www/plugins/gis/lib/leaflet/plugins/images/icon-fullscreen.png
new file mode 100755 (executable)
index 0000000..fdb9a70
Binary files /dev/null and b/www/plugins/gis/lib/leaflet/plugins/images/icon-fullscreen.png differ
diff --git a/www/plugins/gis/lib/leaflet/plugins/images/toggle.png b/www/plugins/gis/lib/leaflet/plugins/images/toggle.png
new file mode 100755 (executable)
index 0000000..205e38b
Binary files /dev/null and b/www/plugins/gis/lib/leaflet/plugins/images/toggle.png differ
diff --git a/www/plugins/gis/lib/leaflet/plugins/leaflet-plugins.css b/www/plugins/gis/lib/leaflet/plugins/leaflet-plugins.css
new file mode 100755 (executable)
index 0000000..3357365
--- /dev/null
@@ -0,0 +1,16 @@
+/* L.Control.FullScreen */
+.leaflet-control-zoom-fullscreen { background-image: url(images/icon-fullscreen.png); }
+.leaflet-retina .leaflet-control-zoom-fullscreen { background-image: url(images/icon-fullscreen-2x.png); background-size: 26px 26px; }
+.leaflet-container:-webkit-full-screen { width: 100% !important; height: 100% !important; z-index: 99999; }
+.leaflet-pseudo-fullscreen { position: fixed !important; width: 100% !important; height: 100% !important; top: 0px !important; left: 0px !important; z-index: 99999; }
+
+/* MiniMap Plugin */
+.leaflet-control-minimap { border: solid rgba(255, 255, 255, 1.0) 4px; box-shadow: 0 1px 5px rgba(0,0,0,0.65); border-radius: 3px; background: #f8f8f9; transition: all .2s; }
+.leaflet-control-minimap a { background-color: rgba(255, 255, 255, 1.0); background-repeat: no-repeat; z-index: 99999; transition: all .2s; border-radius: 3px 0px 0px 0px; }
+.leaflet-control-minimap a.minimized { -webkit-transform: rotate(180deg); transform: rotate(180deg); border-radius: 0px; }
+.leaflet-control-minimap-toggle-display { background-image: url("images/toggle.png"); height: 19px; width: 19px; position: absolute; bottom: 0; right: 0; }
+/* Old IE */
+.leaflet-oldie .leaflet-control-minimap { border: 1px solid #999 }
+.leaflet-oldie .leaflet-control-minimap a { background-color: #fff }
+.leaflet-oldie .leaflet-control-minimap a.minimized { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2) }
+
diff --git a/www/plugins/gis/lib/leaflet/plugins/leaflet-providers.js b/www/plugins/gis/lib/leaflet/plugins/leaflet-providers.js
new file mode 100644 (file)
index 0000000..62f650a
--- /dev/null
@@ -0,0 +1,460 @@
+(function () {
+       'use strict';
+
+       L.TileLayer.Provider = L.TileLayer.extend({
+               initialize: function (arg, options) {
+                       var providers = L.TileLayer.Provider.providers;
+
+                       var parts = arg.split('.');
+
+                       var providerName = parts[0];
+                       var variantName = parts[1];
+
+                       if (!providers[providerName]) {
+                               throw 'No such provider (' + providerName + ')';
+                       }
+
+                       var provider = {
+                               url: providers[providerName].url,
+                               options: providers[providerName].options
+                       };
+
+                       // overwrite values in provider from variant.
+                       if (variantName && 'variants' in providers[providerName]) {
+                               if (!(variantName in providers[providerName].variants)) {
+                                       throw 'No such variant of ' + providerName + ' (' + variantName + ')';
+                               }
+                               var variant = providers[providerName].variants[variantName];
+                               var variantOptions;
+                               if (typeof variant === 'string') {
+                                       variantOptions = {
+                                               variant: variant
+                                       };
+                               } else {
+                                       variantOptions = variant.options;
+                               }
+                               provider = {
+                                       url: variant.url || provider.url,
+                                       options: L.Util.extend({}, provider.options, variantOptions)
+                               };
+                       } else if (typeof provider.url === 'function') {
+                               provider.url = provider.url(parts.splice(1, parts.length - 1).join('.'));
+                       }
+
+                       // replace attribution placeholders with their values from toplevel provider attribution,
+                       // recursively
+                       var attributionReplacer = function (attr) {
+                               if (attr.indexOf('{attribution.') === -1) {
+                                       return attr;
+                               }
+                               return attr.replace(/\{attribution.(\w*)\}/,
+                                       function (match, attributionName) {
+                                               return attributionReplacer(providers[attributionName].options.attribution);
+                                       }
+                               );
+                       };
+                       provider.options.attribution = attributionReplacer(provider.options.attribution);
+
+                       // Compute final options combining provider options with any user overrides
+                       var layerOpts = L.Util.extend({}, provider.options, options);
+                       L.TileLayer.prototype.initialize.call(this, provider.url, layerOpts);
+               }
+       });
+
+       /**
+        * Definition of providers.
+        * see http://leafletjs.com/reference.html#tilelayer for options in the options map.
+        */
+
+       //jshint maxlen:220
+       L.TileLayer.Provider.providers = {
+               OpenStreetMap: {
+                       url: 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
+                       options: {
+                               attribution:
+                                       '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
+                       },
+                       variants: {
+                               Mapnik: {},
+                               BlackAndWhite: {
+                                       url: 'http://{s}.tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png'
+                               },
+                               DE: {
+                                       url: 'http://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png'
+                               },
+                               HOT: {
+                                       url: 'http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png',
+                                       options: {
+                                               attribution: '{attribution.OpenStreetMap}, Tiles courtesy of <a href="http://hot.openstreetmap.org/" target="_blank">Humanitarian OpenStreetMap Team</a>'
+                                       }
+                               }
+                       }
+               },
+               OpenSeaMap: {
+                       url: 'http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png',
+                       options: {
+                               attribution: 'Map data: &copy; <a href="http://www.openseamap.org">OpenSeaMap</a> contributors'
+                       }
+               },
+               Thunderforest: {
+                       url: 'http://{s}.tile.thunderforest.com/{variant}/{z}/{x}/{y}.png',
+                       options: {
+                               attribution:
+                                       '&copy; <a href="http://www.opencyclemap.org">OpenCycleMap</a>, {attribution.OpenStreetMap}',
+                               variant: 'cycle'
+                       },
+                       variants: {
+                               OpenCycleMap: 'cycle',
+                               Transport: 'transport',
+                               Landscape: 'landscape',
+                               Outdoors: 'outdoors'
+                       }
+               },
+               OpenMapSurfer: {
+                       url: 'http://openmapsurfer.uni-hd.de/tiles/{variant}/x={x}&y={y}&z={z}',
+                       options: {
+                               minZoom: 0,
+                               maxZoom: 20,
+                               variant: 'roads',
+                               attribution: 'Imagery from <a href="http://giscience.uni-hd.de/">GIScience Research Group @ University of Heidelberg</a> &mdash; Map data {attribution.OpenStreetMap}'
+                       },
+                       variants: {
+                               Roads: 'roads',
+                               AdminBounds: {
+                                       options: {
+                                               variant: 'adminb',
+                                               maxZoom: 19
+                                       }
+                               },
+                               Grayscale: {
+                                       options: {
+                                               variant: 'roadsg',
+                                               maxZoom: 19
+                                       }
+                               }
+                       }
+               },
+               Hydda: {
+                       url: 'http://{s}.tile.openstreetmap.se/hydda/{variant}/{z}/{x}/{y}.png',
+                       options: {
+                               minZoom: 0,
+                               maxZoom: 18,
+                               variant: 'full',
+                               attribution: 'Tiles courtesy of <a href="http://openstreetmap.se/" target="_blank">OpenStreetMap Sweden</a> &mdash; Map data {attribution.OpenStreetMap}'
+                       },
+                       variants: {
+                               Full: 'full',
+                               Base: 'base',
+                               RoadsAndLabels: 'roads_and_labels',
+                       }
+               },
+               MapQuestOpen: {
+                       url: 'http://otile{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpeg',
+                       options: {
+                               attribution:
+                                       'Tiles Courtesy of <a href="http://www.mapquest.com/">MapQuest</a> &mdash; ' +
+                                       'Map data {attribution.OpenStreetMap}',
+                               subdomains: '1234'
+                       },
+                       variants: {
+                               OSM: {},
+                               Aerial: {
+                                       url: 'http://oatile{s}.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg',
+                                       options: {
+                                               attribution:
+                                                       'Tiles Courtesy of <a href="http://www.mapquest.com/">MapQuest</a> &mdash; ' +
+                                                       'Portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency'
+                                       }
+                               }
+                       }
+               },
+               MapBox: {
+                       url: function (id) {
+                               return 'http://{s}.tiles.mapbox.com/v3/' + id + '/{z}/{x}/{y}.png';
+                       },
+                       options: {
+                               attribution:
+                                       'Imagery from <a href="http://mapbox.com/about/maps/">MapBox</a> &mdash; ' +
+                                       'Map data {attribution.OpenStreetMap}',
+                               subdomains: 'abcd'
+                       }
+               },
+               Stamen: {
+                       url: 'http://{s}.tile.stamen.com/{variant}/{z}/{x}/{y}.png',
+                       options: {
+                               attribution:
+                                       'Map tiles by <a href="http://stamen.com">Stamen Design</a>, ' +
+                                       '<a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> &mdash; ' +
+                                       'Map data {attribution.OpenStreetMap}',
+                               subdomains: 'abcd',
+                               minZoom: 0,
+                               maxZoom: 20,
+                               variant: 'toner'
+                       },
+                       variants: {
+                               Toner: 'toner',
+                               TonerBackground: 'toner-background',
+                               TonerHybrid: 'toner-hybrid',
+                               TonerLines: 'toner-lines',
+                               TonerLabels: 'toner-labels',
+                               TonerLite: 'toner-lite',
+                               Terrain: {
+                                       options: {
+                                               variant: 'terrain',
+                                               minZoom: 4,
+                                               maxZoom: 18
+                                       }
+                               },
+                               TerrainBackground: {
+                                       options: {
+                                               variant: 'terrain-background',
+                                               minZoom: 4,
+                                               maxZoom: 18
+                                       }
+                               },
+                               Watercolor: {
+                                       options: {
+                                               variant: 'watercolor',
+                                               minZoom: 1,
+                                               maxZoom: 16
+                                       }
+                               }
+                       }
+               },
+               Esri: {
+                       url: 'http://server.arcgisonline.com/ArcGIS/rest/services/{variant}/MapServer/tile/{z}/{y}/{x}',
+                       options: {
+                               variant: 'World_Street_Map',
+                               attribution: 'Tiles &copy; Esri'
+                       },
+                       variants: {
+                               WorldStreetMap: {
+                                       options: {
+                                               attribution:
+                                                       '{attribution.Esri} &mdash; ' +
+                                                       'Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, Esri China (Hong Kong), Esri (Thailand), TomTom, 2012'
+                                       }
+                               },
+                               DeLorme: {
+                                       options: {
+                                               variant: 'Specialty/DeLorme_World_Base_Map',
+                                               minZoom: 1,
+                                               maxZoom: 11,
+                                               attribution: '{attribution.Esri} &mdash; Copyright: &copy;2012 DeLorme'
+                                       }
+                               },
+                               WorldTopoMap: {
+                                       options: {
+                                               variant: 'World_Topo_Map',
+                                               attribution:
+                                                       '{attribution.Esri} &mdash; ' +
+                                                       'Esri, DeLorme, NAVTEQ, TomTom, Intermap, iPC, USGS, FAO, NPS, NRCAN, GeoBase, Kadaster NL, Ordnance Survey, Esri Japan, METI, Esri China (Hong Kong), and the GIS User Community'
+                                       }
+                               },
+                               WorldImagery: {
+                                       options: {
+                                               variant: 'World_Imagery',
+                                               attribution:
+                                                       '{attribution.Esri} &mdash; ' +
+                                                       'Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
+                                       }
+                               },
+                               WorldTerrain: {
+                                       options: {
+                                               variant: 'World_Terrain_Base',
+                                               maxZoom: 13,
+                                               attribution:
+                                                       '{attribution.Esri} &mdash; ' +
+                                                       'Source: USGS, Esri, TANA, DeLorme, and NPS'
+                                       }
+                               },
+                               WorldShadedRelief: {
+                                       options: {
+                                               variant: 'World_Shaded_Relief',
+                                               maxZoom: 13,
+                                               attribution: '{attribution.Esri} &mdash; Source: Esri'
+                                       }
+                               },
+                               WorldPhysical: {
+                                       options: {
+                                               variant: 'World_Physical_Map',
+                                               maxZoom: 8,
+                                               attribution: '{attribution.Esri} &mdash; Source: US National Park Service'
+                                       }
+                               },
+                               OceanBasemap: {
+                                       options: {
+                                               variant: 'Ocean_Basemap',
+                                               maxZoom: 13,
+                                               attribution: '{attribution.Esri} &mdash; Sources: GEBCO, NOAA, CHS, OSU, UNH, CSUMB, National Geographic, DeLorme, NAVTEQ, and Esri'
+                                       }
+                               },
+                               NatGeoWorldMap: {
+                                       options: {
+                                               variant: 'NatGeo_World_Map',
+                                               maxZoom: 16,
+                                               attribution: '{attribution.Esri} &mdash; National Geographic, Esri, DeLorme, NAVTEQ, UNEP-WCMC, USGS, NASA, ESA, METI, NRCAN, GEBCO, NOAA, iPC'
+                                       }
+                               },
+                               WorldGrayCanvas: {
+                                       options: {
+                                               variant: 'Canvas/World_Light_Gray_Base',
+                                               maxZoom: 16,
+                                               attribution: '{attribution.Esri} &mdash; Esri, DeLorme, NAVTEQ'
+                                       }
+                               }
+                       }
+               },
+               OpenWeatherMap: {
+                       url: 'http://{s}.tile.openweathermap.org/map/{variant}/{z}/{x}/{y}.png',
+                       options: {
+                               attribution: 'Map data &copy; <a href="http://openweathermap.org">OpenWeatherMap</a>',
+                               opacity: 0.5
+                       },
+                       variants: {
+                               Clouds: 'clouds',
+                               CloudsClassic: 'clouds_cls',
+                               Precipitation: 'precipitation',
+                               PrecipitationClassic: 'precipitation_cls',
+                               Rain: 'rain',
+                               RainClassic: 'rain_cls',
+                               Pressure: 'pressure',
+                               PressureContour: 'pressure_cntr',
+                               Wind: 'wind',
+                               Temperature: 'temp',
+                               Snow: 'snow'
+                       }
+               },
+               HERE: {
+                       /*
+                        * HERE maps, formerly Nokia maps.
+                        * These basemaps are free, but you need an API key. Please sign up at
+                        * http://developer.here.com/getting-started
+                        *
+                        * Note that the base urls contain '.cit' whichs is HERE's
+                        * 'Customer Integration Testing' environment. Please remove for production
+                        * envirionments.
+                        */
+                       url:
+                               'http://{s}.{base}.maps.cit.api.here.com/maptile/2.1/' +
+                               'maptile/{mapID}/{variant}/{z}/{x}/{y}/256/png8?' +
+                               'app_id={app_id}&app_code={app_code}',
+                       options: {
+                               attribution:
+                                       'Map &copy; 1987-2014 <a href="http://developer.here.com">HERE</a>',
+                               subdomains: '1234',
+                               mapID: 'newest',
+                               'app_id': '<insert your app_id here>',
+                               'app_code': '<insert your app_code here>',
+                               base: 'base',
+                               variant: 'normal.day',
+                               minZoom: 0,
+                               maxZoom: 20
+                       },
+                       variants: {
+                               normalDay: 'normal.day',
+                               normalDayCustom: 'normal.day.custom',
+                               normalDayGrey: 'normal.day.grey',
+                               normalDayMobile: 'normal.day.mobile',
+                               normalDayGreyMobile: 'normal.day.grey.mobile',
+                               normalDayTransit: 'normal.day.transit',
+                               normalDayTransitMobile: 'normal.day.transit.mobile',
+                               normalNight: 'normal.night',
+                               normalNightMobile: 'normal.night.mobile',
+                               normalNightGrey: 'normal.night.grey',
+                               normalNightGreyMobile: 'normal.night.grey.mobile',
+
+                               carnavDayGrey: 'carnav.day.grey',
+                               hybridDay: {
+                                       options: {
+                                               base: 'aerial',
+                                               variant: 'hybrid.day'
+                                       }
+                               },
+                               hybridDayMobile: {
+                                       options: {
+                                               base: 'aerial',
+                                               variant: 'hybrid.day.mobile'
+                                       }
+                               },
+                               pedestrianDay: 'pedestrian.day',
+                               pedestrianNight: 'pedestrian.night',
+                               satelliteDay: {
+                                       options: {
+                                               base: 'aerial',
+                                               variant: 'satellite.day'
+                                       }
+                               },
+                               terrainDay: {
+                                       options: {
+                                               base: 'aerial',
+                                               variant: 'terrain.day'
+                                       }
+                               },
+                               terrainDayMobile: {
+                                       options: {
+                                               base: 'aerial',
+                                               variant: 'terrain.day.mobile'
+                                       }
+                               }
+                       }
+               },
+               Acetate: {
+                       url: 'http://a{s}.acetate.geoiq.com/tiles/{variant}/{z}/{x}/{y}.png',
+                       options: {
+                               attribution:
+                                       '&copy;2012 Esri & Stamen, Data from OSM and Natural Earth',
+                               subdomains: '0123',
+                               minZoom: 2,
+                               maxZoom: 18,
+                               variant: 'acetate-base'
+                       },
+                       variants: {
+                               basemap: 'acetate-base',
+                               terrain: 'terrain',
+                               all: 'acetate-hillshading',
+                               foreground: 'acetate-fg',
+                               roads: 'acetate-roads',
+                               labels: 'acetate-labels',
+                               hillshading: 'hillshading'
+                       }
+               },
+               FreeMapSK: {
+                       url: 'http://{s}.freemap.sk/T/{z}/{x}/{y}.jpeg',
+                       options: {
+                               minZoom: 8,
+                               maxZoom: 16,
+                               subdomains: ['t1', 't2', 't3', 't4'],
+                               attribution:
+                                       '{attribution.OpenStreetMap}, vizualization CC-By-SA 2.0 <a href="http://freemap.sk">Freemap.sk</a>'
+                       }
+               },
+               MtbMap: {
+                       url: 'http://tile.mtbmap.cz/mtbmap_tiles/{z}/{x}/{y}.png',
+                       options: {
+                               attribution:
+                                       '{attribution.OpenStreetMap} &amp; USGS'
+                       }
+               },
+               CartoDB: {
+                       url: 'http://{s}.basemaps.cartocdn.com/{variant}/{z}/{x}/{y}.png',
+                       options: {
+                               attribution: '{attribution.OpenStreetMap} &copy; <a href="http://cartodb.com/attributions">CartoDB</a>',
+                               subdomains: 'abcd',
+                               minZoom: 0,
+                               maxZoom: 18,
+                               variant: 'light_all'
+                       },
+                       variants: {
+                               Positron: 'light_all',
+                               PositronNoLabels: 'light_nolabels',
+                               DarkMatter: 'dark_all',
+                               DarkMatterNoLabels: 'dark_nolabels'
+                       }
+               }
+       };
+
+       L.tileLayer.provider = function (provider, options) {
+               return new L.TileLayer.Provider(provider, options);
+       };
+}());
diff --git a/www/plugins/gis/lib/leaflet/plugins/leaflet.markercluster-src.js b/www/plugins/gis/lib/leaflet/plugins/leaflet.markercluster-src.js
new file mode 100644 (file)
index 0000000..1ca4767
--- /dev/null
@@ -0,0 +1,2163 @@
+/*
+ Leaflet.markercluster, Provides Beautiful Animated Marker Clustering functionality for Leaflet, a JS library for interactive maps.
+ https://github.com/Leaflet/Leaflet.markercluster
+ (c) 2012-2013, Dave Leaver, smartrak
+*/
+(function (window, document, undefined) {/*
+ * L.MarkerClusterGroup extends L.FeatureGroup by clustering the markers contained within
+ */
+
+L.MarkerClusterGroup = L.FeatureGroup.extend({
+
+       options: {
+               maxClusterRadius: 80, //A cluster will cover at most this many pixels from its center
+               iconCreateFunction: null,
+
+               spiderfyOnMaxZoom: true,
+               showCoverageOnHover: true,
+               zoomToBoundsOnClick: true,
+               singleMarkerMode: false,
+
+               disableClusteringAtZoom: null,
+
+               // Setting this to false prevents the removal of any clusters outside of the viewpoint, which
+               // is the default behaviour for performance reasons.
+               removeOutsideVisibleBounds: true,
+
+               //Whether to animate adding markers after adding the MarkerClusterGroup to the map
+               // If you are adding individual markers set to true, if adding bulk markers leave false for massive performance gains.
+               animateAddingMarkers: false,
+
+               //Increase to increase the distance away that spiderfied markers appear from the center
+               spiderfyDistanceMultiplier: 1,
+
+               // When bulk adding layers, adds markers in chunks. Means addLayers may not add all the layers in the call, others will be loaded during setTimeouts
+               chunkedLoading: false,
+               chunkInterval: 200, // process markers for a maximum of ~ n milliseconds (then trigger the chunkProgress callback)
+               chunkDelay: 50, // at the end of each interval, give n milliseconds back to system/browser
+               chunkProgress: null, // progress callback: function(processed, total, elapsed) (e.g. for a progress indicator)
+
+               //Options to pass to the L.Polygon constructor
+               polygonOptions: {}
+       },
+
+       initialize: function (options) {
+               L.Util.setOptions(this, options);
+               if (!this.options.iconCreateFunction) {
+                       this.options.iconCreateFunction = this._defaultIconCreateFunction;
+               }
+
+               this._featureGroup = L.featureGroup();
+               this._featureGroup.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);
+
+               this._nonPointGroup = L.featureGroup();
+               this._nonPointGroup.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);
+
+               this._inZoomAnimation = 0;
+               this._needsClustering = [];
+               this._needsRemoving = []; //Markers removed while we aren't on the map need to be kept track of
+               //The bounds of the currently shown area (from _getExpandedVisibleBounds) Updated on zoom/move
+               this._currentShownBounds = null;
+
+               this._queue = [];
+       },
+
+       addLayer: function (layer) {
+
+               if (layer instanceof L.LayerGroup) {
+                       var array = [];
+                       for (var i in layer._layers) {
+                               array.push(layer._layers[i]);
+                       }
+                       return this.addLayers(array);
+               }
+
+               //Don't cluster non point data
+               if (!layer.getLatLng) {
+                       this._nonPointGroup.addLayer(layer);
+                       return this;
+               }
+
+               if (!this._map) {
+                       this._needsClustering.push(layer);
+                       return this;
+               }
+
+               if (this.hasLayer(layer)) {
+                       return this;
+               }
+
+
+               //If we have already clustered we'll need to add this one to a cluster
+
+               if (this._unspiderfy) {
+                       this._unspiderfy();
+               }
+
+               this._addLayer(layer, this._maxZoom);
+
+               //Work out what is visible
+               var visibleLayer = layer,
+                       currentZoom = this._map.getZoom();
+               if (layer.__parent) {
+                       while (visibleLayer.__parent._zoom >= currentZoom) {
+                               visibleLayer = visibleLayer.__parent;
+                       }
+               }
+
+               if (this._currentShownBounds.contains(visibleLayer.getLatLng())) {
+                       if (this.options.animateAddingMarkers) {
+                               this._animationAddLayer(layer, visibleLayer);
+                       } else {
+                               this._animationAddLayerNonAnimated(layer, visibleLayer);
+                       }
+               }
+               return this;
+       },
+
+       removeLayer: function (layer) {
+
+               if (layer instanceof L.LayerGroup)
+               {
+                       var array = [];
+                       for (var i in layer._layers) {
+                               array.push(layer._layers[i]);
+                       }
+                       return this.removeLayers(array);
+               }
+
+               //Non point layers
+               if (!layer.getLatLng) {
+                       this._nonPointGroup.removeLayer(layer);
+                       return this;
+               }
+
+               if (!this._map) {
+                       if (!this._arraySplice(this._needsClustering, layer) && this.hasLayer(layer)) {
+                               this._needsRemoving.push(layer);
+                       }
+                       return this;
+               }
+
+               if (!layer.__parent) {
+                       return this;
+               }
+
+               if (this._unspiderfy) {
+                       this._unspiderfy();
+                       this._unspiderfyLayer(layer);
+               }
+
+               //Remove the marker from clusters
+               this._removeLayer(layer, true);
+
+               if (this._featureGroup.hasLayer(layer)) {
+                       this._featureGroup.removeLayer(layer);
+                       if (layer.setOpacity) {
+                               layer.setOpacity(1);
+                       }
+               }
+
+               return this;
+       },
+
+       //Takes an array of markers and adds them in bulk
+       addLayers: function (layersArray) {
+               var fg = this._featureGroup,
+                       npg = this._nonPointGroup,
+                       chunked = this.options.chunkedLoading,
+                       chunkInterval = this.options.chunkInterval,
+                       chunkProgress = this.options.chunkProgress,
+                       newMarkers, i, l, m;
+
+               if (this._map) {
+                       var offset = 0,
+                               started = (new Date()).getTime();
+                       var process = L.bind(function () {
+                               var start = (new Date()).getTime();
+                               for (; offset < layersArray.length; offset++) {
+                                       if (chunked && offset % 200 === 0) {
+                                               // every couple hundred markers, instrument the time elapsed since processing started:
+                                               var elapsed = (new Date()).getTime() - start;
+                                               if (elapsed > chunkInterval) {
+                                                       break; // been working too hard, time to take a break :-)
+                                               }
+                                       }
+
+                                       m = layersArray[offset];
+
+                                       //Not point data, can't be clustered
+                                       if (!m.getLatLng) {
+                                               npg.addLayer(m);
+                                               continue;
+                                       }
+
+                                       if (this.hasLayer(m)) {
+                                               continue;
+                                       }
+
+                                       this._addLayer(m, this._maxZoom);
+
+                                       //If we just made a cluster of size 2 then we need to remove the other marker from the map (if it is) or we never will
+                                       if (m.__parent) {
+                                               if (m.__parent.getChildCount() === 2) {
+                                                       var markers = m.__parent.getAllChildMarkers(),
+                                                               otherMarker = markers[0] === m ? markers[1] : markers[0];
+                                                       fg.removeLayer(otherMarker);
+                                               }
+                                       }
+                               }
+
+                               if (chunkProgress) {
+                                       // report progress and time elapsed:
+                                       chunkProgress(offset, layersArray.length, (new Date()).getTime() - started);
+                               }
+
+                               if (offset === layersArray.length) {
+                                       //Update the icons of all those visible clusters that were affected
+                                       this._featureGroup.eachLayer(function (c) {
+                                               if (c instanceof L.MarkerCluster && c._iconNeedsUpdate) {
+                                                       c._updateIcon();
+                                               }
+                                       });
+
+                                       this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
+                               } else {
+                                       setTimeout(process, this.options.chunkDelay);
+                               }
+                       }, this);
+
+                       process();
+               } else {
+                       newMarkers = [];
+                       for (i = 0, l = layersArray.length; i < l; i++) {
+                               m = layersArray[i];
+
+                               //Not point data, can't be clustered
+                               if (!m.getLatLng) {
+                                       npg.addLayer(m);
+                                       continue;
+                               }
+
+                               if (this.hasLayer(m)) {
+                                       continue;
+                               }
+
+                               newMarkers.push(m);
+                       }
+                       this._needsClustering = this._needsClustering.concat(newMarkers);
+               }
+               return this;
+       },
+
+       //Takes an array of markers and removes them in bulk
+       removeLayers: function (layersArray) {
+               var i, l, m,
+                       fg = this._featureGroup,
+                       npg = this._nonPointGroup;
+
+               if (!this._map) {
+                       for (i = 0, l = layersArray.length; i < l; i++) {
+                               m = layersArray[i];
+                               this._arraySplice(this._needsClustering, m);
+                               npg.removeLayer(m);
+                       }
+                       return this;
+               }
+
+               for (i = 0, l = layersArray.length; i < l; i++) {
+                       m = layersArray[i];
+
+                       if (!m.__parent) {
+                               npg.removeLayer(m);
+                               continue;
+                       }
+
+                       this._removeLayer(m, true, true);
+
+                       if (fg.hasLayer(m)) {
+                               fg.removeLayer(m);
+                               if (m.setOpacity) {
+                                       m.setOpacity(1);
+                               }
+                       }
+               }
+
+               //Fix up the clusters and markers on the map
+               this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
+
+               fg.eachLayer(function (c) {
+                       if (c instanceof L.MarkerCluster) {
+                               c._updateIcon();
+                       }
+               });
+
+               return this;
+       },
+
+       //Removes all layers from the MarkerClusterGroup
+       clearLayers: function () {
+               //Need our own special implementation as the LayerGroup one doesn't work for us
+
+               //If we aren't on the map (yet), blow away the markers we know of
+               if (!this._map) {
+                       this._needsClustering = [];
+                       delete this._gridClusters;
+                       delete this._gridUnclustered;
+               }
+
+               if (this._noanimationUnspiderfy) {
+                       this._noanimationUnspiderfy();
+               }
+
+               //Remove all the visible layers
+               this._featureGroup.clearLayers();
+               this._nonPointGroup.clearLayers();
+
+               this.eachLayer(function (marker) {
+                       delete marker.__parent;
+               });
+
+               if (this._map) {
+                       //Reset _topClusterLevel and the DistanceGrids
+                       this._generateInitialClusters();
+               }
+
+               return this;
+       },
+
+       //Override FeatureGroup.getBounds as it doesn't work
+       getBounds: function () {
+               var bounds = new L.LatLngBounds();
+
+               if (this._topClusterLevel) {
+                       bounds.extend(this._topClusterLevel._bounds);
+               }
+
+               for (var i = this._needsClustering.length - 1; i >= 0; i--) {
+                       bounds.extend(this._needsClustering[i].getLatLng());
+               }
+
+               bounds.extend(this._nonPointGroup.getBounds());
+
+               return bounds;
+       },
+
+       //Overrides LayerGroup.eachLayer
+       eachLayer: function (method, context) {
+               var markers = this._needsClustering.slice(),
+                       i;
+
+               if (this._topClusterLevel) {
+                       this._topClusterLevel.getAllChildMarkers(markers);
+               }
+
+               for (i = markers.length - 1; i >= 0; i--) {
+                       method.call(context, markers[i]);
+               }
+
+               this._nonPointGroup.eachLayer(method, context);
+       },
+
+       //Overrides LayerGroup.getLayers
+       getLayers: function () {
+               var layers = [];
+               this.eachLayer(function (l) {
+                       layers.push(l);
+               });
+               return layers;
+       },
+
+       //Overrides LayerGroup.getLayer, WARNING: Really bad performance
+       getLayer: function (id) {
+               var result = null;
+
+               this.eachLayer(function (l) {
+                       if (L.stamp(l) === id) {
+                               result = l;
+                       }
+               });
+
+               return result;
+       },
+
+       //Returns true if the given layer is in this MarkerClusterGroup
+       hasLayer: function (layer) {
+               if (!layer) {
+                       return false;
+               }
+
+               var i, anArray = this._needsClustering;
+
+               for (i = anArray.length - 1; i >= 0; i--) {
+                       if (anArray[i] === layer) {
+                               return true;
+                       }
+               }
+
+               anArray = this._needsRemoving;
+               for (i = anArray.length - 1; i >= 0; i--) {
+                       if (anArray[i] === layer) {
+                               return false;
+                       }
+               }
+
+               return !!(layer.__parent && layer.__parent._group === this) || this._nonPointGroup.hasLayer(layer);
+       },
+
+       //Zoom down to show the given layer (spiderfying if necessary) then calls the callback
+       zoomToShowLayer: function (layer, callback) {
+
+               var showMarker = function () {
+                       if ((layer._icon || layer.__parent._icon) && !this._inZoomAnimation) {
+                               this._map.off('moveend', showMarker, this);
+                               this.off('animationend', showMarker, this);
+
+                               if (layer._icon) {
+                                       callback();
+                               } else if (layer.__parent._icon) {
+                                       var afterSpiderfy = function () {
+                                               this.off('spiderfied', afterSpiderfy, this);
+                                               callback();
+                                       };
+
+                                       this.on('spiderfied', afterSpiderfy, this);
+                                       layer.__parent.spiderfy();
+                               }
+                       }
+               };
+
+               if (layer._icon && this._map.getBounds().contains(layer.getLatLng())) {
+                       //Layer is visible ond on screen, immediate return
+                       callback();
+               } else if (layer.__parent._zoom < this._map.getZoom()) {
+                       //Layer should be visible at this zoom level. It must not be on screen so just pan over to it
+                       this._map.on('moveend', showMarker, this);
+                       this._map.panTo(layer.getLatLng());
+               } else {
+                       var moveStart = function () {
+                               this._map.off('movestart', moveStart, this);
+                               moveStart = null;
+                       };
+
+                       this._map.on('movestart', moveStart, this);
+                       this._map.on('moveend', showMarker, this);
+                       this.on('animationend', showMarker, this);
+                       layer.__parent.zoomToBounds();
+
+                       if (moveStart) {
+                               //Never started moving, must already be there, probably need clustering however
+                               showMarker.call(this);
+                       }
+               }
+       },
+
+       //Overrides FeatureGroup.onAdd
+       onAdd: function (map) {
+               this._map = map;
+               var i, l, layer;
+
+               if (!isFinite(this._map.getMaxZoom())) {
+                       throw "Map has no maxZoom specified";
+               }
+
+               this._featureGroup.onAdd(map);
+               this._nonPointGroup.onAdd(map);
+
+               if (!this._gridClusters) {
+                       this._generateInitialClusters();
+               }
+
+               for (i = 0, l = this._needsRemoving.length; i < l; i++) {
+                       layer = this._needsRemoving[i];
+                       this._removeLayer(layer, true);
+               }
+               this._needsRemoving = [];
+
+               //Remember the current zoom level and bounds
+               this._zoom = this._map.getZoom();
+               this._currentShownBounds = this._getExpandedVisibleBounds();
+
+               this._map.on('zoomend', this._zoomEnd, this);
+               this._map.on('moveend', this._moveEnd, this);
+
+               if (this._spiderfierOnAdd) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely
+                       this._spiderfierOnAdd();
+               }
+
+               this._bindEvents();
+
+               //Actually add our markers to the map:
+               l = this._needsClustering;
+               this._needsClustering = [];
+               this.addLayers(l);
+       },
+
+       //Overrides FeatureGroup.onRemove
+       onRemove: function (map) {
+               map.off('zoomend', this._zoomEnd, this);
+               map.off('moveend', this._moveEnd, this);
+
+               this._unbindEvents();
+
+               //In case we are in a cluster animation
+               this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '');
+
+               if (this._spiderfierOnRemove) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely
+                       this._spiderfierOnRemove();
+               }
+
+
+
+               //Clean up all the layers we added to the map
+               this._hideCoverage();
+               this._featureGroup.onRemove(map);
+               this._nonPointGroup.onRemove(map);
+
+               this._featureGroup.clearLayers();
+
+               this._map = null;
+       },
+
+       getVisibleParent: function (marker) {
+               var vMarker = marker;
+               while (vMarker && !vMarker._icon) {
+                       vMarker = vMarker.__parent;
+               }
+               return vMarker || null;
+       },
+
+       //Remove the given object from the given array
+       _arraySplice: function (anArray, obj) {
+               for (var i = anArray.length - 1; i >= 0; i--) {
+                       if (anArray[i] === obj) {
+                               anArray.splice(i, 1);
+                               return true;
+                       }
+               }
+       },
+
+       //Internal function for removing a marker from everything.
+       //dontUpdateMap: set to true if you will handle updating the map manually (for bulk functions)
+       _removeLayer: function (marker, removeFromDistanceGrid, dontUpdateMap) {
+               var gridClusters = this._gridClusters,
+                       gridUnclustered = this._gridUnclustered,
+                       fg = this._featureGroup,
+                       map = this._map;
+
+               //Remove the marker from distance clusters it might be in
+               if (removeFromDistanceGrid) {
+                       for (var z = this._maxZoom; z >= 0; z--) {
+                               if (!gridUnclustered[z].removeObject(marker, map.project(marker.getLatLng(), z))) {
+                                       break;
+                               }
+                       }
+               }
+
+               //Work our way up the clusters removing them as we go if required
+               var cluster = marker.__parent,
+                       markers = cluster._markers,
+                       otherMarker;
+
+               //Remove the marker from the immediate parents marker list
+               this._arraySplice(markers, marker);
+
+               while (cluster) {
+                       cluster._childCount--;
+
+                       if (cluster._zoom < 0) {
+                               //Top level, do nothing
+                               break;
+                       } else if (removeFromDistanceGrid && cluster._childCount <= 1) { //Cluster no longer required
+                               //We need to push the other marker up to the parent
+                               otherMarker = cluster._markers[0] === marker ? cluster._markers[1] : cluster._markers[0];
+
+                               //Update distance grid
+                               gridClusters[cluster._zoom].removeObject(cluster, map.project(cluster._cLatLng, cluster._zoom));
+                               gridUnclustered[cluster._zoom].addObject(otherMarker, map.project(otherMarker.getLatLng(), cluster._zoom));
+
+                               //Move otherMarker up to parent
+                               this._arraySplice(cluster.__parent._childClusters, cluster);
+                               cluster.__parent._markers.push(otherMarker);
+                               otherMarker.__parent = cluster.__parent;
+
+                               if (cluster._icon) {
+                                       //Cluster is currently on the map, need to put the marker on the map instead
+                                       fg.removeLayer(cluster);
+                                       if (!dontUpdateMap) {
+                                               fg.addLayer(otherMarker);
+                                       }
+                               }
+                       } else {
+                               cluster._recalculateBounds();
+                               if (!dontUpdateMap || !cluster._icon) {
+                                       cluster._updateIcon();
+                               }
+                       }
+
+                       cluster = cluster.__parent;
+               }
+
+               delete marker.__parent;
+       },
+
+       _isOrIsParent: function (el, oel) {
+               while (oel) {
+                       if (el === oel) {
+                               return true;
+                       }
+                       oel = oel.parentNode;
+               }
+               return false;
+       },
+
+       _propagateEvent: function (e) {
+               if (e.layer instanceof L.MarkerCluster) {
+                       //Prevent multiple clustermouseover/off events if the icon is made up of stacked divs (Doesn't work in ie <= 8, no relatedTarget)
+                       if (e.originalEvent && this._isOrIsParent(e.layer._icon, e.originalEvent.relatedTarget)) {
+                               return;
+                       }
+                       e.type = 'cluster' + e.type;
+               }
+
+               this.fire(e.type, e);
+       },
+
+       //Default functionality
+       _defaultIconCreateFunction: function (cluster) {
+               var childCount = cluster.getChildCount();
+
+               var c = ' marker-cluster-';
+               if (childCount < 10) {
+                       c += 'small';
+               } else if (childCount < 100) {
+                       c += 'medium';
+               } else {
+                       c += 'large';
+               }
+
+               return new L.DivIcon({ html: '<div><span>' + childCount + '</span></div>', className: 'marker-cluster' + c, iconSize: new L.Point(40, 40) });
+       },
+
+       _bindEvents: function () {
+               var map = this._map,
+                   spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom,
+                   showCoverageOnHover = this.options.showCoverageOnHover,
+                   zoomToBoundsOnClick = this.options.zoomToBoundsOnClick;
+
+               //Zoom on cluster click or spiderfy if we are at the lowest level
+               if (spiderfyOnMaxZoom || zoomToBoundsOnClick) {
+                       this.on('clusterclick', this._zoomOrSpiderfy, this);
+               }
+
+               //Show convex hull (boundary) polygon on mouse over
+               if (showCoverageOnHover) {
+                       this.on('clustermouseover', this._showCoverage, this);
+                       this.on('clustermouseout', this._hideCoverage, this);
+                       map.on('zoomend', this._hideCoverage, this);
+               }
+       },
+
+       _zoomOrSpiderfy: function (e) {
+               var map = this._map;
+               if (map.getMaxZoom() === map.getZoom()) {
+                       if (this.options.spiderfyOnMaxZoom) {
+                               e.layer.spiderfy();
+                       }
+               } else if (this.options.zoomToBoundsOnClick) {
+                       e.layer.zoomToBounds();
+               }
+
+               // Focus the map again for keyboard users.
+               if (e.originalEvent && e.originalEvent.keyCode === 13) {
+                       map._container.focus();
+               }
+       },
+
+       _showCoverage: function (e) {
+               var map = this._map;
+               if (this._inZoomAnimation) {
+                       return;
+               }
+               if (this._shownPolygon) {
+                       map.removeLayer(this._shownPolygon);
+               }
+               if (e.layer.getChildCount() > 2 && e.layer !== this._spiderfied) {
+                       this._shownPolygon = new L.Polygon(e.layer.getConvexHull(), this.options.polygonOptions);
+                       map.addLayer(this._shownPolygon);
+               }
+       },
+
+       _hideCoverage: function () {
+               if (this._shownPolygon) {
+                       this._map.removeLayer(this._shownPolygon);
+                       this._shownPolygon = null;
+               }
+       },
+
+       _unbindEvents: function () {
+               var spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom,
+                       showCoverageOnHover = this.options.showCoverageOnHover,
+                       zoomToBoundsOnClick = this.options.zoomToBoundsOnClick,
+                       map = this._map;
+
+               if (spiderfyOnMaxZoom || zoomToBoundsOnClick) {
+                       this.off('clusterclick', this._zoomOrSpiderfy, this);
+               }
+               if (showCoverageOnHover) {
+                       this.off('clustermouseover', this._showCoverage, this);
+                       this.off('clustermouseout', this._hideCoverage, this);
+                       map.off('zoomend', this._hideCoverage, this);
+               }
+       },
+
+       _zoomEnd: function () {
+               if (!this._map) { //May have been removed from the map by a zoomEnd handler
+                       return;
+               }
+               this._mergeSplitClusters();
+
+               this._zoom = this._map._zoom;
+               this._currentShownBounds = this._getExpandedVisibleBounds();
+       },
+
+       _moveEnd: function () {
+               if (this._inZoomAnimation) {
+                       return;
+               }
+
+               var newBounds = this._getExpandedVisibleBounds();
+
+               this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, this._zoom, newBounds);
+               this._topClusterLevel._recursivelyAddChildrenToMap(null, this._map._zoom, newBounds);
+
+               this._currentShownBounds = newBounds;
+               return;
+       },
+
+       _generateInitialClusters: function () {
+               var maxZoom = this._map.getMaxZoom(),
+                       radius = this.options.maxClusterRadius,
+                       radiusFn = radius;
+       
+               //If we just set maxClusterRadius to a single number, we need to create
+               //a simple function to return that number. Otherwise, we just have to
+               //use the function we've passed in.
+               if (typeof radius !== "function") {
+                       radiusFn = function () { return radius; };
+               }
+
+               if (this.options.disableClusteringAtZoom) {
+                       maxZoom = this.options.disableClusteringAtZoom - 1;
+               }
+               this._maxZoom = maxZoom;
+               this._gridClusters = {};
+               this._gridUnclustered = {};
+       
+               //Set up DistanceGrids for each zoom
+               for (var zoom = maxZoom; zoom >= 0; zoom--) {
+                       this._gridClusters[zoom] = new L.DistanceGrid(radiusFn(zoom));
+                       this._gridUnclustered[zoom] = new L.DistanceGrid(radiusFn(zoom));
+               }
+
+               this._topClusterLevel = new L.MarkerCluster(this, -1);
+       },
+
+       //Zoom: Zoom to start adding at (Pass this._maxZoom to start at the bottom)
+       _addLayer: function (layer, zoom) {
+               var gridClusters = this._gridClusters,
+                   gridUnclustered = this._gridUnclustered,
+                   markerPoint, z;
+
+               if (this.options.singleMarkerMode) {
+                       layer.options.icon = this.options.iconCreateFunction({
+                               getChildCount: function () {
+                                       return 1;
+                               },
+                               getAllChildMarkers: function () {
+                                       return [layer];
+                               }
+                       });
+               }
+
+               //Find the lowest zoom level to slot this one in
+               for (; zoom >= 0; zoom--) {
+                       markerPoint = this._map.project(layer.getLatLng(), zoom); // calculate pixel position
+
+                       //Try find a cluster close by
+                       var closest = gridClusters[zoom].getNearObject(markerPoint);
+                       if (closest) {
+                               closest._addChild(layer);
+                               layer.__parent = closest;
+                               return;
+                       }
+
+                       //Try find a marker close by to form a new cluster with
+                       closest = gridUnclustered[zoom].getNearObject(markerPoint);
+                       if (closest) {
+                               var parent = closest.__parent;
+                               if (parent) {
+                                       this._removeLayer(closest, false);
+                               }
+
+                               //Create new cluster with these 2 in it
+
+                               var newCluster = new L.MarkerCluster(this, zoom, closest, layer);
+                               gridClusters[zoom].addObject(newCluster, this._map.project(newCluster._cLatLng, zoom));
+                               closest.__parent = newCluster;
+                               layer.__parent = newCluster;
+
+                               //First create any new intermediate parent clusters that don't exist
+                               var lastParent = newCluster;
+                               for (z = zoom - 1; z > parent._zoom; z--) {
+                                       lastParent = new L.MarkerCluster(this, z, lastParent);
+                                       gridClusters[z].addObject(lastParent, this._map.project(closest.getLatLng(), z));
+                               }
+                               parent._addChild(lastParent);
+
+                               //Remove closest from this zoom level and any above that it is in, replace with newCluster
+                               for (z = zoom; z >= 0; z--) {
+                                       if (!gridUnclustered[z].removeObject(closest, this._map.project(closest.getLatLng(), z))) {
+                                               break;
+                                       }
+                               }
+
+                               return;
+                       }
+
+                       //Didn't manage to cluster in at this zoom, record us as a marker here and continue upwards
+                       gridUnclustered[zoom].addObject(layer, markerPoint);
+               }
+
+               //Didn't get in anything, add us to the top
+               this._topClusterLevel._addChild(layer);
+               layer.__parent = this._topClusterLevel;
+               return;
+       },
+
+       //Enqueue code to fire after the marker expand/contract has happened
+       _enqueue: function (fn) {
+               this._queue.push(fn);
+               if (!this._queueTimeout) {
+                       this._queueTimeout = setTimeout(L.bind(this._processQueue, this), 300);
+               }
+       },
+       _processQueue: function () {
+               for (var i = 0; i < this._queue.length; i++) {
+                       this._queue[i].call(this);
+               }
+               this._queue.length = 0;
+               clearTimeout(this._queueTimeout);
+               this._queueTimeout = null;
+       },
+
+       //Merge and split any existing clusters that are too big or small
+       _mergeSplitClusters: function () {
+
+               //Incase we are starting to split before the animation finished
+               this._processQueue();
+
+               if (this._zoom < this._map._zoom && this._currentShownBounds.intersects(this._getExpandedVisibleBounds())) { //Zoom in, split
+                       this._animationStart();
+                       //Remove clusters now off screen
+                       this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, this._zoom, this._getExpandedVisibleBounds());
+
+                       this._animationZoomIn(this._zoom, this._map._zoom);
+
+               } else if (this._zoom > this._map._zoom) { //Zoom out, merge
+                       this._animationStart();
+
+                       this._animationZoomOut(this._zoom, this._map._zoom);
+               } else {
+                       this._moveEnd();
+               }
+       },
+
+       //Gets the maps visible bounds expanded in each direction by the size of the screen (so the user cannot see an area we do not cover in one pan)
+       _getExpandedVisibleBounds: function () {
+               if (!this.options.removeOutsideVisibleBounds) {
+                       return this.getBounds();
+               }
+
+               var map = this._map,
+                       bounds = map.getBounds(),
+                       sw = bounds._southWest,
+                       ne = bounds._northEast,
+                       latDiff = L.Browser.mobile ? 0 : Math.abs(sw.lat - ne.lat),
+                       lngDiff = L.Browser.mobile ? 0 : Math.abs(sw.lng - ne.lng);
+
+               return new L.LatLngBounds(
+                       new L.LatLng(sw.lat - latDiff, sw.lng - lngDiff, true),
+                       new L.LatLng(ne.lat + latDiff, ne.lng + lngDiff, true));
+       },
+
+       //Shared animation code
+       _animationAddLayerNonAnimated: function (layer, newCluster) {
+               if (newCluster === layer) {
+                       this._featureGroup.addLayer(layer);
+               } else if (newCluster._childCount === 2) {
+                       newCluster._addToMap();
+
+                       var markers = newCluster.getAllChildMarkers();
+                       this._featureGroup.removeLayer(markers[0]);
+                       this._featureGroup.removeLayer(markers[1]);
+               } else {
+                       newCluster._updateIcon();
+               }
+       }
+});
+
+L.MarkerClusterGroup.include(!L.DomUtil.TRANSITION ? {
+
+       //Non Animated versions of everything
+       _animationStart: function () {
+               //Do nothing...
+       },
+       _animationZoomIn: function (previousZoomLevel, newZoomLevel) {
+               this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, previousZoomLevel);
+               this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
+
+               //We didn't actually animate, but we use this event to mean "clustering animations have finished"
+               this.fire('animationend');
+       },
+       _animationZoomOut: function (previousZoomLevel, newZoomLevel) {
+               this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, previousZoomLevel);
+               this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
+
+               //We didn't actually animate, but we use this event to mean "clustering animations have finished"
+               this.fire('animationend');
+       },
+       _animationAddLayer: function (layer, newCluster) {
+               this._animationAddLayerNonAnimated(layer, newCluster);
+       }
+} : {
+
+       //Animated versions here
+       _animationStart: function () {
+               this._map._mapPane.className += ' leaflet-cluster-anim';
+               this._inZoomAnimation++;
+       },
+       _animationEnd: function () {
+               if (this._map) {
+                       this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '');
+               }
+               this._inZoomAnimation--;
+               this.fire('animationend');
+       },
+       _animationZoomIn: function (previousZoomLevel, newZoomLevel) {
+               var bounds = this._getExpandedVisibleBounds(),
+                   fg = this._featureGroup,
+                   i;
+
+               //Add all children of current clusters to map and remove those clusters from map
+               this._topClusterLevel._recursively(bounds, previousZoomLevel, 0, function (c) {
+                       var startPos = c._latlng,
+                               markers = c._markers,
+                               m;
+
+                       if (!bounds.contains(startPos)) {
+                               startPos = null;
+                       }
+
+                       if (c._isSingleParent() && previousZoomLevel + 1 === newZoomLevel) { //Immediately add the new child and remove us
+                               fg.removeLayer(c);
+                               c._recursivelyAddChildrenToMap(null, newZoomLevel, bounds);
+                       } else {
+                               //Fade out old cluster
+                               c.setOpacity(0);
+                               c._recursivelyAddChildrenToMap(startPos, newZoomLevel, bounds);
+                       }
+
+                       //Remove all markers that aren't visible any more
+                       //TODO: Do we actually need to do this on the higher levels too?
+                       for (i = markers.length - 1; i >= 0; i--) {
+                               m = markers[i];
+                               if (!bounds.contains(m._latlng)) {
+                                       fg.removeLayer(m);
+                               }
+                       }
+
+               });
+
+               this._forceLayout();
+
+               //Update opacities
+               this._topClusterLevel._recursivelyBecomeVisible(bounds, newZoomLevel);
+               //TODO Maybe? Update markers in _recursivelyBecomeVisible
+               fg.eachLayer(function (n) {
+                       if (!(n instanceof L.MarkerCluster) && n._icon) {
+                               n.setOpacity(1);
+                       }
+               });
+
+               //update the positions of the just added clusters/markers
+               this._topClusterLevel._recursively(bounds, previousZoomLevel, newZoomLevel, function (c) {
+                       c._recursivelyRestoreChildPositions(newZoomLevel);
+               });
+
+               //Remove the old clusters and close the zoom animation
+               this._enqueue(function () {
+                       //update the positions of the just added clusters/markers
+                       this._topClusterLevel._recursively(bounds, previousZoomLevel, 0, function (c) {
+                               fg.removeLayer(c);
+                               c.setOpacity(1);
+                       });
+
+                       this._animationEnd();
+               });
+       },
+
+       _animationZoomOut: function (previousZoomLevel, newZoomLevel) {
+               this._animationZoomOutSingle(this._topClusterLevel, previousZoomLevel - 1, newZoomLevel);
+
+               //Need to add markers for those that weren't on the map before but are now
+               this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
+               //Remove markers that were on the map before but won't be now
+               this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, previousZoomLevel, this._getExpandedVisibleBounds());
+       },
+       _animationZoomOutSingle: function (cluster, previousZoomLevel, newZoomLevel) {
+               var bounds = this._getExpandedVisibleBounds();
+
+               //Animate all of the markers in the clusters to move to their cluster center point
+               cluster._recursivelyAnimateChildrenInAndAddSelfToMap(bounds, previousZoomLevel + 1, newZoomLevel);
+
+               var me = this;
+
+               //Update the opacity (If we immediately set it they won't animate)
+               this._forceLayout();
+               cluster._recursivelyBecomeVisible(bounds, newZoomLevel);
+
+               //TODO: Maybe use the transition timing stuff to make this more reliable
+               //When the animations are done, tidy up
+               this._enqueue(function () {
+
+                       //This cluster stopped being a cluster before the timeout fired
+                       if (cluster._childCount === 1) {
+                               var m = cluster._markers[0];
+                               //If we were in a cluster animation at the time then the opacity and position of our child could be wrong now, so fix it
+                               m.setLatLng(m.getLatLng());
+                               if (m.setOpacity) {
+                                       m.setOpacity(1);
+                               }
+                       } else {
+                               cluster._recursively(bounds, newZoomLevel, 0, function (c) {
+                                       c._recursivelyRemoveChildrenFromMap(bounds, previousZoomLevel + 1);
+                               });
+                       }
+                       me._animationEnd();
+               });
+       },
+       _animationAddLayer: function (layer, newCluster) {
+               var me = this,
+                       fg = this._featureGroup;
+
+               fg.addLayer(layer);
+               if (newCluster !== layer) {
+                       if (newCluster._childCount > 2) { //Was already a cluster
+
+                               newCluster._updateIcon();
+                               this._forceLayout();
+                               this._animationStart();
+
+                               layer._setPos(this._map.latLngToLayerPoint(newCluster.getLatLng()));
+                               layer.setOpacity(0);
+
+                               this._enqueue(function () {
+                                       fg.removeLayer(layer);
+                                       layer.setOpacity(1);
+
+                                       me._animationEnd();
+                               });
+
+                       } else { //Just became a cluster
+                               this._forceLayout();
+
+                               me._animationStart();
+                               me._animationZoomOutSingle(newCluster, this._map.getMaxZoom(), this._map.getZoom());
+                       }
+               }
+       },
+
+       //Force a browser layout of stuff in the map
+       // Should apply the current opacity and location to all elements so we can update them again for an animation
+       _forceLayout: function () {
+               //In my testing this works, infact offsetWidth of any element seems to work.
+               //Could loop all this._layers and do this for each _icon if it stops working
+
+               L.Util.falseFn(document.body.offsetWidth);
+       }
+});
+
+L.markerClusterGroup = function (options) {
+       return new L.MarkerClusterGroup(options);
+};
+
+
+L.MarkerCluster = L.Marker.extend({
+       initialize: function (group, zoom, a, b) {
+
+               L.Marker.prototype.initialize.call(this, a ? (a._cLatLng || a.getLatLng()) : new L.LatLng(0, 0), { icon: this });
+
+
+               this._group = group;
+               this._zoom = zoom;
+
+               this._markers = [];
+               this._childClusters = [];
+               this._childCount = 0;
+               this._iconNeedsUpdate = true;
+
+               this._bounds = new L.LatLngBounds();
+
+               if (a) {
+                       this._addChild(a);
+               }
+               if (b) {
+                       this._addChild(b);
+               }
+       },
+
+       //Recursively retrieve all child markers of this cluster
+       getAllChildMarkers: function (storageArray) {
+               storageArray = storageArray || [];
+
+               for (var i = this._childClusters.length - 1; i >= 0; i--) {
+                       this._childClusters[i].getAllChildMarkers(storageArray);
+               }
+
+               for (var j = this._markers.length - 1; j >= 0; j--) {
+                       storageArray.push(this._markers[j]);
+               }
+
+               return storageArray;
+       },
+
+       //Returns the count of how many child markers we have
+       getChildCount: function () {
+               return this._childCount;
+       },
+
+       //Zoom to the minimum of showing all of the child markers, or the extents of this cluster
+       zoomToBounds: function () {
+               var childClusters = this._childClusters.slice(),
+                       map = this._group._map,
+                       boundsZoom = map.getBoundsZoom(this._bounds),
+                       zoom = this._zoom + 1,
+                       mapZoom = map.getZoom(),
+                       i;
+
+               //calculate how far we need to zoom down to see all of the markers
+               while (childClusters.length > 0 && boundsZoom > zoom) {
+                       zoom++;
+                       var newClusters = [];
+                       for (i = 0; i < childClusters.length; i++) {
+                               newClusters = newClusters.concat(childClusters[i]._childClusters);
+                       }
+                       childClusters = newClusters;
+               }
+
+               if (boundsZoom > zoom) {
+                       this._group._map.setView(this._latlng, zoom);
+               } else if (boundsZoom <= mapZoom) { //If fitBounds wouldn't zoom us down, zoom us down instead
+                       this._group._map.setView(this._latlng, mapZoom + 1);
+               } else {
+                       this._group._map.fitBounds(this._bounds);
+               }
+       },
+
+       getBounds: function () {
+               var bounds = new L.LatLngBounds();
+               bounds.extend(this._bounds);
+               return bounds;
+       },
+
+       _updateIcon: function () {
+               this._iconNeedsUpdate = true;
+               if (this._icon) {
+                       this.setIcon(this);
+               }
+       },
+
+       //Cludge for Icon, we pretend to be an icon for performance
+       createIcon: function () {
+               if (this._iconNeedsUpdate) {
+                       this._iconObj = this._group.options.iconCreateFunction(this);
+                       this._iconNeedsUpdate = false;
+               }
+               return this._iconObj.createIcon();
+       },
+       createShadow: function () {
+               return this._iconObj.createShadow();
+       },
+
+
+       _addChild: function (new1, isNotificationFromChild) {
+
+               this._iconNeedsUpdate = true;
+               this._expandBounds(new1);
+
+               if (new1 instanceof L.MarkerCluster) {
+                       if (!isNotificationFromChild) {
+                               this._childClusters.push(new1);
+                               new1.__parent = this;
+                       }
+                       this._childCount += new1._childCount;
+               } else {
+                       if (!isNotificationFromChild) {
+                               this._markers.push(new1);
+                       }
+                       this._childCount++;
+               }
+
+               if (this.__parent) {
+                       this.__parent._addChild(new1, true);
+               }
+       },
+
+       //Expand our bounds and tell our parent to
+       _expandBounds: function (marker) {
+               var addedCount,
+                   addedLatLng = marker._wLatLng || marker._latlng;
+
+               if (marker instanceof L.MarkerCluster) {
+                       this._bounds.extend(marker._bounds);
+                       addedCount = marker._childCount;
+               } else {
+                       this._bounds.extend(addedLatLng);
+                       addedCount = 1;
+               }
+
+               if (!this._cLatLng) {
+                       // when clustering, take position of the first point as the cluster center
+                       this._cLatLng = marker._cLatLng || addedLatLng;
+               }
+
+               // when showing clusters, take weighted average of all points as cluster center
+               var totalCount = this._childCount + addedCount;
+
+               //Calculate weighted latlng for display
+               if (!this._wLatLng) {
+                       this._latlng = this._wLatLng = new L.LatLng(addedLatLng.lat, addedLatLng.lng);
+               } else {
+                       this._wLatLng.lat = (addedLatLng.lat * addedCount + this._wLatLng.lat * this._childCount) / totalCount;
+                       this._wLatLng.lng = (addedLatLng.lng * addedCount + this._wLatLng.lng * this._childCount) / totalCount;
+               }
+       },
+
+       //Set our markers position as given and add it to the map
+       _addToMap: function (startPos) {
+               if (startPos) {
+                       this._backupLatlng = this._latlng;
+                       this.setLatLng(startPos);
+               }
+               this._group._featureGroup.addLayer(this);
+       },
+
+       _recursivelyAnimateChildrenIn: function (bounds, center, maxZoom) {
+               this._recursively(bounds, 0, maxZoom - 1,
+                       function (c) {
+                               var markers = c._markers,
+                                       i, m;
+                               for (i = markers.length - 1; i >= 0; i--) {
+                                       m = markers[i];
+
+                                       //Only do it if the icon is still on the map
+                                       if (m._icon) {
+                                               m._setPos(center);
+                                               m.setOpacity(0);
+                                       }
+                               }
+                       },
+                       function (c) {
+                               var childClusters = c._childClusters,
+                                       j, cm;
+                               for (j = childClusters.length - 1; j >= 0; j--) {
+                                       cm = childClusters[j];
+                                       if (cm._icon) {
+                                               cm._setPos(center);
+                                               cm.setOpacity(0);
+                                       }
+                               }
+                       }
+               );
+       },
+
+       _recursivelyAnimateChildrenInAndAddSelfToMap: function (bounds, previousZoomLevel, newZoomLevel) {
+               this._recursively(bounds, newZoomLevel, 0,
+                       function (c) {
+                               c._recursivelyAnimateChildrenIn(bounds, c._group._map.latLngToLayerPoint(c.getLatLng()).round(), previousZoomLevel);
+
+                               //TODO: depthToAnimateIn affects _isSingleParent, if there is a multizoom we may/may not be.
+                               //As a hack we only do a animation free zoom on a single level zoom, if someone does multiple levels then we always animate
+                               if (c._isSingleParent() && previousZoomLevel - 1 === newZoomLevel) {
+                                       c.setOpacity(1);
+                                       c._recursivelyRemoveChildrenFromMap(bounds, previousZoomLevel); //Immediately remove our children as we are replacing them. TODO previousBounds not bounds
+                               } else {
+                                       c.setOpacity(0);
+                               }
+
+                               c._addToMap();
+                       }
+               );
+       },
+
+       _recursivelyBecomeVisible: function (bounds, zoomLevel) {
+               this._recursively(bounds, 0, zoomLevel, null, function (c) {
+                       c.setOpacity(1);
+               });
+       },
+
+       _recursivelyAddChildrenToMap: function (startPos, zoomLevel, bounds) {
+               this._recursively(bounds, -1, zoomLevel,
+                       function (c) {
+                               if (zoomLevel === c._zoom) {
+                                       return;
+                               }
+
+                               //Add our child markers at startPos (so they can be animated out)
+                               for (var i = c._markers.length - 1; i >= 0; i--) {
+                                       var nm = c._markers[i];
+
+                                       if (!bounds.contains(nm._latlng)) {
+                                               continue;
+                                       }
+
+                                       if (startPos) {
+                                               nm._backupLatlng = nm.getLatLng();
+
+                                               nm.setLatLng(startPos);
+                                               if (nm.setOpacity) {
+                                                       nm.setOpacity(0);
+                                               }
+                                       }
+
+                                       c._group._featureGroup.addLayer(nm);
+                               }
+                       },
+                       function (c) {
+                               c._addToMap(startPos);
+                       }
+               );
+       },
+
+       _recursivelyRestoreChildPositions: function (zoomLevel) {
+               //Fix positions of child markers
+               for (var i = this._markers.length - 1; i >= 0; i--) {
+                       var nm = this._markers[i];
+                       if (nm._backupLatlng) {
+                               nm.setLatLng(nm._backupLatlng);
+                               delete nm._backupLatlng;
+                       }
+               }
+
+               if (zoomLevel - 1 === this._zoom) {
+                       //Reposition child clusters
+                       for (var j = this._childClusters.length - 1; j >= 0; j--) {
+                               this._childClusters[j]._restorePosition();
+                       }
+               } else {
+                       for (var k = this._childClusters.length - 1; k >= 0; k--) {
+                               this._childClusters[k]._recursivelyRestoreChildPositions(zoomLevel);
+                       }
+               }
+       },
+
+       _restorePosition: function () {
+               if (this._backupLatlng) {
+                       this.setLatLng(this._backupLatlng);
+                       delete this._backupLatlng;
+               }
+       },
+
+       //exceptBounds: If set, don't remove any markers/clusters in it
+       _recursivelyRemoveChildrenFromMap: function (previousBounds, zoomLevel, exceptBounds) {
+               var m, i;
+               this._recursively(previousBounds, -1, zoomLevel - 1,
+                       function (c) {
+                               //Remove markers at every level
+                               for (i = c._markers.length - 1; i >= 0; i--) {
+                                       m = c._markers[i];
+                                       if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
+                                               c._group._featureGroup.removeLayer(m);
+                                               if (m.setOpacity) {
+                                                       m.setOpacity(1);
+                                               }
+                                       }
+                               }
+                       },
+                       function (c) {
+                               //Remove child clusters at just the bottom level
+                               for (i = c._childClusters.length - 1; i >= 0; i--) {
+                                       m = c._childClusters[i];
+                                       if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
+                                               c._group._featureGroup.removeLayer(m);
+                                               if (m.setOpacity) {
+                                                       m.setOpacity(1);
+                                               }
+                                       }
+                               }
+                       }
+               );
+       },
+
+       //Run the given functions recursively to this and child clusters
+       // boundsToApplyTo: a L.LatLngBounds representing the bounds of what clusters to recurse in to
+       // zoomLevelToStart: zoom level to start running functions (inclusive)
+       // zoomLevelToStop: zoom level to stop running functions (inclusive)
+       // runAtEveryLevel: function that takes an L.MarkerCluster as an argument that should be applied on every level
+       // runAtBottomLevel: function that takes an L.MarkerCluster as an argument that should be applied at only the bottom level
+       _recursively: function (boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel) {
+               var childClusters = this._childClusters,
+                   zoom = this._zoom,
+                       i, c;
+
+               if (zoomLevelToStart > zoom) { //Still going down to required depth, just recurse to child clusters
+                       for (i = childClusters.length - 1; i >= 0; i--) {
+                               c = childClusters[i];
+                               if (boundsToApplyTo.intersects(c._bounds)) {
+                                       c._recursively(boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel);
+                               }
+                       }
+               } else { //In required depth
+
+                       if (runAtEveryLevel) {
+                               runAtEveryLevel(this);
+                       }
+                       if (runAtBottomLevel && this._zoom === zoomLevelToStop) {
+                               runAtBottomLevel(this);
+                       }
+
+                       //TODO: This loop is almost the same as above
+                       if (zoomLevelToStop > zoom) {
+                               for (i = childClusters.length - 1; i >= 0; i--) {
+                                       c = childClusters[i];
+                                       if (boundsToApplyTo.intersects(c._bounds)) {
+                                               c._recursively(boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel);
+                                       }
+                               }
+                       }
+               }
+       },
+
+       _recalculateBounds: function () {
+               var markers = this._markers,
+                       childClusters = this._childClusters,
+                       i;
+
+               this._bounds = new L.LatLngBounds();
+               delete this._wLatLng;
+
+               for (i = markers.length - 1; i >= 0; i--) {
+                       this._expandBounds(markers[i]);
+               }
+               for (i = childClusters.length - 1; i >= 0; i--) {
+                       this._expandBounds(childClusters[i]);
+               }
+       },
+
+
+       //Returns true if we are the parent of only one cluster and that cluster is the same as us
+       _isSingleParent: function () {
+               //Don't need to check this._markers as the rest won't work if there are any
+               return this._childClusters.length > 0 && this._childClusters[0]._childCount === this._childCount;
+       }
+});
+
+
+
+L.DistanceGrid = function (cellSize) {
+       this._cellSize = cellSize;
+       this._sqCellSize = cellSize * cellSize;
+       this._grid = {};
+       this._objectPoint = { };
+};
+
+L.DistanceGrid.prototype = {
+
+       addObject: function (obj, point) {
+               var x = this._getCoord(point.x),
+                   y = this._getCoord(point.y),
+                   grid = this._grid,
+                   row = grid[y] = grid[y] || {},
+                   cell = row[x] = row[x] || [],
+                   stamp = L.Util.stamp(obj);
+
+               this._objectPoint[stamp] = point;
+
+               cell.push(obj);
+       },
+
+       updateObject: function (obj, point) {
+               this.removeObject(obj);
+               this.addObject(obj, point);
+       },
+
+       //Returns true if the object was found
+       removeObject: function (obj, point) {
+               var x = this._getCoord(point.x),
+                   y = this._getCoord(point.y),
+                   grid = this._grid,
+                   row = grid[y] = grid[y] || {},
+                   cell = row[x] = row[x] || [],
+                   i, len;
+
+               delete this._objectPoint[L.Util.stamp(obj)];
+
+               for (i = 0, len = cell.length; i < len; i++) {
+                       if (cell[i] === obj) {
+
+                               cell.splice(i, 1);
+
+                               if (len === 1) {
+                                       delete row[x];
+                               }
+
+                               return true;
+                       }
+               }
+
+       },
+
+       eachObject: function (fn, context) {
+               var i, j, k, len, row, cell, removed,
+                   grid = this._grid;
+
+               for (i in grid) {
+                       row = grid[i];
+
+                       for (j in row) {
+                               cell = row[j];
+
+                               for (k = 0, len = cell.length; k < len; k++) {
+                                       removed = fn.call(context, cell[k]);
+                                       if (removed) {
+                                               k--;
+                                               len--;
+                                       }
+                               }
+                       }
+               }
+       },
+
+       getNearObject: function (point) {
+               var x = this._getCoord(point.x),
+                   y = this._getCoord(point.y),
+                   i, j, k, row, cell, len, obj, dist,
+                   objectPoint = this._objectPoint,
+                   closestDistSq = this._sqCellSize,
+                   closest = null;
+
+               for (i = y - 1; i <= y + 1; i++) {
+                       row = this._grid[i];
+                       if (row) {
+
+                               for (j = x - 1; j <= x + 1; j++) {
+                                       cell = row[j];
+                                       if (cell) {
+
+                                               for (k = 0, len = cell.length; k < len; k++) {
+                                                       obj = cell[k];
+                                                       dist = this._sqDist(objectPoint[L.Util.stamp(obj)], point);
+                                                       if (dist < closestDistSq) {
+                                                               closestDistSq = dist;
+                                                               closest = obj;
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+               }
+               return closest;
+       },
+
+       _getCoord: function (x) {
+               return Math.floor(x / this._cellSize);
+       },
+
+       _sqDist: function (p, p2) {
+               var dx = p2.x - p.x,
+                   dy = p2.y - p.y;
+               return dx * dx + dy * dy;
+       }
+};
+
+
+/* Copyright (c) 2012 the authors listed at the following URL, and/or
+the authors of referenced articles or incorporated external code:
+http://en.literateprograms.org/Quickhull_(Javascript)?action=history&offset=20120410175256
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Retrieved from: http://en.literateprograms.org/Quickhull_(Javascript)?oldid=18434
+*/
+
+(function () {
+       L.QuickHull = {
+
+               /*
+                * @param {Object} cpt a point to be measured from the baseline
+                * @param {Array} bl the baseline, as represented by a two-element
+                *   array of latlng objects.
+                * @returns {Number} an approximate distance measure
+                */
+               getDistant: function (cpt, bl) {
+                       var vY = bl[1].lat - bl[0].lat,
+                               vX = bl[0].lng - bl[1].lng;
+                       return (vX * (cpt.lat - bl[0].lat) + vY * (cpt.lng - bl[0].lng));
+               },
+
+               /*
+                * @param {Array} baseLine a two-element array of latlng objects
+                *   representing the baseline to project from
+                * @param {Array} latLngs an array of latlng objects
+                * @returns {Object} the maximum point and all new points to stay
+                *   in consideration for the hull.
+                */
+               findMostDistantPointFromBaseLine: function (baseLine, latLngs) {
+                       var maxD = 0,
+                               maxPt = null,
+                               newPoints = [],
+                               i, pt, d;
+
+                       for (i = latLngs.length - 1; i >= 0; i--) {
+                               pt = latLngs[i];
+                               d = this.getDistant(pt, baseLine);
+
+                               if (d > 0) {
+                                       newPoints.push(pt);
+                               } else {
+                                       continue;
+                               }
+
+                               if (d > maxD) {
+                                       maxD = d;
+                                       maxPt = pt;
+                               }
+                       }
+
+                       return { maxPoint: maxPt, newPoints: newPoints };
+               },
+
+
+               /*
+                * Given a baseline, compute the convex hull of latLngs as an array
+                * of latLngs.
+                *
+                * @param {Array} latLngs
+                * @returns {Array}
+                */
+               buildConvexHull: function (baseLine, latLngs) {
+                       var convexHullBaseLines = [],
+                               t = this.findMostDistantPointFromBaseLine(baseLine, latLngs);
+
+                       if (t.maxPoint) { // if there is still a point "outside" the base line
+                               convexHullBaseLines =
+                                       convexHullBaseLines.concat(
+                                               this.buildConvexHull([baseLine[0], t.maxPoint], t.newPoints)
+                                       );
+                               convexHullBaseLines =
+                                       convexHullBaseLines.concat(
+                                               this.buildConvexHull([t.maxPoint, baseLine[1]], t.newPoints)
+                                       );
+                               return convexHullBaseLines;
+                       } else {  // if there is no more point "outside" the base line, the current base line is part of the convex hull
+                               return [baseLine[0]];
+                       }
+               },
+
+               /*
+                * Given an array of latlngs, compute a convex hull as an array
+                * of latlngs
+                *
+                * @param {Array} latLngs
+                * @returns {Array}
+                */
+               getConvexHull: function (latLngs) {
+                       // find first baseline
+                       var maxLat = false, minLat = false,
+                               maxPt = null, minPt = null,
+                               i;
+
+                       for (i = latLngs.length - 1; i >= 0; i--) {
+                               var pt = latLngs[i];
+                               if (maxLat === false || pt.lat > maxLat) {
+                                       maxPt = pt;
+                                       maxLat = pt.lat;
+                               }
+                               if (minLat === false || pt.lat < minLat) {
+                                       minPt = pt;
+                                       minLat = pt.lat;
+                               }
+                       }
+                       var ch = [].concat(this.buildConvexHull([minPt, maxPt], latLngs),
+                                                               this.buildConvexHull([maxPt, minPt], latLngs));
+                       return ch;
+               }
+       };
+}());
+
+L.MarkerCluster.include({
+       getConvexHull: function () {
+               var childMarkers = this.getAllChildMarkers(),
+                       points = [],
+                       p, i;
+
+               for (i = childMarkers.length - 1; i >= 0; i--) {
+                       p = childMarkers[i].getLatLng();
+                       points.push(p);
+               }
+
+               return L.QuickHull.getConvexHull(points);
+       }
+});
+
+
+//This code is 100% based on https://github.com/jawj/OverlappingMarkerSpiderfier-Leaflet
+//Huge thanks to jawj for implementing it first to make my job easy :-)
+
+L.MarkerCluster.include({
+
+       _2PI: Math.PI * 2,
+       _circleFootSeparation: 25, //related to circumference of circle
+       _circleStartAngle: Math.PI / 6,
+
+       _spiralFootSeparation:  28, //related to size of spiral (experiment!)
+       _spiralLengthStart: 11,
+       _spiralLengthFactor: 5,
+
+       _circleSpiralSwitchover: 9, //show spiral instead of circle from this marker count upwards.
+                                                               // 0 -> always spiral; Infinity -> always circle
+
+       spiderfy: function () {
+               if (this._group._spiderfied === this || this._group._inZoomAnimation) {
+                       return;
+               }
+
+               var childMarkers = this.getAllChildMarkers(),
+                       group = this._group,
+                       map = group._map,
+                       center = map.latLngToLayerPoint(this._latlng),
+                       positions;
+
+               this._group._unspiderfy();
+               this._group._spiderfied = this;
+
+               //TODO Maybe: childMarkers order by distance to center
+
+               if (childMarkers.length >= this._circleSpiralSwitchover) {
+                       positions = this._generatePointsSpiral(childMarkers.length, center);
+               } else {
+                       center.y += 10; //Otherwise circles look wrong
+                       positions = this._generatePointsCircle(childMarkers.length, center);
+               }
+
+               this._animationSpiderfy(childMarkers, positions);
+       },
+
+       unspiderfy: function (zoomDetails) {
+               /// <param Name="zoomDetails">Argument from zoomanim if being called in a zoom animation or null otherwise</param>
+               if (this._group._inZoomAnimation) {
+                       return;
+               }
+               this._animationUnspiderfy(zoomDetails);
+
+               this._group._spiderfied = null;
+       },
+
+       _generatePointsCircle: function (count, centerPt) {
+               var circumference = this._group.options.spiderfyDistanceMultiplier * this._circleFootSeparation * (2 + count),
+                       legLength = circumference / this._2PI,  //radius from circumference
+                       angleStep = this._2PI / count,
+                       res = [],
+                       i, angle;
+
+               res.length = count;
+
+               for (i = count - 1; i >= 0; i--) {
+                       angle = this._circleStartAngle + i * angleStep;
+                       res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
+               }
+
+               return res;
+       },
+
+       _generatePointsSpiral: function (count, centerPt) {
+               var legLength = this._group.options.spiderfyDistanceMultiplier * this._spiralLengthStart,
+                       separation = this._group.options.spiderfyDistanceMultiplier * this._spiralFootSeparation,
+                       lengthFactor = this._group.options.spiderfyDistanceMultiplier * this._spiralLengthFactor,
+                       angle = 0,
+                       res = [],
+                       i;
+
+               res.length = count;
+
+               for (i = count - 1; i >= 0; i--) {
+                       angle += separation / legLength + i * 0.0005;
+                       res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
+                       legLength += this._2PI * lengthFactor / angle;
+               }
+               return res;
+       },
+
+       _noanimationUnspiderfy: function () {
+               var group = this._group,
+                       map = group._map,
+                       fg = group._featureGroup,
+                       childMarkers = this.getAllChildMarkers(),
+                       m, i;
+
+               this.setOpacity(1);
+               for (i = childMarkers.length - 1; i >= 0; i--) {
+                       m = childMarkers[i];
+
+                       fg.removeLayer(m);
+
+                       if (m._preSpiderfyLatlng) {
+                               m.setLatLng(m._preSpiderfyLatlng);
+                               delete m._preSpiderfyLatlng;
+                       }
+                       if (m.setZIndexOffset) {
+                               m.setZIndexOffset(0);
+                       }
+
+                       if (m._spiderLeg) {
+                               map.removeLayer(m._spiderLeg);
+                               delete m._spiderLeg;
+                       }
+               }
+
+               group._spiderfied = null;
+       }
+});
+
+L.MarkerCluster.include(!L.DomUtil.TRANSITION ? {
+       //Non Animated versions of everything
+       _animationSpiderfy: function (childMarkers, positions) {
+               var group = this._group,
+                       map = group._map,
+                       fg = group._featureGroup,
+                       i, m, leg, newPos;
+
+               for (i = childMarkers.length - 1; i >= 0; i--) {
+                       newPos = map.layerPointToLatLng(positions[i]);
+                       m = childMarkers[i];
+
+                       m._preSpiderfyLatlng = m._latlng;
+                       m.setLatLng(newPos);
+                       if (m.setZIndexOffset) {
+                               m.setZIndexOffset(1000000); //Make these appear on top of EVERYTHING
+                       }
+
+                       fg.addLayer(m);
+
+
+                       leg = new L.Polyline([this._latlng, newPos], { weight: 1.5, color: '#222' });
+                       map.addLayer(leg);
+                       m._spiderLeg = leg;
+               }
+               this.setOpacity(0.3);
+               group.fire('spiderfied');
+       },
+
+       _animationUnspiderfy: function () {
+               this._noanimationUnspiderfy();
+       }
+} : {
+       //Animated versions here
+       SVG_ANIMATION: (function () {
+               return document.createElementNS('http://www.w3.org/2000/svg', 'animate').toString().indexOf('SVGAnimate') > -1;
+       }()),
+
+       _animationSpiderfy: function (childMarkers, positions) {
+               var me = this,
+                       group = this._group,
+                       map = group._map,
+                       fg = group._featureGroup,
+                       thisLayerPos = map.latLngToLayerPoint(this._latlng),
+                       i, m, leg, newPos;
+
+               //Add markers to map hidden at our center point
+               for (i = childMarkers.length - 1; i >= 0; i--) {
+                       m = childMarkers[i];
+
+                       //If it is a marker, add it now and we'll animate it out
+                       if (m.setOpacity) {
+                               m.setZIndexOffset(1000000); //Make these appear on top of EVERYTHING
+                               m.setOpacity(0);
+                       
+                               fg.addLayer(m);
+
+                               m._setPos(thisLayerPos);
+                       } else {
+                               //Vectors just get immediately added
+                               fg.addLayer(m);
+                       }
+               }
+
+               group._forceLayout();
+               group._animationStart();
+
+               var initialLegOpacity = L.Path.SVG ? 0 : 0.3,
+                       xmlns = L.Path.SVG_NS;
+
+
+               for (i = childMarkers.length - 1; i >= 0; i--) {
+                       newPos = map.layerPointToLatLng(positions[i]);
+                       m = childMarkers[i];
+
+                       //Move marker to new position
+                       m._preSpiderfyLatlng = m._latlng;
+                       m.setLatLng(newPos);
+                       
+                       if (m.setOpacity) {
+                               m.setOpacity(1);
+                       }
+
+
+                       //Add Legs.
+                       leg = new L.Polyline([me._latlng, newPos], { weight: 1.5, color: '#222', opacity: initialLegOpacity });
+                       map.addLayer(leg);
+                       m._spiderLeg = leg;
+
+                       //Following animations don't work for canvas
+                       if (!L.Path.SVG || !this.SVG_ANIMATION) {
+                               continue;
+                       }
+
+                       //How this works:
+                       //http://stackoverflow.com/questions/5924238/how-do-you-animate-an-svg-path-in-ios
+                       //http://dev.opera.com/articles/view/advanced-svg-animation-techniques/
+
+                       //Animate length
+                       var length = leg._path.getTotalLength();
+                       leg._path.setAttribute("stroke-dasharray", length + "," + length);
+
+                       var anim = document.createElementNS(xmlns, "animate");
+                       anim.setAttribute("attributeName", "stroke-dashoffset");
+                       anim.setAttribute("begin", "indefinite");
+                       anim.setAttribute("from", length);
+                       anim.setAttribute("to", 0);
+                       anim.setAttribute("dur", 0.25);
+                       leg._path.appendChild(anim);
+                       anim.beginElement();
+
+                       //Animate opacity
+                       anim = document.createElementNS(xmlns, "animate");
+                       anim.setAttribute("attributeName", "stroke-opacity");
+                       anim.setAttribute("attributeName", "stroke-opacity");
+                       anim.setAttribute("begin", "indefinite");
+                       anim.setAttribute("from", 0);
+                       anim.setAttribute("to", 0.5);
+                       anim.setAttribute("dur", 0.25);
+                       leg._path.appendChild(anim);
+                       anim.beginElement();
+               }
+               me.setOpacity(0.3);
+
+               //Set the opacity of the spiderLegs back to their correct value
+               // The animations above override this until they complete.
+               // If the initial opacity of the spiderlegs isn't 0 then they appear before the animation starts.
+               if (L.Path.SVG) {
+                       this._group._forceLayout();
+
+                       for (i = childMarkers.length - 1; i >= 0; i--) {
+                               m = childMarkers[i]._spiderLeg;
+
+                               m.options.opacity = 0.5;
+                               m._path.setAttribute('stroke-opacity', 0.5);
+                       }
+               }
+
+               setTimeout(function () {
+                       group._animationEnd();
+                       group.fire('spiderfied');
+               }, 200);
+       },
+
+       _animationUnspiderfy: function (zoomDetails) {
+               var group = this._group,
+                       map = group._map,
+                       fg = group._featureGroup,
+                       thisLayerPos = zoomDetails ? map._latLngToNewLayerPoint(this._latlng, zoomDetails.zoom, zoomDetails.center) : map.latLngToLayerPoint(this._latlng),
+                       childMarkers = this.getAllChildMarkers(),
+                       svg = L.Path.SVG && this.SVG_ANIMATION,
+                       m, i, a;
+
+               group._animationStart();
+
+               //Make us visible and bring the child markers back in
+               this.setOpacity(1);
+               for (i = childMarkers.length - 1; i >= 0; i--) {
+                       m = childMarkers[i];
+
+                       //Marker was added to us after we were spidified
+                       if (!m._preSpiderfyLatlng) {
+                               continue;
+                       }
+
+                       //Fix up the location to the real one
+                       m.setLatLng(m._preSpiderfyLatlng);
+                       delete m._preSpiderfyLatlng;
+                       //Hack override the location to be our center
+                       if (m.setOpacity) {
+                               m._setPos(thisLayerPos);
+                               m.setOpacity(0);
+                       } else {
+                               fg.removeLayer(m);
+                       }
+
+                       //Animate the spider legs back in
+                       if (svg) {
+                               a = m._spiderLeg._path.childNodes[0];
+                               a.setAttribute('to', a.getAttribute('from'));
+                               a.setAttribute('from', 0);
+                               a.beginElement();
+
+                               a = m._spiderLeg._path.childNodes[1];
+                               a.setAttribute('from', 0.5);
+                               a.setAttribute('to', 0);
+                               a.setAttribute('stroke-opacity', 0);
+                               a.beginElement();
+
+                               m._spiderLeg._path.setAttribute('stroke-opacity', 0);
+                       }
+               }
+
+               setTimeout(function () {
+                       //If we have only <= one child left then that marker will be shown on the map so don't remove it!
+                       var stillThereChildCount = 0;
+                       for (i = childMarkers.length - 1; i >= 0; i--) {
+                               m = childMarkers[i];
+                               if (m._spiderLeg) {
+                                       stillThereChildCount++;
+                               }
+                       }
+
+
+                       for (i = childMarkers.length - 1; i >= 0; i--) {
+                               m = childMarkers[i];
+
+                               if (!m._spiderLeg) { //Has already been unspiderfied
+                                       continue;
+                               }
+
+
+                               if (m.setOpacity) {
+                                       m.setOpacity(1);
+                                       m.setZIndexOffset(0);
+                               }
+
+                               if (stillThereChildCount > 1) {
+                                       fg.removeLayer(m);
+                               }
+
+                               map.removeLayer(m._spiderLeg);
+                               delete m._spiderLeg;
+                       }
+                       group._animationEnd();
+               }, 200);
+       }
+});
+
+
+L.MarkerClusterGroup.include({
+       //The MarkerCluster currently spiderfied (if any)
+       _spiderfied: null,
+
+       _spiderfierOnAdd: function () {
+               this._map.on('click', this._unspiderfyWrapper, this);
+
+               if (this._map.options.zoomAnimation) {
+                       this._map.on('zoomstart', this._unspiderfyZoomStart, this);
+               }
+               //Browsers without zoomAnimation or a big zoom don't fire zoomstart
+               this._map.on('zoomend', this._noanimationUnspiderfy, this);
+
+               if (L.Path.SVG && !L.Browser.touch) {
+                       this._map._initPathRoot();
+                       //Needs to happen in the pageload, not after, or animations don't work in webkit
+                       //  http://stackoverflow.com/questions/8455200/svg-animate-with-dynamically-added-elements
+                       //Disable on touch browsers as the animation messes up on a touch zoom and isn't very noticable
+               }
+       },
+
+       _spiderfierOnRemove: function () {
+               this._map.off('click', this._unspiderfyWrapper, this);
+               this._map.off('zoomstart', this._unspiderfyZoomStart, this);
+               this._map.off('zoomanim', this._unspiderfyZoomAnim, this);
+
+               this._unspiderfy(); //Ensure that markers are back where they should be
+       },
+
+
+       //On zoom start we add a zoomanim handler so that we are guaranteed to be last (after markers are animated)
+       //This means we can define the animation they do rather than Markers doing an animation to their actual location
+       _unspiderfyZoomStart: function () {
+               if (!this._map) { //May have been removed from the map by a zoomEnd handler
+                       return;
+               }
+
+               this._map.on('zoomanim', this._unspiderfyZoomAnim, this);
+       },
+       _unspiderfyZoomAnim: function (zoomDetails) {
+               //Wait until the first zoomanim after the user has finished touch-zooming before running the animation
+               if (L.DomUtil.hasClass(this._map._mapPane, 'leaflet-touching')) {
+                       return;
+               }
+
+               this._map.off('zoomanim', this._unspiderfyZoomAnim, this);
+               this._unspiderfy(zoomDetails);
+       },
+
+
+       _unspiderfyWrapper: function () {
+               /// <summary>_unspiderfy but passes no arguments</summary>
+               this._unspiderfy();
+       },
+
+       _unspiderfy: function (zoomDetails) {
+               if (this._spiderfied) {
+                       this._spiderfied.unspiderfy(zoomDetails);
+               }
+       },
+
+       _noanimationUnspiderfy: function () {
+               if (this._spiderfied) {
+                       this._spiderfied._noanimationUnspiderfy();
+               }
+       },
+
+       //If the given layer is currently being spiderfied then we unspiderfy it so it isn't on the map anymore etc
+       _unspiderfyLayer: function (layer) {
+               if (layer._spiderLeg) {
+                       this._featureGroup.removeLayer(layer);
+
+                       layer.setOpacity(1);
+                       //Position will be fixed up immediately in _animationUnspiderfy
+                       layer.setZIndexOffset(0);
+
+                       this._map.removeLayer(layer._spiderLeg);
+                       delete layer._spiderLeg;
+               }
+       }
+});
+
+
+}(window, document));
\ No newline at end of file
diff --git a/www/plugins/gis/lib/leaflet/plugins/leaflet.markercluster.css b/www/plugins/gis/lib/leaflet/plugins/leaflet.markercluster.css
new file mode 100755 (executable)
index 0000000..789c8a3
--- /dev/null
@@ -0,0 +1,67 @@
+.leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow {
+       -webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in;
+       -moz-transition: -moz-transform 0.3s ease-out, opacity 0.3s ease-in;
+       -o-transition: -o-transform 0.3s ease-out, opacity 0.3s ease-in;
+       transition: transform 0.3s ease-out, opacity 0.3s ease-in;
+       }
+
+.marker-cluster-small {
+       background-color: rgba(181, 226, 140, 0.6);
+       }
+.marker-cluster-small div {
+       background-color: rgba(110, 204, 57, 0.6);
+       }
+
+.marker-cluster-medium {
+       background-color: rgba(241, 211, 87, 0.6);
+       }
+.marker-cluster-medium div {
+       background-color: rgba(240, 194, 12, 0.6);
+       }
+
+.marker-cluster-large {
+       background-color: rgba(253, 156, 115, 0.6);
+       }
+.marker-cluster-large div {
+       background-color: rgba(241, 128, 23, 0.6);
+       }
+
+       /* IE 6-8 fallback colors */
+.leaflet-oldie .marker-cluster-small {
+       background-color: rgb(181, 226, 140);
+       }
+.leaflet-oldie .marker-cluster-small div {
+       background-color: rgb(110, 204, 57);
+       }
+
+.leaflet-oldie .marker-cluster-medium {
+       background-color: rgb(241, 211, 87);
+       }
+.leaflet-oldie .marker-cluster-medium div {
+       background-color: rgb(240, 194, 12);
+       }
+
+.leaflet-oldie .marker-cluster-large {
+       background-color: rgb(253, 156, 115);
+       }
+.leaflet-oldie .marker-cluster-large div {
+       background-color: rgb(241, 128, 23);
+}
+
+.marker-cluster {
+       background-clip: padding-box;
+       border-radius: 20px;
+       }
+.marker-cluster div {
+       width: 30px;
+       height: 30px;
+       margin-left: 5px;
+       margin-top: 5px;
+
+       text-align: center;
+       border-radius: 15px;
+       font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
+       }
+.marker-cluster span {
+       line-height: 30px;
+       }
\ No newline at end of file
diff --git a/www/plugins/gis/modeles/carte_gis.html b/www/plugins/gis/modeles/carte_gis.html
new file mode 100644 (file)
index 0000000..3ee2176
--- /dev/null
@@ -0,0 +1,148 @@
+[(#REM)\r
+\r
+Modele carte_gis\r
+----------------\r
+\r
+Parametres possibles :\r
+\r
+- id_map|id_carte_gis = 1          id de la carte\r
+- lat|latit|latitude = 48.3        latitude du centre de la carte\r
+- lon|lonxit|longitude = -4.7      longitude du centre de la carte\r
+- zoom = 5                         zoom de la carte\r
+- maxZoom = 13                     zoom maximum autorisé\r
+- minZoom = 13                     zoom minimum autorisé\r
+\r
+- default_layer = acetate                   nom de la couche affichée par défaut\r
+- affiche_layers = openmapsurfer/acetate    noms des couches proposées séparés par des /\r
+\r
+- sw_lat = lat - 10°               latitude du sud-ouest de la bounding box\r
+- sw_lon = lon - 10°               longitude du sud-ouest de la bounding box\r
+- ne_lat = lat + 10°               latitude du nord-est de la bounding box\r
+- ne_lon = lon + 10°               longitude du nord-est de la bounding box\r
+\r
+- width|largeur = 100%             largeur de la carte, 100% par defaut\r
+- height|hauteur = 400px           hauteur de la carte, 400px par defaut\r
+- style = non                      ne pas styler la carte\r
+\r
+- fullscreen = oui                     afficher un bouton pour passer la carte en plein écran\r
+- zoom_molette|zoom_wheel = non        désactiver le zoom avec la molette de la souris, actif par defaut\r
+- control_type|controle_type = non     ne pas afficher le contrôle de changement de couche\r
+- control_type_collapsed = non         afficher le contrôle de changement de couche replié (oui par défaut)\r
+- no_control|aucun_controle = oui      ne pas afficher les contrôles de la carte\r
+- scale = oui                          afficher l'échelle de la carte\r
+- overview = oui                       afficher une mini carte de situation\r
+\r
+- autocenterandzoom|centrer_auto = oui        centrer et zoomer la carte automatiquement pour afficher tous les marqueurs\r
+- localize_visitor|localiser_visiteur = oui   centrer la carte sur la position du visiteur (API geolocation HTML5)\r
+- localize_visitor_zoom = 12                  niveau de zoom sur la position du visiteur (par défaut la valeur de zoom de la carte) \r
+- id_a_ouvrir                                 id_gis de l'infobulle à afficher au chargement(marqueur uniquement)\r
+\r
+- objets = gis                     type d'objets à afficher (fichier json/gis_xx qui génère la source de donnees)\r
+- limit|limite = 500               nombre max de marqueurs à afficher, 500 par defaut\r
+- kml = 12                         kml à superposer à la carte (id_document ou url ou liste d'url)\r
+- gpx = 12                         gpx à superposer à la carte (id_document ou url ou liste d'url)\r
+- geojson = 12                     geojson à superposer à la carte (id_document ou url  ou liste d'url)\r
+- centrer_fichier = non            permet de ne pas centrer la carte automatiquement sur les fichiers kml/gpx surperposés\r
+- point = non                      si elle vaut "non" cette option n'affichera pas de points du tout (utile pour n'afficher qu'un kml par exemple)\r
+\r
+- media = non                      permet de passer le critère 'media' (pour les documents)\r
+- mots = #LISTE{1,4,7}             plugin critere {mots} http://contrib.spip.net/Critere-mots\r
+- path_styles=#ARRAY{color,#fff}   options de style des éléments de la couche GeoJSON (voir http://leafletjs.com/reference.html#path-options)\r
+\r
+Uniquement si objets = point_libre :\r
+- icone = chemin/vers/image        image utilisée pour le marker\r
+- titre                            titre du point\r
+- description                      description du point\r
+\r
+Clustering (regroupement de points proches) :\r
+- cluster = oui                      Active le clustering\r
+- clusterMaxZoom = 11                Regroupe les points jusque à ce zoom, mais pas au delà\r
+- clusterShowCoverageOnHover = oui   Affiche au survol du cluster le contour de la zone couverte par les points regroupés\r
+- maxClusterRadius = 80              Le rayon maximal (en pixels) qu'un cluster couvrira (80 pixels par defaut)\r
+- clusterSpiderfyOnMaxZoom = oui     Active l'effet d'éclatement pour afficher les points qui se chevauchent\r
+]\r
+\r
+[(#SET{width,#ENV{width,#ENV{largeur,100%}}})]\r
+[(#SET{height,#ENV{height,#ENV{hauteur,400px}}})]\r
+[(#SET{id,#ENV{id_carte_gis,#ENV{id_map,#ENV{id,1}}}})]\r
+[(#REM) -- compat gis v1 -- ]\r
+[(#SET{lat,#ENV{lat,#ENV{latit,#ENV{latitude,#CONFIG{gis/lat,0}}}}})]\r
+[(#SET{lon,#ENV{lon,#ENV{lonxit,#ENV{longitude,#CONFIG{gis/lon,0}}}}})]\r
+[(#REM) On utilise la bounding box seulement si le centre n'a pas été donné et si les quatre valeurs de la bounding box sont renseignées\r
+    les valeurs par defaut sont "centre +/- 10°", ce qui est naze, mais c'est un cas normalement impossible\r
+]\r
+[(#ENV{lat}|ou{#ENV{lon}}|non|et{#ENV{sw_lat}}|et{#ENV{sw_lon}}|et{#ENV{ne_lat}}|et{#ENV{ne_lon}})\r
+       #SET{utiliser_bb, oui}\r
+       #SET{sw_lat,#ENV{sw_lat,#GET{lat}|moins{10}}}\r
+       #SET{sw_lon,#ENV{sw_lon,#GET{lon}|moins{10}}}\r
+       #SET{ne_lat,#ENV{ne_lat,#GET{lat}|plus{10}}}\r
+       #SET{ne_lon,#ENV{ne_lon,#GET{lon}|plus{10}}}\r
+]\r
+\r
+<div id="map[(#GET{id})]" class="carte_gis"[(#ENV{style}|!={'non'}|?{' '})style="[width:(#GET{width});][ height:(#GET{height});]"]></div>\r
+\r
+<script type="text/javascript">/*<!\[CDATA\[*/\r
+var map[(#GET{id})];\r
+var jQgisloader;\r
+// Charger le javascript de GIS une seule fois si plusieurs carte\r
+if (typeof jQgisloader=="undefined"){\r
+       jQgisloader = jQuery.ajax({url: '[(#PRODUIRE{fond=javascript/gis.js})]', dataType: 'script', cache: true});\r
+}\r
+// et initialiser la carte (des que js GIS charge et des que DOM ready)\r
+jQgisloader.done(function(){\r
+       jQuery(function(){\r
+               map[(#GET{id})] = new L.Map.Gis('map[(#GET{id})]',{\r
+                       mapId: '[(#GET{id})]',\r
+                       callback: (typeof(callback_map[(#GET{id})]) === "function") ? callback_map[(#GET{id})] : false,\r
+                       center: [#GET{lat},#GET{lon}],\r
+                       zoom: [(#ENV{zoom,#CONFIG{gis/zoom,0}})][,\r
+                       maxZoom: (#ENV{maxZoom})][,\r
+                       minZoom: (#ENV{minZoom})][,\r
+                       default_layer: '(#ENV{default_layer})'][,\r
+                       affiche_layers: (#ENV{affiche_layers}|?{[(#ENV{affiche_layers}|explode{/}|json_encode)],''})],\r
+                       scrollWheelZoom: [(#ENV{zoom_molette,#ENV{zoom_wheel}}|=={non}|?{false,true})],\r
+                       zoomControl: [(#ENV{no_control,#ENV{aucun_controle}}|!={oui}|?{true,false})],\r
+                       fullscreenControl: [(#ENV{fullscreen}|=={oui}|?{true,false})],\r
+                       scaleControl: [(#ENV{scale}|=={oui}|?{true,false})],\r
+                       overviewControl:[(#ENV{overview}|=={oui}|?{true,false})],\r
+                       layersControl: [(#ENV{control_type,#ENV{controle_type}}|=={non}|?{false,true})],\r
+                       layersControlOptions: {\r
+                               collapsed: [(#ENV{control_type_collapsed,#ENV{control_type_collapsed}}|=={non}|?{false,true})]\r
+                       },\r
+                       noControl: [(#ENV{no_control,#ENV{aucun_controle}}|=={oui}|?{true,false})],\r
+                       utiliser_bb: [(#GET{utiliser_bb}|?{true,false})][,\r
+                       sw_lat: (#GET{sw_lat})][,\r
+                       ne_lat: (#GET{ne_lat})][,\r
+                       sw_lon: (#GET{sw_lon})][,\r
+                       ne_lon: (#GET{ne_lon})],\r
+                       affiche_points: [(#ENV{point,''}|=={non}|?{false,true})],\r
+                       json_points:{\r
+                               url: '[(#URL_PAGE{gis_json}|url_absolue)]'[,\r
+                               objets: '(#ENV{objets,#ENV{class}}|trim)'],\r
+                               limit: [(#ENV{limit,#ENV{limite,500}}|trim)],\r
+                               env: [(#ENV*{args,#ENV*}|gis_modele_url_json_env|json_encode)][,\r
+                               titre: (#ENV{titre}|?{#ENV{titre},''}|json_encode)][,\r
+                               description: (#ENV{description}|?{#ENV{description},''}|json_encode)][,\r
+                               icone: (#ENV{icone}|?{#ENV{icone},''}|json_encode)]\r
+                       },\r
+                       cluster: [(#ENV{cluster}|=={oui}|?{true,false})],\r
+                       clusterOptions: {\r
+                               disableClusteringAtZoom: [(#ENV{clusterMaxZoom,0})],\r
+                               showCoverageOnHover: [(#ENV{clusterShowCoverageOnHover}|?{true,false})],\r
+                               spiderfyOnMaxZoom: [(#ENV{clusterSpiderfyOnMaxZoom}|?{true,false})],\r
+                               maxClusterRadius: [(#ENV{maxClusterRadius,80})]\r
+                       },\r
+                       pathStyles: [(#ENV*{path_styles}|json_encode)],\r
+                       autocenterandzoom: [(#ENV{autocenterandzoom,#ENV{centrer_auto}}|?{true,false})],\r
+                       openId: [(#ENV{id_a_ouvrir,false})],\r
+                       localize_visitor: [(#ENV{localize_visitor,#ENV{localiser_visiteur}}|?{true,false})],\r
+                       localize_visitor_zoom: [(#ENV{localize_visitor_zoom,#ENV{zoom,#CONFIG{gis/zoom,0}}})],\r
+                       centrer_fichier: [(#ENV{centrer_fichier,oui}|=={oui}|?{true,false})],\r
+                       kml: [(#ENV{kml,''}|?{[(#ENV{kml}|is_array|?{#ENV{kml},#LISTE{#ENV{kml}}}|gis_kml_to_urls|json_encode)],false})],\r
+                       gpx: [(#ENV{gpx,''}|?{[(#ENV{gpx}|is_array|?{#ENV{gpx},#LISTE{#ENV{gpx}}}|gis_kml_to_urls|json_encode)],false})],\r
+                       geojson: [(#ENV{geojson,''}|?{[(#ENV{geojson}|is_array|?{#ENV{geojson},#LISTE{#ENV{geojson}}}|gis_kml_to_urls|json_encode)],false})]\r
+               });\r
+       });\r
+});\r
+/*\]\]>*/\r
+</script>\r
diff --git a/www/plugins/gis/modeles/carte_gis.yaml b/www/plugins/gis/modeles/carte_gis.yaml
new file mode 100644 (file)
index 0000000..7ccf033
--- /dev/null
@@ -0,0 +1,278 @@
+nom: <:gis:info_1_gis:>
+logo: 'prive/themes/spip/images/gis-24.png'
+icone_barre: 'gis.png'
+traiter: 'gis_inserer_modeles_traiter'
+parametres:
+  -
+    saisie: 'hidden'
+    options:
+      nom: 'modele'
+      defaut: 'carte_gis'
+  -
+    saisie: 'selection'
+    options:
+      nom: 'objets'
+      label: <:gis:label_inserer_modele_objets:>
+      defaut: 'point_libre' #pas pris en compte ?
+      cacher_option_intro: 'oui'
+      datas:
+        '': <:gis:label_inserer_modele_point_gis:>
+        point_libre: <:gis:label_inserer_modele_point_libre:>
+        articles: <:gis:label_inserer_modele_articles:>
+        sites: <:gis:label_inserer_modele_sites:>
+        articles_plus_sites: <:gis:label_inserer_modele_articles_sites:>
+        rubriques: <:gis:label_inserer_modele_rubriques:>
+        documents: <:gis:label_inserer_modele_documents:>
+        mots: <:gis:label_inserer_modele_mots:>
+        auteurs: <:gis:label_inserer_modele_auteurs:>
+  # IDENTIFIANTS
+  -
+    saisie: 'input'
+    options:
+      nom: 'id_gis'
+      label: <:gis:label_inserer_modele_identifiant:>
+      placeholder: <:gis:label_inserer_modele_identifiant_placeholder:>
+      afficher_si: '@objets@ == ""'
+  -
+    saisie: 'input'
+    options:
+      nom: 'id_article'
+      label: <:gis:label_inserer_modele_identifiant_opt:>
+      placeholder: 'id_article'
+      afficher_si: '@objets@ == "articles" || @objets@ == "articles_plus_sites"'
+  -
+    saisie: 'input'
+    options:
+      nom: 'id_rubrique'
+      label: <:gis:label_inserer_modele_identifiant_opt:>
+      placeholder: 'id_rubrique'
+      afficher_si: '@objets@ == "rubriques"'
+  -
+    saisie: 'input'
+    options:
+      nom: 'id_document'
+      label: <:gis:label_inserer_modele_identifiant_opt:>
+      placeholder: 'id_document'
+      afficher_si: '@objets@ == "documents"'
+  -
+    saisie: 'input'
+    options:
+      nom: 'id_mot'
+      label: <:gis:label_inserer_modele_identifiant_opt:>
+      placeholder: 'id_mot'
+      afficher_si: '@objets@ == "mots"'
+  -
+    saisie: 'input'
+    options:
+      nom: 'id_site'
+      label: <:gis:label_inserer_modele_identifiant_opt:>
+      placeholder: 'id_site'
+      afficher_si: '@objets@ == "sites"'
+  -
+    saisie: 'input'
+    options:
+      nom: 'id_auteur'
+      label: <:gis:label_inserer_modele_identifiant_opt:>
+      placeholder: 'id_auteur'
+      afficher_si: '@objets@ == "auteurs"'
+#
+# === POINT LIBRE ===
+#
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'field_point_libre'
+      afficher_si: '@objets@ == "point_libre"'
+    saisies:
+      -
+        saisie: 'carte'
+        options:
+          nom: 'carte'
+          hauteur: '200px'
+      -
+        saisie: 'input'
+        options:
+          nom: 'titre'
+          label: <:gis:titre_objet:>
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'description'
+          label: <:gis:label_inserer_modele_description:>
+          rows: '2'
+      -
+        saisie: 'hidden'
+        options:
+          nom: 'lat'
+      -
+        saisie: 'hidden'
+        options:
+          nom: 'lon'
+      -
+        saisie: 'hidden'
+        options:
+          nom: 'zoom'
+      -
+        saisie: 'input'
+        options:
+          nom: 'adresse'
+          placeholder: <:gis:label_adress:>
+          readonly: 'oui'
+      -
+        saisie: 'input'
+        options:
+          nom: 'code_postal'
+          placeholder: <:gis:label_code_postal:>
+          readonly: 'oui'
+      -
+        saisie: 'input'
+        options:
+          nom: 'ville'
+          placeholder: <:gis:label_ville:>
+          readonly: 'oui'
+      -
+        saisie: 'input'
+        options:
+          nom: 'pays'
+          placeholder: <:gis:label_pays:>
+          readonly: 'oui'
+#
+# === OPTIONS ===
+#
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'field_options'
+      pliable: 'oui'
+      plie: 'oui'
+      label: 'options'
+    saisies:
+      -
+        saisie: 'input'
+        options:
+          nom: 'titre_carte'
+          label: <:gis:label_inserer_modele_titre_carte:>
+      -
+        saisie: 'input'
+        options:
+          nom: 'largeur'
+          label: <:gis:label_inserer_modele_largeur_carte:>
+          placeholder: '100%'
+      -
+        saisie: 'input'
+        options:
+          nom: 'hauteur'
+          label: <:gis:label_inserer_modele_hauteur_carte:>
+          placeholder: '400px'
+#      -
+#        saisie: 'case'
+#        options:
+#          nom: 'style'
+#          label_case: 'ne pas styler la carte'
+#          defaut: ''
+#          valeur_oui: 'non'
+#          valeur_non: ''
+      -
+        saisie: 'input'
+        options:
+          nom: 'limit'
+          label:  <:gis:label_inserer_modele_limite:>
+          afficher_si: '@objets@ != "gis" || @objets@ != "point_unique"'
+          placeholder: '500'
+      -
+        saisie: 'case'
+        options:
+          nom: 'scale'
+          label_case: <:gis:label_inserer_modele_echelle:>
+          defaut: ''
+          valeur_oui: 'oui'
+          valeur_non: ''
+      -
+        saisie: 'case'
+        options:
+          nom: 'fullscreen'
+          label_case: <:gis:label_inserer_modele_fullscreen:>
+          defaut: ''
+          valeur_oui: 'oui'
+          valeur_non: ''
+      -
+        saisie: 'case'
+        options:
+          nom: 'overview'
+          label_case: <:gis:label_inserer_modele_mini_carte:>
+          defaut: ''
+          valeur_oui: 'oui'
+          valeur_non: ''
+      -
+        saisie: 'case'
+        options:
+          nom: 'zoom_molette'
+          label_case: <:gis:label_inserer_modele_molette:>
+          defaut: ''
+          valeur_oui: 'non'
+          valeur_non: ''
+      -
+        saisie: 'case'
+        options:
+          nom: 'aucun_controle'
+          label_case: <:gis:label_inserer_modele_controle:>
+          defaut: ''
+          valeur_oui: 'oui'
+          valeur_non: ''
+      -
+        saisie: 'case'
+        options:
+          nom: 'controle_type'
+          label_case: <:gis:label_inserer_modele_controle_type:>
+          defaut: ''
+          valeur_oui: 'non'
+          valeur_non: ''
+      -
+        saisie: 'case'
+        options:
+          nom: 'localize_visitor'
+          label_case: <:gis:label_inserer_modele_localiser_visiteur:>
+          defaut: ''
+          valeur_oui: 'oui'
+          valeur_non: ''
+      -
+        saisie: 'case'
+        options:
+          nom: 'centrer_auto'
+          label_case: <:gis:label_inserer_modele_centrer_auto:>
+          defaut: 'oui'
+          valeur_oui: ''
+          valeur_non: 'oui'
+          afficher_si: '@objets@ != "gis" || @objets@ != "point_unique"'
+      -
+        saisie: 'input'
+        options:
+          nom: 'kml'
+          label: <:gis:label_inserer_modele_kml:>
+          placeholder: <:gis:label_inserer_modele_kml_gpx:>
+          afficher_si: '@objets@ != "gis" || @objets@ != "point_unique"'
+      -
+        saisie: 'input'
+        options:
+          nom: 'gpx'
+          label: <:gis:label_inserer_modele_gpx:>
+          placeholder: <:gis:label_inserer_modele_kml_gpx:>
+          afficher_si: '@objets@ != "gis" || @objets@ != "point_unique"'
+      -
+        saisie: 'case'
+        options:
+          nom: 'centrer_fichier'
+          label_case: <:gis:label_inserer_modele_centrer_fichier:>
+          valeur_oui: 'oui'
+          valeur_non: ''
+          afficher_si: '@objets@ != "gis" || @objets@ != "point_unique"'
+      -
+        saisie: 'case'
+        options:
+          nom: 'point'
+          label_case: <:gis:label_inserer_modele_points:>
+          #explication: "Utile pour n'afficher que les données kml/gpx"
+          defaut: ''
+          valeur_oui: 'non'
+          valeur_non: ''
+          afficher_si: '@objets@ != "gis" || @objets@ != "point_unique"'
diff --git a/www/plugins/gis/modeles/carte_gis_objet.html b/www/plugins/gis/modeles/carte_gis_objet.html
new file mode 100644 (file)
index 0000000..abc2d72
--- /dev/null
@@ -0,0 +1,27 @@
+[(#REM)
+       S'il y a au moins un point GIS *ou* au moins un fichier de tracé, on affiche une carte.
+       Celle-ci contiendra tous les points liés à l'objet demandé, et tous les tracés joints quelque soit leur format.
+]
+
+#SET{carte,''}
+#SET{point,non}
+<BOUCLE_gis(GIS){objet}{id_objet}{0,1}>
+#SET{carte,oui}
+#SET{point,oui}
+</BOUCLE_gis>
+#SET{kml,#LISTE} #SET{gpx,#LISTE} #SET{json,#LISTE}
+<BOUCLE_trace(DOCUMENTS){objet}{id_objet}{extension IN gpx,kml,json}>
+#SET{#EXTENSION, #GET{#EXTENSION}|push{#ID_DOCUMENT}}
+#SET{carte,oui}
+</BOUCLE_trace>
+
+[(#GET{carte}|oui)
+       [(#INCLURE{fond=modeles/carte_gis,
+               env,
+               objet=#ENV{objet},
+               id_objet=#ENV{id_objet},
+               point=#GET{point},
+               geojson=#GET{json},
+               gpx=#GET{gpx},
+               kml=#GET{kml}})]
+]
diff --git a/www/plugins/gis/modeles/carte_gis_preview.html b/www/plugins/gis/modeles/carte_gis_preview.html
new file mode 100755 (executable)
index 0000000..2e9ca57
--- /dev/null
@@ -0,0 +1,112 @@
+<BOUCLE_gis(GIS){objet ?}{id_objet ?}{id_gis ?}{0,1}>\r
+<div id="map_preview" class="carte_gis" style="width: 100%; height: 150px;"></div>\r
+\r
+<script type="text/javascript">\r
+/*<![CDATA[*/\r
+(function($){\r
+       var init_map_preview = function() {\r
+               var map_preview;\r
+               var map_preview_container = 'map_preview';\r
+               var marker;\r
+               \r
+               map_preview = new L.Map(map_preview_container);\r
+               \r
+               map_preview.attributionControl.setPrefix('');\r
+               \r
+               var base_layer = [new (#EVAL{$GLOBALS\['gis_layers'\]}|table_valeur{[(#REM|gis_layer_defaut)]/layer})];\r
+               map_preview.addLayer(base_layer);\r
+               \r
+               map_preview.setView(new L.LatLng([(#LAT)],[(#LON)]),[(#ZOOM|sinon{#CONFIG{gis/zoom,0}})]);\r
+               
+               [(#REM) On ajoute les points ou le point unique suivant les params ]\r
+               <B_points>\r
+               var data = {\r
+                       "type": "FeatureCollection",\r
+                       "features": [\r
+               <BOUCLE_points(GIS){id_objet}{objet}{","}>\r
+                               {"type": "Feature",\r
+                                       "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},\r
+                                       "id":"#ID_GIS",\r
+                                       "properties": {\r
+                                               "title":[(#TITRE|supprimer_numero|json_encode)],\r
+                                               "description":[(#DESCRIPTIF|json_encode)][,(#LOGO_GIS|oui)\r
+                                               [(#SET{logo_doc,#LOGO_GIS|image_passe_partout{28,28}|image_recadre{28,28}|image_recadre{32,32,center}|image_aplatir{jpg,ffffff}})]\r
+                                               #SET{icon_w,#GET{logo_doc}|extraire_attribut{src}|largeur}\r
+                                               #SET{icon_h,#GET{logo_doc}|extraire_attribut{src}|hauteur}\r
+                                               ["icon": (#GET{logo_doc}|extraire_attribut{src}|url_absolue|json_encode)],\r
+                                               "icon_size": \[#GET{icon_w},#GET{icon_h}\],\r
+                                               "icon_anchor": \[[(#GET{icon_w}|div{2})],[(#GET{icon_h})]\],\r
+                                               "popup_anchor": \[1,[-(#GET{icon_h}|div{1.2})]\]]\r
+                                       }\r
+                               }\r
+               </BOUCLE_points>\r
+                       ]\r
+               };\r
+               [(#TOTAL_BOUCLE|>{1}|oui) #SET{autocenter,oui} ]\r
+               </B_points>\r
+               <BOUCLE_point(GIS){id_gis}>\r
+               var data = {\r
+                       "type": "FeatureCollection",\r
+                       "features": [\r
+                               {"type": "Feature",\r
+                                       "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},\r
+                                       "id":"#ID_GIS",\r
+                                       "properties": {\r
+                                               "title":[(#TITRE|supprimer_numero|json_encode)],\r
+                                               "description":[(#DESCRIPTIF|json_encode)][,(#LOGO_GIS|oui)\r
+                                               [(#SET{logo_doc,#LOGO_GIS|image_passe_partout{28,28}|image_recadre{28,28}|image_recadre{32,32,center}|image_aplatir{jpg,ffffff}})]\r
+                                               #SET{icon_w,#GET{logo_doc}|extraire_attribut{src}|largeur}\r
+                                               #SET{icon_h,#GET{logo_doc}|extraire_attribut{src}|hauteur}\r
+                                               ["icon": (#GET{logo_doc}|extraire_attribut{src}|url_absolue|json_encode)],\r
+                                               "icon_size": \[#GET{icon_w},#GET{icon_h}\],\r
+                                               "icon_anchor": \[[(#GET{icon_w}|div{2})],[(#GET{icon_h})]\],\r
+                                               "popup_anchor": \[1,[-(#GET{icon_h}|div{1.2})]\]]\r
+                                       }\r
+                               }]\r
+               };\r
+               </BOUCLE_point>\r
+               <//B_points>\r
+               if (data) {\r
+                       var geojson = new L.geoJson('', {\r
+                               onEachFeature: function (feature, layer) {\r
+                                       if (feature.properties && feature.properties.icon){\r
+                                               layer.setIcon(new L.Icon({\r
+                                                       iconUrl: feature.properties.icon,\r
+                                                       iconSize: new L.Point( feature.properties.icon_size\[0\], feature.properties.icon_size\[1\] ),\r
+                                                       iconAnchor: new L.Point( feature.properties.icon_anchor\[0\], feature.properties.icon_anchor\[1\] ),\r
+                                                       popupAnchor: new L.Point( feature.properties.popup_anchor\[0\], feature.properties.popup_anchor\[1\] )\r
+                                               }));\r
+                                       }\r
+                                       if (feature.properties && feature.properties.title){\r
+                                               var popupContent = '<strong>' + feature.properties.title + '</strong>';\r
+                                               if (feature.properties.description)\r
+                                                       popupContent = popupContent + feature.properties.description;\r
+                                               layer.bindPopup(popupContent);\r
+                                       }\r
+                               }\r
+                       }).addTo(map_preview);\r
+                       geojson.addData(data);\r
+                       [(#GET{autocenter}|oui)\r
+                       map_preview.fitBounds(geojson.getBounds());]\r
+               }\r
+               [(#REM) On ajoute les KML attachés à l'objet ]\r
+               <BOUCLE_kml(DOCUMENTS){tous}{objet}{id_objet}{extension=kml}>\r
+               map_preview.addLayer(new L.KML('[(#URL_DOCUMENT|url_absolue)]', {async: true}));\r
+               </BOUCLE_kml>\r
+               [(#REM) On ajoute les GPX attachés à l'objet ]\r
+               <BOUCLE_gpx(DOCUMENTS){tous}{objet}{id_objet}{extension=gpx}>\r
+               map_preview.addLayer(new L.GPX('[(#URL_DOCUMENT|url_absolue)]', {async: true}));\r
+               </BOUCLE_gpx>\r
+       }\r
+\r
+       $(function(){\r
+               jQuery.getScript('[(#PRODUIRE{fond=javascript/gis.js}|compacte)]',function(){\r
+                       init_map_preview();\r
+               });\r
+               //onAjaxLoad(init_map_preview(true));\r
+       });\r
+\r
+})(jQuery);\r
+/*]]>*/\r
+</script>\r
+</BOUCLE_gis>\r
diff --git a/www/plugins/gis/paquet.xml b/www/plugins/gis/paquet.xml
new file mode 100644 (file)
index 0000000..e3da2d5
--- /dev/null
@@ -0,0 +1,94 @@
+<paquet
+       prefix="gis"
+       categorie="divers"
+       version="4.26.1"
+       schema="2.0.7"
+       etat="stable"
+       compatibilite="[3.0.0;3.1.*]"
+       logo="images/gis.png"
+       documentation="http://contrib.spip.net/4189"
+>
+
+       <nom>GIS</nom>
+       <!-- Système d'information géographique -->
+
+       <auteur>b_b</auteur>
+       <auteur lien="http://www.kent1.info">kent1</auteur>
+       <auteur lien="http://www.ldd.fr">Les Développements Durables</auteur>
+       <credit lien="http://leafletjs.com/">Leaflet</credit>
+       <credit lien="https://github.com/shramov/leaflet-plugins">Leaflet plugins</credit>
+       <credit lien="https://github.com/leaflet-extras/leaflet-providers">Leaflet providers</credit>
+       <credit lien="https://github.com/brunob/leaflet.fullscreen">Leaflet fullscreen</credit>
+       <credit lien="https://github.com/Norkart/Leaflet-MiniMap">Leaflet minimap</credit>
+       <copyright>2011-2014</copyright>
+       <licence>GPL v3</licence>
+       <credit lien="http://mattrich.deviantart.com/art/Picnic-101256405">Icône de mattrich sous licence CC BY-NC-SA</credit>
+
+       <traduire module="gis" reference="fr" gestionnaire="salvatore" />
+       <traduire module="paquet-gis" reference="fr" gestionnaire="salvatore" />
+
+       <utilise nom="selecteurgenerique" compatibilite="[0.8.6;]" />
+       <necessite nom="saisies" compatibilite="[2.0.3;]" />
+
+       <pipeline
+               nom="gis_modele_parametres_autorises"
+       />
+       <pipeline
+               nom="declarer_tables_interfaces"
+               inclure="base/gis.php"
+       />
+       <pipeline
+               nom="declarer_tables_objets_sql"
+               inclure="base/gis.php"
+       />
+       <pipeline
+               nom="declarer_tables_auxiliaires"
+               inclure="base/gis.php"
+       />
+       <pipeline
+               nom="insert_head"
+               inclure="gis_pipelines.php"
+       />
+       <pipeline
+               nom="header_prive"
+               inclure="gis_pipelines.php"
+       />
+       <pipeline
+               nom="insert_head_css"
+               inclure="gis_pipelines.php"
+       />
+       <pipeline
+               nom="afficher_contenu_objet"
+               inclure="gis_pipelines.php"
+       />
+       <pipeline
+               nom="post_edition"
+               inclure="gis_pipelines.php"
+       />
+       <pipeline
+               nom="optimiser_base_disparus"
+               inclure="gis_pipelines.php"
+       />
+       <pipeline
+               nom="autoriser"
+               inclure="gis_autoriser.php"
+       />
+       <pipeline
+               nom="saisies_autonomes"
+               inclure="gis_pipelines.php"
+       />
+       <pipeline
+               nom="xmlrpc_methodes"
+               inclure="gis_pipelines.php"
+       />
+       <pipeline
+               nom="xmlrpc_server_class"
+               inclure="gis_pipelines.php"
+       />
+       <pipeline
+               nom="formulaire_traiter"
+               inclure="gis_pipelines.php"
+       />      
+       <menu nom="gis_tous" titre="gis:icone_gis_tous" parent="menu_edition" icone="images/gis-16.png" />
+       <menu nom="gis_nouveau" titre="gis:editer_gis_nouveau" parent="outils_rapides" icone="images/gis-new-16.png" action="gis_edit" parametres="new=oui" />
+</paquet>
diff --git a/www/plugins/gis/prive/contenu/gis_objet.html b/www/plugins/gis/prive/contenu/gis_objet.html
new file mode 100644 (file)
index 0000000..74dbd33
--- /dev/null
@@ -0,0 +1,6 @@
+[(#SET{titre, #VAL{gis:info_geolocalisation}|_T})]
+[(#BOITE_OUVRIR{#CHEMIN_IMAGE{gis-24.png}|balise_img{'',cadre-icone}|concat{#GET{titre}}, 'simple'})]
+<div id="editer_gis_[(#ENV{objet})]_[(#ENV{id_objet})]" class="ajax-action nom_action">
+       [(#INCLURE{fond=prive/inclure/gis_objet_formulaires,env,ajax})]
+</div>
+#BOITE_FERMER
\ No newline at end of file
diff --git a/www/plugins/gis/prive/inclure/gis_objet_formulaires.html b/www/plugins/gis/prive/inclure/gis_objet_formulaires.html
new file mode 100644 (file)
index 0000000..dc6ab9b
--- /dev/null
@@ -0,0 +1,41 @@
+<div class="ajax">
+<BOUCLE_test(GIS){objet}{id_objet}{0,1}> </BOUCLE_test>
+#SET{gis_defaut,nouveau}
+#SET{gis_defaut,glop}
+<//B_test>
+
+#SET{bloc_gis, #ENV{bloc_gis,#GET{gis_defaut}|=={nouveau}|?{editer,lier}}}
+
+<div class="actions onglets_simple second clearfix">
+       <ul>
+       <li>
+       [(#GET{bloc_gis}|=={lier}|?{<strong>,<a href="[(#SELF|parametre_url{bloc_gis,lier}|parametre_url{id_gis,''})]" class="ajax">})]
+               <:gis:titre_bloc_points_lies:>
+       [(#GET{bloc_gis}|=={lier}|?{</strong>,</a>})]
+       </li>
+       <li>
+       [(#GET{bloc_gis}|=={rechercher}|?{<strong>,<a href="[(#SELF|parametre_url{bloc_gis,rechercher}|parametre_url{id_gis,''})]" class="ajax">})]
+               <:gis:titre_bloc_rechercher_point:>
+       [(#GET{bloc_gis}|=={rechercher}|?{</strong>,</a>})]
+       </li>
+       <li>
+       [(#GET{bloc_gis}|=={editer}|?{<strong>,<a href="[(#SELF|parametre_url{bloc_gis,editer}|parametre_url{id_gis,nouveau})]" class="ajax">})]
+               <:gis:titre_bloc_creer_point:>
+       [(#GET{bloc_gis}|=={editer}|?{</strong>,</a>})]
+       </li>
+       </ul>
+</div>
+
+[(#GET{bloc_gis}|=={editer}|oui)
+       #FORMULAIRE_EDITER_GIS{#ENV{id_gis,#GET{gis_defaut}},#ENV{objet},#ENV{id_objet},#SELF,'non',#ENV{options_formulaire_editer_gis}}
+]
+
+[(#GET{bloc_gis}|=={lier}|oui)
+       [(#INCLURE{fond=prive/objets/liste/gis_lies,sinon=<:gis:aucun_gis:>,env})]
+]
+
+<BOUCLE_gis(GIS){0,1}> </BOUCLE_gis>[(#GET{bloc_gis}|=={rechercher}|oui)
+<div class="ajax">
+       #FORMULAIRE_RECHERCHER_GIS{#ENV{objet},#ENV{id_objet},#SELF|parametre_url{bloc_gis,lier}}
+</div>]</B_gis>
+</div>
diff --git a/www/plugins/gis/prive/objets/contenu/gis.html b/www/plugins/gis/prive/objets/contenu/gis.html
new file mode 100644 (file)
index 0000000..50766c6
--- /dev/null
@@ -0,0 +1,23 @@
+<BOUCLE_gis(GIS){id_gis=#ID}>
+       <div class="champ contenu_descriptif[(#DESCRIPTIF|non)vide]">
+               #DESCRIPTIF
+       </div>
+       <div class="champ contenu_adresse[(#ADRESSE|non)vide]">
+               #ADRESSE
+       </div>
+       <div class="champ contenu_region[(#REGION|non)vide]">
+               #REGION
+       </div>
+       <div class="champ contenu_departement[(#DEPARTEMENT|non)vide]">
+               #DEPARTEMENT
+       </div>
+       <div class="champ contenu_code_postal[(#CODE_POSTAL|non)vide]">
+               #CODE_POSTAL
+       </div>
+       <div class="champ contenu_ville[(#VILLE|non)vide]">
+               #VILLE
+       </div>
+       <div class="champ contenu_pays[(#PAYS|non)vide]">
+               #PAYS [((#CODE_PAYS))]
+       </div>
+</BOUCLE_gis>
diff --git a/www/plugins/gis/prive/objets/infos/gis.html b/www/plugins/gis/prive/objets/infos/gis.html
new file mode 100644 (file)
index 0000000..b7856c9
--- /dev/null
@@ -0,0 +1,16 @@
+<BOUCLE_gis(GIS){id_gis=#ENV{id}}>
+<div class='infos'>
+       <div class='numero'><:gis:info_numero_gis:><p>#ID_GIS</p></div>
+
+       [(#INCLURE{fond=prive/objets/liste/objets_gis_simple,id_gis=#ID_GIS,env})]
+
+       [(#AUTORISER{supprimer,gis,#ID_GIS}|oui)
+               [(#URL_ACTION_AUTEUR{
+                       supprimer_gis,
+                       #ID_GIS,
+                       #URL_ECRIRE{gis_tous}
+                       }|icone_horizontale{<:gis:bouton_supprimer_gis:>,gis-24,del}
+               )]
+       ]
+</div>
+</BOUCLE_gis>
diff --git a/www/plugins/gis/prive/objets/liste/gis.html b/www/plugins/gis/prive/objets/liste/gis.html
new file mode 100644 (file)
index 0000000..a4f6e6c
--- /dev/null
@@ -0,0 +1,40 @@
+[(#SET{defaut_tri,#ARRAY{
+       multi titre,1,
+       multi pays,1,
+       multi ville,1,
+       id_gis,1,
+       points,-1
+}})
+]
+<B_liste_gis>
+#ANCRE_PAGINATION
+<div class="liste-objets gis">
+<table class='spip liste'>
+[<caption><strong class="caption">(#ENV*{titre,#GRAND_TOTAL|singulier_ou_pluriel{gis:info_1_gis,gis:info_nb_gis}})</strong></caption>]
+       <thead>
+               <tr class='first_row'>
+                       <th class='titre' scope='col'>[(#TRI{multi titre,<:info_titre:>,ajax})]</th>
+                       <th class='pays' scope='col'>[(#TRI{multi pays,<:gis:label_pays:>,ajax})]</th>
+                       <th class='ville' scope='col'>[(#TRI{multi ville,<:gis:label_ville:>,ajax})]</th>
+                       <th class='id_gis' scope='col'>[(#TRI{id_gis,<:info_numero_abbreviation:>,ajax})]</th>
+               </tr>
+       </thead>
+       <tbody>
+       <BOUCLE_liste_gis(GIS){id_gis ?}{id_rubrique ?}{id_article ?}{id_breve ?}{id_document ?}{id_mot ?}{id_auteur ?}{id_syndic ?}{where?}{recherche?}{tri #ENV{par,multi titre},#GET{defaut_tri}}{pagination #ENV{nb,10}}>
+               <tr class="[(#COMPTEUR_BOUCLE|alterner{row_odd,row_even})][(#ID_GIS|=={#ENV{id_lien_ajoute}}|oui)append]">
+                       <td class='titre principale'>[(#LOGO_GIS|image_reduire{20,20})]<a href="[(#ID_GIS|generer_url_entite{gis})]"
+                               title="<:gis:texte_voir_gis:>">[(#TITRE|sinon{<:ecrire:info_sans_titre:>})]</a></td>
+                       <td class='pays'>#PAYS</td>
+                       <td class='ville'>#VILLE</td>
+                       <td class='id_gis'><a href="[(#ID_GIS|generer_url_entite{gis})]"
+                               title="<:gis:texte_voir_gis:>">#ID_GIS</a></td>
+               </tr>
+       </BOUCLE_liste_gis>
+       </tbody>
+</table>
+[<p class='pagination'>(#PAGINATION{prive})</p>]
+</div>
+</B_liste_gis>
+[
+<div class="liste-objets gis caption-wrap"><strong class="caption">(#ENV*{sinon,''})</strong></div>
+]<//B_liste_gis>
\ No newline at end of file
diff --git a/www/plugins/gis/prive/objets/liste/gis_associer.html b/www/plugins/gis/prive/objets/liste/gis_associer.html
new file mode 100644 (file)
index 0000000..1770b5e
--- /dev/null
@@ -0,0 +1,55 @@
+[(#SET{defaut_tri,#ARRAY{
+       multi titre,1,
+}})
+]
+#SET{exclus,#ENV{objet_source}|lister_objets_lies{#ENV{objet},#ENV{id_objet},#ENV{_objet_lien}}}
+#SET{debut,#ENV{debutgisa,#EVAL{_request("debutgisa");}}}
+<input type="hidden" name="debutgisa" value="#GET{debut}" />
+<B_liste_gis>
+[(#REM) En cas de pagination indirecte @32, il faut refaire le set car la boucle
+a mis a jour la valeur avec la page reelle]
+#SET{debut,#ENV{debutgisa,#EVAL{_request("debutgisa");}}}
+#ANCRE_PAGINATION
+[<h3><:info_resultat_recherche:> &laquo;(#ENV{recherche})&raquo;</h3>]
+<div class="liste-objets liste-objets-associer gis">
+<table class='spip liste'>
+[<caption><span class="caption"><strong>(#ENV*{titre,#GRAND_TOTAL|singulier_ou_pluriel{gis:info_1_gis,gis:info_nb_gis}})</strong>
+<span class="recherche">
+       <label for="recherche-#ENV{id}"><:info_rechercher_02:></label>
+       <input type="text" class="recherche" name="recherche" id="recherche-#ENV{id}" value="#ENV{recherche}" />
+       <input type="submit" class="tout_voir" name="tout_voir" value="<:info_tout_afficher:>" />
+</span>
+</span>
+</caption>]
+       <thead>
+               <tr class='first_row'>
+            <th class='picto' scope='col'></th>
+            <th class='titre' scope='col'>[(#TRI{multi titre,<:info_titre:>,ajax})]</th>
+            <th class='action' scope='col'>&nbsp;</th>
+               </tr>
+       </thead>
+       <tbody>
+       <BOUCLE_liste_gis(GIS){!id_gis IN #GET{exclus}}{tout}{where?}{recherche?}{tri #ENV{par,multi titre},#GET{defaut_tri}}{pagination #ENV{nb,5} gisa}>
+               <tr class="[(#COMPTEUR_BOUCLE|alterner{row_odd,row_even})]">
+                       <td class='picto'>[(#CHEMIN_IMAGE{gis-16.png}|balise_img)]</td>
+                       <td class='titre principale'>[(#LOGO_GIS|image_reduire{20,20})]<a href="[(#ID_GIS|generer_url_entite{gis})]"
+                               title="<:gis:texte_voir_gis:>">[(#TITRE|sinon{<:ecrire:info_sans_titre:>})]</a></td>
+                       <td class='action'><button type="submit" class="button" name="ajouter_lien[gis-#ID_GIS-#OBJET-#ID_OBJET]" value="+"><:gis:lien_ajouter_gis:> [(#CHEMIN_IMAGE{ajouter-12.png}|balise_img{'+'})]</button></td>
+               </tr>
+       </BOUCLE_liste_gis>
+       </tbody>
+</table>
+[<p class='pagination'>(#PAGINATION{prive})</p>]
+</div>
+</B_liste_gis>
+[(#ENV{recherche}|oui)
+<div class="liste-objets liste-objets-associer gis caption-wrap">
+<span class="caption"><strong>[(#VAL{gis:info_recherche_gis_zero}|_T{#ARRAY{cherche_gis,#ENV{recherche}}})]</strong>
+<span class="recherche">
+       <label for="recherche-#ENV{id}"><:info_rechercher_02:></label>
+       <input type="text" class="recherche" name="recherche" id="recherche-#ENV{id}" value="#ENV{recherche}" />
+       <input type="submit" class="tout_voir" name="tout_voir" value="<:info_tout_afficher:>" />
+</span>
+</span>
+</div>
+]<//B_liste_gis>
diff --git a/www/plugins/gis/prive/objets/liste/gis_associer_fonctions.php b/www/plugins/gis/prive/objets/liste/gis_associer_fonctions.php
new file mode 100644 (file)
index 0000000..891b756
--- /dev/null
@@ -0,0 +1,3 @@
+<?php
+include_spip('inc/filtres_ecrire');
+?>
\ No newline at end of file
diff --git a/www/plugins/gis/prive/objets/liste/gis_lies.html b/www/plugins/gis/prive/objets/liste/gis_lies.html
new file mode 100644 (file)
index 0000000..d4950cf
--- /dev/null
@@ -0,0 +1,44 @@
+[(#SET{defaut_tri,#ARRAY{
+       multi titre,1,
+       id_gis,1
+}})
+]
+#SET{selection,#VAL{gis}|lister_objets_lies{#ENV{objet},#ENV{id_objet},#ENV{_objet_lien}}}
+<input type="hidden" name="debutgisl" value='#ENV{debutgisl,#EVAL{_request("debutgisl");}}' />
+<B_liste_gis>
+#ANCRE_PAGINATION
+<div class="liste-objets liste-objets-lies gis">
+<table class='spip liste'>
+[<caption><strong class="caption">(#ENV*{titre,#GRAND_TOTAL|singulier_ou_pluriel{gis:info_1_gis,gis:info_nb_gis}})</strong></caption>]
+       <thead>
+               <tr class='first_row'>
+                       <th class='picto' scope='col'></th>
+                       <th class='titre' scope='col'>[(#TRI{multi titre,<:info_titre:>,ajax})]</th>
+                       <th class='action' scope='col'>&nbsp;</th>
+               </tr>
+       </thead>
+       <tbody>
+       <BOUCLE_liste_gis(GIS){id_gis IN #GET{selection}}{where?}{tri #ENV{par,multi titre},#GET{defaut_tri}}{pagination #ENV{nb,10} gisl}{!lang_select}>
+               <tr class="[(#COMPTEUR_BOUCLE|alterner{row_odd,row_even})][(#ID_GIS|=={#ENV{id_lien_ajoute}}|oui)append]">
+                       <td class='picto'>[(#CHEMIN_IMAGE{gis-16.png}|balise_img)]</td>
+                       <td class='titre principale'>[(#LOGO_GIS|image_reduire{20,20})]<a href="[(#ID_GIS|generer_url_entite{gis})]"
+                               title="<:gis:texte_voir_gis:>">[(#TITRE|sinon{<:ecrire:info_sans_titre:>})]</a></td>
+                       <td class='action'>
+                [(#BOUTON_ACTION{
+                    <:gis:info_supprimer_lien:>  [(#CHEMIN_IMAGE{supprimer-12.png}|balise_img{'X'})],
+                    #URL_ACTION_AUTEUR{editer_lien_gis,delier/#ID_GIS/#OBJET/#ID_OBJET,#SELF},
+                    ajax})]
+                       </td>
+               </tr>
+       </BOUCLE_liste_gis>
+       </tbody>
+</table>
+[<p class='pagination'>(#PAGINATION{prive})</p>]
+[(#GRAND_TOTAL|>{3}|oui)<div class="action"><button type="submit" class="button link" name="supprimer_lien#EVAL{chr(91)}gis-*-#OBJET-#ID_OBJET#EVAL{chr(93)}" value="X"><:gis:info_supprimer_liens:> [(#CHEMIN_IMAGE{supprimer-12.png}|balise_img{'X'})]</button></div>]
+[(#INCLURE{fond=modeles/carte_gis_preview,id_objet,objet})]
+</div>
+</B_liste_gis>
+<div class="liste-objets liste-objets-lies gis caption-wrap">
+<strong class="caption">[(#ENV*{titre,<:gis:info_aucun_gis:>}) ]</strong>
+</div>
+<//B_liste_gis>
diff --git a/www/plugins/gis/prive/objets/liste/gis_lies_fonctions.php b/www/plugins/gis/prive/objets/liste/gis_lies_fonctions.php
new file mode 100644 (file)
index 0000000..891b756
--- /dev/null
@@ -0,0 +1,3 @@
+<?php
+include_spip('inc/filtres_ecrire');
+?>
\ No newline at end of file
diff --git a/www/plugins/gis/prive/objets/liste/objets_gis.html b/www/plugins/gis/prive/objets/liste/objets_gis.html
new file mode 100644 (file)
index 0000000..d0a22cb
--- /dev/null
@@ -0,0 +1,41 @@
+[(#SET{defaut_tri,#ARRAY{
+       objet,#ENV{objet_sens,-1},
+       id_objet,1
+}})
+]<B_liste_objets>
+#ANCRE_PAGINATION
+<div class="liste-objets objets_gis">
+<table class='spip liste'>
+[<caption><strong class="caption">(#ENV*{titre,#GRAND_TOTAL|singulier_ou_pluriel{gis:info_1_objet_gis,gis:info_nb_objets_gis}})</strong></caption>]
+       <thead>
+               <tr class='first_row'>
+            <th class='picto' scope='col'></th>
+                       <th class='objet' scope='col'>[(#TRI{objet,<:gis:info_objet:>,ajax})]</th>
+                       <th class='id_objet' scope='col'>[(#TRI{id_objet,<:gis:info_id_objet:>,ajax})]</th>
+                       <th class='titre principale' scope='col'><:gis:titre_objet:></th>
+                       <th></th>
+               </tr>
+       </thead>
+       <tbody>
+       <BOUCLE_liste_objets(GIS gis_liens){id_gis}{tri #ENV{order,objet},#GET{defaut_tri}}{pagination #ENV{nb,10}}{!lang_select}>
+               <tr class="[(#COMPTEUR_BOUCLE|alterner{row_odd,row_even})]">
+                       <td class='picto'>[(#OBJET|objet_icone{16})]</td>
+                       <td class='objet'>[(#OBJET|objet_info{texte_objet}|_T)]</td>
+                       <td class='id_objet'>[(#ID_OBJET)]</td>
+            <td class='titre principale'><a href="[(#ID_OBJET|generer_url_entite{#OBJET})]"
+                               title="<:gis:info_voir_fiche_objet:> <:info_numero_abbreviation|attribut_html:> #ID_OBJET">#INFO_TITRE{#OBJET,#ID_OBJET}</a></td>
+                       <td class='action'>
+                               [(#BOUTON_ACTION{
+                                       <:gis:info_supprimer_lien:>  [(#CHEMIN_IMAGE{supprimer-12.png}|balise_img{'X'})],
+                                       #URL_ACTION_AUTEUR{editer_lien_gis,delier/#ID_GIS/#OBJET/#ID_OBJET,#SELF},
+                                       ajax})]
+                       </td>
+               </tr>
+       </BOUCLE_liste_objets>
+       </tbody>
+</table>
+[<p class='pagination'>(#PAGINATION{prive})</p>]
+</div>
+</B_liste_objets>[
+<div class="liste-elements objets_gis"><strong class="caption">(#ENV*{sinon,''})</strong></div>
+]<//B_liste_objets>
diff --git a/www/plugins/gis/prive/objets/liste/objets_gis_simple.html b/www/plugins/gis/prive/objets/liste/objets_gis_simple.html
new file mode 100644 (file)
index 0000000..688e540
--- /dev/null
@@ -0,0 +1,16 @@
+<B_liste_objets>
+
+#ANCRE_PAGINATION
+<div class="liste-elements objets_gis">
+#SET{total,#GRAND_TOTAL}
+<h3>[(#GET{total}|singulier_ou_pluriel{gis:info_1_objet_gis,gis:info_nb_objets_gis})]</h3>
+<ul class="liste_items">
+    <BOUCLE_liste_objets(GIS gis_liens){id_gis}{tri #ENV{order,objet},#GET{defaut_tri}}{pagination #ENV{nb,10}}{!lang_select}>
+        <li class="item #OBJET">
+            <a href="[(#ID_OBJET|generer_url_entite{#OBJET})]">[(#INFO_TITRE{#OBJET,#ID_OBJET}) ]([(#OBJET|objet_info{texte_objet}|_T)] <:info_numero_abbreviation|attribut_html:>#ID_OBJET)</a>
+        </li>
+    </BOUCLE_liste_objets>
+</ul>
+[<p class='pagination'>(#PAGINATION{prive})</p>]
+</div>
+</B_liste_objets>
diff --git a/www/plugins/gis/prive/squelettes/contenu/configurer_gis.html b/www/plugins/gis/prive/squelettes/contenu/configurer_gis.html
new file mode 100644 (file)
index 0000000..7c3b5c2
--- /dev/null
@@ -0,0 +1,6 @@
+[(#AUTORISER{configurer,gis}|sinon_interdire_acces)]
+<h1 class="grostitre"><:gis:cfg_titre_gis:></h1>
+
+<div class="ajax">
+#FORMULAIRE_CONFIGURER_GIS
+</div>
diff --git a/www/plugins/gis/prive/squelettes/contenu/gis.html b/www/plugins/gis/prive/squelettes/contenu/gis.html
new file mode 100644 (file)
index 0000000..101a4a1
--- /dev/null
@@ -0,0 +1,27 @@
+<BOUCLE_gis(GIS){id_gis}{tout}{si #ENV{exec}|=={gis}}>
+[(#BOITE_OUVRIR{[
+       [(#AUTORISER{modifier,gis,#ID_GIS})
+       [(#URL_ECRIRE{gis_edit,id_gis=#ID_GIS}|icone_verticale{<:gis:texte_modifier_gis:>,gis,edit,right ajax preload})]
+       ]
+       <h1>(#TITRE|sinon{<:info_sans_titre:>})[(#CHEMIN_IMAGE{gis-24.png}|balise_img{gis,cadre-icone})]</h1>
+],simple fiche_objet})]
+
+[(#INCLURE{fond=modeles/carte_gis_preview,id_gis})]
+
+<div id="wysiwyg">
+<INCLURE{fond=prive/objets/contenu/gis,id=#ID_GIS,id_gis=#ID_GIS} />
+</div>
+
+<INCLURE{fond=prive/objets/liste/objets_gis,sinon=<:gis:aucun_objet:>,env,ajax} />
+
+#BOITE_FERMER
+
+#PIPELINE{afficher_complement_objet,#ARRAY{args,#ARRAY{type,gis,id,#ID_GIS},data,'<div class="nettoyeur"></div>'}}
+
+[(#EVAL{_AJAX}|oui)
+<script type="text/javascript">/*<!\[CDATA\[*/reloadExecPage('#ENV{exec}','#navigation,#chemin');/*\]\]>*/</script>
+]
+
+</BOUCLE_gis>
+[(#ENV**{exec}|=={gis_edit}|?{#INCLURE{fond=prive/squelettes/contenu/gis_edit,redirect='',env,retourajax=oui},#REM|sinon_interdire_acces})]
+<//B_gis>
diff --git a/www/plugins/gis/prive/squelettes/contenu/gis_edit.html b/www/plugins/gis/prive/squelettes/contenu/gis_edit.html
new file mode 100644 (file)
index 0000000..ca63efe
--- /dev/null
@@ -0,0 +1,36 @@
+[(#ID_GIS|oui)
+       [(#AUTORISER{modifier,gis,#ID_GIS}|sinon_interdire_acces)]
+][(#ID_GIS|non)
+       [(#AUTORISER{creer,gis,#ID_GIS,'','',#ARRAY{associer_objet,#ENV{associer_objet}}}|sinon_interdire_acces)]
+]
+#SET{redirect,#ENV{redirect,#ENV{id_gis}|?{#URL_ECRIRE{gis,id_gis=#ID_GIS},#URL_ECRIRE{gis_tous}}}}
+
+#SET{objet, #ENV{objet, ''}}
+#SET{id_objet, #ENV{id_objet, 0}}
+[(#ENV{associer_objet}|oui)
+       [(#SET{array_objet, #ENV{associer_objet}|explode{'|'}})]
+       #SET{objet, #GET{array_objet}|table_valeur{0}}
+       #SET{id_objet, #GET{array_objet}|table_valeur{1}}
+]
+<div class='cadre-formulaire-editer'>
+<div class="entete-formulaire">
+       [(#ID_GIS|oui)
+       [(#GET{redirect}|icone_verticale{<:icone_retour:>,gis,'',left retour[(#ENV{retourajax,''}|oui)ajax preload]})]
+       ]
+       [
+               [(#ID_GIS|?{<:gis:texte_modifier_gis:>,<:gis:texte_creer_gis:>})]
+               <h1>(#ENV{titre,#INFO_TITRE{gis,#ID_GIS}|sinon{<:gis:titre_nouveau_point:>}})</h1>
+       ]
+</div>
+
+#SET{redirect,#ENV{redirect,#ID_GIS|generer_url_entite{gis}}}
+[(#ENV{retourajax,''}|oui)
+       #SET{redirect,'javascript:if (window.jQuery) jQuery(".entete-formulaire .retour a").followLink();'}
+       <div class="ajax">
+]
+               [(#FORMULAIRE_EDITER_GIS{#ENV{id_gis,oui},#GET{objet},#GET{id_objet},#GET{redirect},'',#ENV{associer_objet}})]
+[(#ENV{retourajax,''}|oui)
+       </div>
+       <script type="text/javascript">/*<!\[CDATA\[*/reloadExecPage('#ENV{exec}','#navigation,#chemin');/*\]\]>*/</script>
+]
+</div>
\ No newline at end of file
diff --git a/www/plugins/gis/prive/squelettes/contenu/gis_tous.html b/www/plugins/gis/prive/squelettes/contenu/gis_tous.html
new file mode 100644 (file)
index 0000000..6a88b72
--- /dev/null
@@ -0,0 +1,19 @@
+<div class="onglets_simple clearfix">
+       <ul>
+               [<li>(#URL_ECRIRE{gis_tous}|parametre_url{afficher,carte}|lien_ou_expose{Carte,#ENV{afficher,carte}|=={carte},ajax})</li>]
+               [<li>(#URL_ECRIRE{gis_tous}|parametre_url{afficher,liste}|lien_ou_expose{Liste,#ENV{afficher,carte}|=={liste},ajax})</li>]
+       </ul>
+       [(#FORMULAIRE_RECHERCHE_ECRIRE{#SELF,ajax})]
+</div>
+
+[(#ENV{afficher,carte}|=={carte}|oui)
+       [(#INCLURE{fond=modeles/carte_gis,
+               id_carte_gis=_all,
+               objets=tous_avec_liens_espace_prive,
+               recherche}
+       )]
+]
+
+[(#ENV{afficher,carte}|=={liste}|oui)
+       [(#INCLURE{fond=prive/objets/liste/gis,env})]
+]
\ No newline at end of file
diff --git a/www/plugins/gis/prive/squelettes/extra/gis.html b/www/plugins/gis/prive/squelettes/extra/gis.html
new file mode 100644 (file)
index 0000000..74dad10
--- /dev/null
@@ -0,0 +1,4 @@
+#BOITE_OUVRIR{'','info'}
+[(#URL_PAGE{gis_download}|parametre_url{id_gis,#ENV{id_gis}}|parametre_url{format,kml}|icone_horizontale{<:gis:telecharger_gis{format=KML}:>,telecharger-16})]
+[(#URL_PAGE{gis_download}|parametre_url{id_gis,#ENV{id_gis}}|parametre_url{format,gpx}|icone_horizontale{<:gis:telecharger_gis{format=GPX}:>,telecharger-16})]
+#BOITE_FERMER
\ No newline at end of file
diff --git a/www/plugins/gis/prive/squelettes/hierarchie/gis.html b/www/plugins/gis/prive/squelettes/hierarchie/gis.html
new file mode 100644 (file)
index 0000000..ddd24d5
--- /dev/null
@@ -0,0 +1,8 @@
+<a href="#URL_ECRIRE{gis_tous}">
+       <:gis:gis_pluriel:>
+</a> &gt;
+<BOUCLE_point(GIS){id_gis}{0,1}>
+       <strong class="on">#TITRE</strong>
+</BOUCLE_point>
+       <strong class="on"><:gis:titre_nouveau_point:></strong>
+<//B_point>
diff --git a/www/plugins/gis/prive/squelettes/hierarchie/gis_edit.html b/www/plugins/gis/prive/squelettes/hierarchie/gis_edit.html
new file mode 100644 (file)
index 0000000..f17f81d
--- /dev/null
@@ -0,0 +1 @@
+<INCLURE{fond=prive/squelettes/hierarchie/gis,env} />
\ No newline at end of file
diff --git a/www/plugins/gis/prive/squelettes/navigation/gis_edit.html b/www/plugins/gis/prive/squelettes/navigation/gis_edit.html
new file mode 100644 (file)
index 0000000..91a9d56
--- /dev/null
@@ -0,0 +1,9 @@
+[(#ID_GIS|oui)
+#BOITE_OUVRIR{'','info'}
+#PIPELINE{boite_infos,#ARRAY{data,'',args,#ARRAY{'type','gis','id',#ID_GIS}}}
+#BOITE_FERMER
+
+<div class="ajax">
+#FORMULAIRE_EDITER_LOGO{'gis',#ID_GIS,'',#ENV**}
+</div>
+]
diff --git a/www/plugins/gis/prive/squelettes/top/gis_tous.html b/www/plugins/gis/prive/squelettes/top/gis_tous.html
new file mode 100644 (file)
index 0000000..b210f49
--- /dev/null
@@ -0,0 +1,2 @@
+<h1 class="invisible"><:gis:editer_gis_titre:></h1>
+#LARGEUR_ECRAN{pleine_largeur}
\ No newline at end of file
diff --git a/www/plugins/gis/prive/style_prive_plugin_gis.html b/www/plugins/gis/prive/style_prive_plugin_gis.html
new file mode 100644 (file)
index 0000000..eb7ea22
--- /dev/null
@@ -0,0 +1,12 @@
+#CACHE{3600*100,cache-client}
+#HTTP_HEADER{Content-Type: text/css; charset=iso-8859-15}
+#HTTP_HEADER{Vary: Accept-Encoding}
+
+#SET{claire,##ENV{couleur_claire,edf3fe}}
+#SET{foncee,##ENV{couleur_foncee,3874b0}}
+#SET{left,#ENV{ltr}|choixsiegal{left,left,right}}
+#SET{right,#ENV{ltr}|choixsiegal{left,right,left}}
+
+/* liste des points exec=gis_tous */
+
+.gis_tous .onglets_simple .formulaire_recherche { margin-bottom: 0; }
diff --git a/www/plugins/gis/prive/themes/spip/images/gis-16.png b/www/plugins/gis/prive/themes/spip/images/gis-16.png
new file mode 100644 (file)
index 0000000..b0e5a4c
Binary files /dev/null and b/www/plugins/gis/prive/themes/spip/images/gis-16.png differ
diff --git a/www/plugins/gis/prive/themes/spip/images/gis-24.png b/www/plugins/gis/prive/themes/spip/images/gis-24.png
new file mode 100755 (executable)
index 0000000..6cb9f54
Binary files /dev/null and b/www/plugins/gis/prive/themes/spip/images/gis-24.png differ
diff --git a/www/plugins/gis/prive/themes/spip/images/gis-new-16.png b/www/plugins/gis/prive/themes/spip/images/gis-new-16.png
new file mode 100644 (file)
index 0000000..e84098a
Binary files /dev/null and b/www/plugins/gis/prive/themes/spip/images/gis-new-16.png differ
diff --git a/www/plugins/gis/saisies/carte.html b/www/plugins/gis/saisies/carte.html
new file mode 100644 (file)
index 0000000..a58fbdf
--- /dev/null
@@ -0,0 +1,262 @@
+[(#REM)
+
+       Saisie carte
+
+       Parametres optionnels:
+
+       - lat = 48.3                    latitude du centre de la carte
+       - lon = -4.7                    longitude du centre de la carte
+       - zoom = 5                      zoom de la carte
+       - sw_lat = lat - 10°            latitude du sud-ouest de la bounding box
+       - sw_lon = lon - 10°            longitude du sud-ouest de la bounding box
+       - ne_lat = lat + 10°            latitude du nord-est de la bounding box
+       - ne_lon = lon + 10°            longitude du nord-est de la bounding box
+       - largeur = 100%
+       - hauteur = 350px
+
+]
+
+[(#SET{init_lat,#ENV{lat,#CONFIG{gis/lat,0}}})]
+[(#SET{init_lon,#ENV{lon,#CONFIG{gis/lon,0}}})]
+[(#SET{init_zoom,#ENV{zoom,#CONFIG{gis/zoom,0}}})]
+[(#REM) On utilise la bounding box seulement si le centre n'a pas été donné et si les quatre valeurs de la bounding box sont renseignées
+        Les valeurs par defaut sont "centre +/- 10°", ce qui est naze, mais c'est un cas normalement impossible
+]
+[(#ENV{lat}|ou{#ENV{lon}}|non|et{#ENV{sw_lat}}|et{#ENV{sw_lon}}|et{#ENV{ne_lat}}|et{#ENV{ne_lon}})
+       #SET{utiliser_bb, oui}
+       #SET{init_sw_lat,#ENV{sw_lat,#GET{lat}|moins{10}}}
+       #SET{init_sw_lon,#ENV{sw_lon,#GET{lon}|moins{10}}}
+       #SET{init_ne_lat,#ENV{ne_lat,#GET{lat}|plus{10}}}
+       #SET{init_ne_lon,#ENV{ne_lon,#GET{lon}|plus{10}}}
+]
+
+<li class="pleine_largeur editer editer_[(#ENV{nom})][ (#ENV{li_class})][ saisie_(#ENV{type_saisie})]"[ data-id="(#ENV{id_saisie})"]>
+#ENV*{inserer_debut}
+<div id="map_[(#ENV{nom})]" name="formMap" class="formMap" style="width: #ENV{largeur,100%}; height: #ENV{hauteur,350px}"></div>
+<script type="text/javascript">
+/*<![CDATA[*/
+var form_map,
+       annuler_geocoder = 0;
+[(#ENV{recherche}|!={non}|oui|et{#CONFIG{gis/geocoder}|oui})
+[(#SET{geocoder,oui})]
+var geocoder;]
+
+(function($){
+       var champ_lat = $('#champ_#ENV{champ_lat,lat}'),
+               champ_lon = $('#champ_#ENV{champ_lon,lon}'),
+               champ_zoom = $('#champ_#ENV{champ_zoom,zoom}'),
+               champ_adresse = $('#champ_#ENV{champ_adresse,adresse}'),
+               champ_code_postal = $('##ENV{champ_code_postal,code_postal}'),
+               champ_ville = $('#champ_#ENV{champ_ville,ville}'),
+               champ_departement = $('#champ_#ENV{champ_departement,departement}'),
+               champ_region = $('#champ_#ENV{champ_region,region}'),
+               champ_pays = $('#champ_#ENV{champ_pays,pays}'),
+               champ_code_pays = $('#champ_#ENV{champ_code_pays,code_pays}'),
+               marker;
+
+       var maj_inputs = function(map,data,action) {
+               [(#GET{geocoder}|oui)
+               if (action != 'geocoding') {
+                       var f = geocoder.geocode(data);
+               }]
+               var zoom = map.getZoom();
+               if(data.lng <= -180) data.lng = data.lng+360
+               else if(data.lng > 180) data.lng = data.lng-360;
+               $('#champ_#ENV{champ_zoom,zoom}').val(zoom);
+               if(action == 'click'){
+                       $('#champ_#ENV{champ_lat,lat}').val(data.lat);
+                       $('#champ_#ENV{champ_lon,lon}').val(data.lng); 
+                       annuler_geocoder = 1;
+                       form_map.panTo(data);
+                       marker.setLatLng(data);
+               }
+               else if(annuler_geocoder != 1){
+                       if(data.point == 'undefined'){
+                               $('#champ_#ENV{champ_lat,lat}').val(data.lat);
+                               $('#champ_#ENV{champ_lon,lon}').val(data.lng);
+                               form_map.panTo(data);
+                               marker.setLatLng(data);
+                       }else{
+                               $('#champ_#ENV{champ_lat,lat}').val(data.point.lat);
+                               $('#champ_#ENV{champ_lon,lon}').val(data.point.lng);
+                               form_map.panTo(data.point);
+                               marker.setLatLng(data.point);
+                       }
+               }
+               if (!marker._map)
+                       form_map.addLayer(marker);
+       }
+
+       [(#GET{geocoder}|oui)
+       function geocode(query) {
+               if(! query.error){
+                       $('#champ_#ENV{champ_adresse,adresse}').val(query.street);
+                       $('#champ_#ENV{champ_code_postal,code_postal}').val(query.postcode);
+                       $('#champ_#ENV{champ_ville,ville}').val(query.locality);
+                       $('#champ_#ENV{champ_departement,departement}').val(query.departement);
+                       $('#champ_#ENV{champ_region,region}').val(query.region); 
+                       $('#champ_#ENV{champ_pays,pays}').val(query.country);
+                       $('#champ_#ENV{champ_code_pays,code_pays}').val(query.country_code);
+                       maj_inputs(form_map,query,'geocoding');
+               }else{
+                       alert('<:gis:erreur_geocoder:> '+query.search);
+               }
+       }]
+       
+       var init_map = function(callback) {
+               // creer la carte
+               var map_container = 'map_[(#ENV{nom})]';
+               form_map = new L.Map(map_container);
+               
+               // affecter sur l'objet du DOM
+               jQuery("#"+map_container).get(0).map=form_map;
+
+               // appeler l'éventuelle fonction de callback
+               if (callback && typeof(callback) === "function") {
+                       form_map.on('load',function(e){
+                               callback(e.target);
+                       });
+               }
+               form_map.attributionControl.setPrefix('');
+               
+               marker = new L.Marker(new L.LatLng(#ENV{lat,0}, #ENV{lon,0}), {draggable: true});
+               
+               //default layer
+               #SET{layer_defaut,#REM|gis_layer_defaut} #SET{layers,#EVAL{$GLOBALS['gis_layers']}}
+               var [(#GET{layer_defaut})] = [new (#GET{layers}|table_valeur{#GET{layer_defaut}/layer})];
+               form_map.addLayer([(#GET{layer_defaut})]);
+               
+               <B_layers>
+               var layers_control = new L.Control.Layers();
+               layers_control.addBaseLayer([(#GET{layer_defaut})],["(#GET{layers}|table_valeur{#GET{layer_defaut}/nom})"]);
+               <BOUCLE_layers(DATA){source table, #GET{layers}}{si #ENV{control_type,#ENV{controle_type}}|!={non}|et{#ENV{no_control,#ENV{aucun_controle}}|!={oui}}|et{#CONFIG{gis/layers,#ARRAY}|count|>{1}|oui}|oui}>[
+               (#CLE|!={#GET{layer_defaut}}|oui|et{#CLE|in_array{#CONFIG{gis/layers,#ARRAY}}|oui}|oui)
+               layers_control.addBaseLayer([new (#VALEUR|table_valeur{layer})],"[(#VALEUR|table_valeur{nom})]");]
+               </BOUCLE_layers>
+               form_map.addControl(layers_control);
+               // ajouter l'objet du controle de layers à la carte pour permettre d'y accéder depuis le callback
+               form_map.layersControl = layers_control;
+               // classe noajax sur le layer_control pour éviter l'ajout de hidden par SPIP
+               $(layers_control._form).addClass('noajax');
+               </B_layers>
+               
+               [(#GET{utiliser_bb}|non)
+               form_map.setView(new L.LatLng([(#GET{init_lat})], [(#GET{init_lon})]), [(#GET{init_zoom})]); 
+               ]
+               [(#GET{utiliser_bb}|oui)
+               form_map.fitBounds(
+                       new L.LatLngBounds(
+                               new L.LatLng([(#GET{init_sw_lat})], [(#GET{init_sw_lon})]),
+                               new L.LatLng([(#GET{init_ne_lat})], [(#GET{init_ne_lon})])
+                       )
+               );
+               // mettre à jour les champs de latitude et longitude quand la carte est chargée
+               // a voir si on le fait dans tous les cas, pas seulement pour la boundingbox, comme pour le zoom
+               form_map.on('load', function(e) {
+                       $('#champ_#ENV{champ_lat,lat}').val(e.latlng.lat);
+                       $('#champ_#ENV{champ_lon,lon}').val(e.latlng.lon);
+               });             
+               ]
+               
+               [(#GET{geocoder}|oui)
+               geocoder = new L.Geocoder(geocode,{acceptLanguage:'#ENV{lang}'});]
+               
+               [(#REM) Ici on utilise #ENV{lat} et #ENV{lon}, et pas les valeurs
+                       d'initialisation #GET{init_lat} et #GET{init_lon} qui sont toujours remplies]
+               [(#ENV{lat}|et{#ENV{lon}}|oui)
+               var data = {
+                       "type": "FeatureCollection",
+                       "features": \[
+                               {"type": "Feature",
+                                       "geometry": {"type": "Point", "coordinates": \[#ENV{lon}, #ENV{lat}\]},
+                                       "id":"#ENV{id_gis,oui}",
+                                       "properties": {
+                                               "title":[(#ENV{titre,''}|supprimer_numero|json_encode)],
+                                               "description":[(#ENV{descriptif,''}|json_encode)][,(#LOGO_GIS|oui)
+                                               [(#SET{logo_doc,#LOGO_GIS|image_passe_partout{32,32}|image_recadre{32,32}})]
+                                               #SET{icon_w,#GET{logo_doc}|extraire_attribut{src}|largeur}
+                                               #SET{icon_h,#GET{logo_doc}|extraire_attribut{src}|hauteur}
+                                               ["icon": (#GET{logo_doc}|extraire_attribut{src}|url_absolue|json_encode)],
+                                               "icon_size": \[#GET{icon_w},#GET{icon_h}\],
+                                               "icon_anchor": \[[(#GET{icon_w}|div{2})],[(#GET{icon_h})]\],
+                                               "popup_anchor": \[1,[-(#GET{icon_h}|div{1.2})]\]]
+                                       }
+                               }\]
+               }
+               
+               var geojson = new L.geoJson('', {
+                       onEachFeature: function (feature, layer) {
+                               marker = layer;
+                               layer.options.draggable = true;
+                               if (feature.properties && feature.properties.icon){
+                                       layer.setIcon(new L.Icon({
+                                               iconUrl: feature.properties.icon,
+                                               iconSize: new L.Point( feature.properties.icon_size\[0\], feature.properties.icon_size\[1\] ),
+                                               iconAnchor: new L.Point( feature.properties.icon_anchor\[0\], feature.properties.icon_anchor\[1\] ),
+                                               popupAnchor: new L.Point( feature.properties.popup_anchor\[0\], feature.properties.popup_anchor\[1\] )
+                                       }));
+                               }
+                               if (feature.properties && feature.properties.title){
+                                       layer.bindPopup(feature.properties.title);
+                               }
+                       }
+               }).addTo(form_map);
+               geojson.addData(data);]
+               
+               // mettre a jour les coordonnees quand on clique la carte
+               form_map.on('click', function(e) {
+                       annuler_geocoder = 0;
+                       maj_inputs(form_map,e.latlng,'click');
+               });
+               
+               marker.on("dragend", function(e){
+                       maj_inputs(form_map,e.target._latlng,'click');
+               });
+               
+               // mettre à jour le zoom quand on le modifie
+               form_map.on('zoomend', function(e) {
+                       $('#champ_#ENV{champ_zoom,zoom}').val(e.target._zoom);
+               });
+               
+               [(#GET{geocoder}|oui)
+               // geocoder si clic...
+               $('a##ENV{nom}_rechercher_geocodage').css("cursor","pointer").click(function(){
+                       var address = $("#champ_#ENV{nom}_geocoder").val();
+                       annuler_geocoder = 0;
+                       geocoder.geocode(address);
+               });
+
+               // ne pas soumettre le formulaire si on presse Entree depuis le champ de recherche
+               $('#champ_#ENV{nom}_geocoder').keypress(function(e){
+                       if (e.which == 13) {
+                               $('a##ENV{nom}_rechercher_geocodage').trigger("click");
+                               return false;
+                       }
+               });]
+               
+               [(#ENV{id_gis}|non|ou{#ENV{id_gis}|=={oui}}|et{#CONFIG{gis/geolocaliser_user_html5}|=={on}}|oui)
+               form_map.locate({setView: true, maxZoom: [(#GET{init_zoom})]});]
+               
+       };
+
+       $(function(){
+               jQuery.getScript('[(#PRODUIRE{fond=javascript/gis.js}|compacte)]',function(){
+                       if (typeof(callback_form_map) === "function") {
+                               init_map(callback_form_map);
+                       } else {
+                               init_map();
+                       }
+               });
+       });
+       
+})(jQuery);
+/*]]>*/
+</script>
+#ENV*{inserer_fin}
+</li>
+[(#GET{geocoder}|oui)
+<li class="rechercher_adresse editer_[(#ENV{nom})]">
+       <label for="champ_#ENV{nom}_geocoder"><:gis:label_rechercher_address:></label>
+       <input type="text" class="text" name="champ_#ENV{nom}_geocoder" id="champ_#ENV{nom}_geocoder" value="" />
+       <a id="#ENV{nom}_rechercher_geocodage"><:info_rechercher:></a>
+</li>]
diff --git a/www/plugins/gis/svn.revision b/www/plugins/gis/svn.revision
new file mode 100644 (file)
index 0000000..ec1599a
--- /dev/null
@@ -0,0 +1,10 @@
+<svn_revision>
+<text_version>
+Origine: file:///home/svn/repository/spip-zone/_plugins_/gis/trunk
+Revision: 86891
+Dernier commit: 2014-12-29 10:00:02 +0100 
+</text_version>
+<origine>file:///home/svn/repository/spip-zone/_plugins_/gis/trunk</origine>
+<revision>86891</revision>
+<commit>2014-12-29 10:00:02 +0100 </commit>
+</svn_revision>
\ No newline at end of file
diff --git a/www/plugins/gis/tests/gis_connect_sql.php b/www/plugins/gis/tests/gis_connect_sql.php
new file mode 100644 (file)
index 0000000..b40ee7e
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+       // attention avant de lancer ce test !
+       // le dossier du plugin ne doit pas être un lien symbolique dans /plugins
+       // sous peine de générer un timeout...
+       
+       $test = 'gis_connect_sql';
+
+       $remonte = "../";
+       while (!is_dir($remonte."ecrire"))
+               $remonte = "../$remonte";
+       require $remonte.'tests/test.inc';
+       find_in_path("./base/connect_sql.php",'',true);
+
+
+       // Les tests
+       $essais['table_objet'] = array(
+       array('gis','gis'),
+       );
+
+       $essais['table_objet_sql'] = array(
+       array('spip_gis','gis'),
+       );
+
+       $essais['id_table_objet'] = array(
+       array('id_gis','gis'),
+       );
+
+
+       $essais['objet_type'] = array(
+       array('gis','gis'),
+       );
+
+       // hop ! on y va
+       $err = array();
+       foreach($essais as $f=>$essai)
+               $err = array_merge(tester_fun($f, $essai),$err);
+       
+       // si le tableau $err est pas vide ca va pas
+       if ($err) {
+               echo ('<dl>' . join('', $err) . '</dl>');
+       } else {
+               echo "OK";
+       }
+
+?>
\ No newline at end of file
diff --git a/www/plugins/icalendar/demo/iter_icalendar.html b/www/plugins/icalendar/demo/iter_icalendar.html
new file mode 100644 (file)
index 0000000..d3f03db
--- /dev/null
@@ -0,0 +1,31 @@
+#CACHE{0}
+
+
+#SET{today,#DATE}
+#SET{nextyear,#VAL{Y-m-d}|date{#VAL{next year}|strtotime}}
+
+<h1>Vacances scolaires</h1>
+
+<BOUCLE_zone(DATA){liste A,B,C}>
+<BOUCLE_vacances(DATA)
+{source ics, http://media.education.gouv.fr/ics/Calendrier_Scolaire_Zone_#VALEUR.ics}
+{par dtstart/str}
+{summary/value != ''}
+{dtend/str>=#GET{today}}
+{dtstart/str<=#GET{nextyear}}
+>
+[<h2>Vacances (#VALEUR{summary/value}|match{Zone.*$}|unique)</h2>]
+
+<dt>
+[(#SET{date,#VALEUR{dtstart/str}})]
+[(#GET{date}|jour) ][(#GET{date}|nom_mois)][ (#GET{date}|annee|unique{#_zone:VALEUR})]
+[(#SET{date,#VALEUR{dtend/str}})]
+[ &mdash; (#GET{date}|jour) ][(#GET{date}|nom_mois)][ (#GET{date}|annee|unique{#_zone:VALEUR})]
+</dt>
+<dd>[(#VALEUR{summary/value}|replace{- Zone.*$})]</dd>
+</BOUCLE_vacances>
+
+</BOUCLE_zone>
+
+
+<p><small>A noter : le format ical implique que DTEND est un jour apr&#232;s la fin de l'&#233;v&#232;nement&#160;; le calendrier indique donc le jour de rentrée et non le dernier jour des vacances.</small></p>
diff --git a/www/plugins/icalendar/http/ical.php b/www/plugins/icalendar/http/ical.php
new file mode 100644 (file)
index 0000000..f86aaa4
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+
+// Sécurité
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+/*
+ * Implémentation d'un serveur REST pour iCal
+ * (le vrai truc bien serait d'implémenter au moins une partie de CalDAV, mais on en est pas là)
+ */ 
+
+
+/*
+ * GET sur la racine du serveur iCal
+ * http://site/rest.api/ical
+ */
+function http_ical_get_index_dist(){
+       
+}
+
+/*
+ * GET sur une collection
+ * http://site/rest.api/ical/all
+ */
+function http_ical_get_collection_dist($collection){
+       // Pour l'instant on va simplement chercher un squelette du nom de la collection
+       // Le squelette prend en contexte les paramètres du GET uniquement
+       if ($flux = recuperer_fond("http/ical/$collection", $_GET)){
+               header('Status: 200 OK');
+               header("Content-type: text/calendar; charset=utf-8");
+               echo $flux;
+               exit;
+       }
+       // Si on ne trouve rien c'est que ça n'existe pas
+       else{
+               header('Status: 404 Not Found');
+               exit;
+       }
+}
+
+/*
+ * GET sur une ressource
+ * http://site/rest.api/ical/patates
+ */
+function http_ical_get_ressource_dist($collection, $ressource){
+       // Quelque soit la collection, tous les événements ont le même squelette
+       // Le squelette prend en contexte les paramètres du GET + l'identifiant de l'évenement en essayant de faire au mieux
+       $contexte = array(
+               'id_evenement' => $ressource,
+               'ressource' => $ressource,
+       );
+       $contexte = array_merge($_GET, $contexte);
+       
+       if ($flux = recuperer_fond("http/ical/event", $contexte)){
+               header('Status: 200 OK');
+               header("Content-type: text/calendar; charset=utf-8");
+               echo $flux;
+               exit;
+       }
+       // Si on ne trouve rien c'est que ça n'existe pas
+       else{
+               header('Status: 404 Not Found');
+               exit;
+       }
+}
+
+?>
diff --git a/www/plugins/icalendar/http/ical/inc-event.html b/www/plugins/icalendar/http/ical/inc-event.html
new file mode 100644 (file)
index 0000000..158a66b
--- /dev/null
@@ -0,0 +1,27 @@
+<BOUCLE_evenement(EVENEMENTS){id_evenement}>
+[(#REM) Mise en mémoire des infos géo si présentes ]
+<BOUCLE_geo(GIS?){id_evenement}{0,1}>
+#SET{lat,#LAT}
+#SET{lon,#LON}
+#SET{titre_gis,#TITRE|filtrer_ical}
+#SET{adresse,#ADRESSE|supprimer_tags|textebrut|filtrer_ical}
+#SET{ville,#VILLE|filtrer_ical}
+#SET{code_postal,#CODE_POSTAL|filtrer_ical}
+</BOUCLE_geo>
+BEGIN:VEVENT
+SUMMARY:[(#TITRE|textebrut|filtrer_ical)]
+UID:evenement#ID_EVENEMENT @ [(#URL_SITE_SPIP|filtrer_ical)][
+DTSTAMP:(#DATE_DEBUT|date_ical)][(#HORAIRE|=={oui}|?{[
+DTSTART:(#DATE_DEBUT|date_ical)][
+DTEND:(#DATE_FIN|date_ical)],[
+DTSTART;VALUE=DATE:(#DATE_DEBUT|affdate{Ymd})][
+DTEND;VALUE=DATE:(#DATE_FIN|agenda_jourdecal{1,Ymd})]})][(#GET{titre_gis}|oui)
+LOCATION:#GET{titre_gis}[\, (#GET{adresse})][\, [(#GET{code_postal}) ](#GET{ville})]][(#GET{titre_gis}|non)[
+LOCATION:(#LIEU|PtoBR|textebrut|filtrer_ical)]][(#GET{lat}|et{#GET{lon}}|oui)
+GEO:#GET{lat};#GET{lon}][
+DESCRIPTION:(#DESCRIPTIF|supprimer_tags|textebrut|filtrer_ical)]
+CATEGORIES:<BOUCLE_article(ARTICLES) {id_article=#ID_ARTICLE}>[(#TITRE|textebrut|filtrer_ical)]</BOUCLE_article><B_mots>\, <BOUCLE_mots(MOTS?){id_evenement=#ID_EVENEMENT}{"\, "}>[(#TITRE|textebrut|filtrer_ical)]</BOUCLE_mots>
+URL:[(#URL_EVENEMENT{#ID_EVENEMENT}|url_absolue|filtrer_ical)]
+STATUS:CONFIRMED
+END:VEVENT
+</BOUCLE_evenement>
diff --git a/www/plugins/icalendar/icalendar.png b/www/plugins/icalendar/icalendar.png
new file mode 100644 (file)
index 0000000..bc6db5b
Binary files /dev/null and b/www/plugins/icalendar/icalendar.png differ
diff --git a/www/plugins/icalendar/inc/ics_to_array.php b/www/plugins/icalendar/inc/ics_to_array.php
new file mode 100644 (file)
index 0000000..bd35dd3
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+
+
+# hors de la fonction, de facon a ce que la class soit chargee
+# meme si le resultat est deja dans le cache (sinon le cache est inexploitable).
+# cf. iterateur/data.php
+include_spip('lib/iCalcreator.class');
+
+function inc_ics_to_array($u) {
+
+       # on passe par un fichier temp car notre librairie fonctionne comme ca
+       $tmp = _DIR_TMP . 'ics-'.md5($u);
+       ecrire_fichier($tmp, str_replace("\r\n", "\n", $u));
+
+       $cal = new vcalendar();
+       $cal->setConfig( 'filename', $tmp );
+       $cal->parse();
+
+       supprimer_fichier($tmp);
+
+       $table_valeur = function_exists('Iterateurs_table_valeur')
+               ? 'Iterateurs_table_valeur' : 'table_valeur';
+
+       # noter les dates cles dans un format plus facile a recuperer
+       foreach($cal->components as $k => &$v) {
+
+               foreach(array('dtstart', 'dtend', 'dtstamp', 'lastmodified', 'created')
+               as $champ) {
+                       if (isset($v->$champ)
+                         AND $w = &$v->$champ
+                         AND $date = $table_valeur($w, "value")) {
+                               $w['str'] = date('Y-m-d H:i:s', strtotime(sprintf("%04d-%02d-%02dT%02d:%02d:%02d%s",
+                                       $date['year'],
+                                       $date['month'],
+                                       $date['day'],
+                                       $date['hour'],
+                                       $date['min'],
+                                       $date['sec'],
+                                       $date['tz']))
+                               );
+                       }
+               }
+       }
+
+       return($cal->components);
+}
+
diff --git a/www/plugins/icalendar/lang/paquet-icalendar_fr.php b/www/plugins/icalendar/lang/paquet-icalendar_fr.php
new file mode 100644 (file)
index 0000000..70b9f2a
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+
+// Ceci est un fichier langue de SPIP -- This is a SPIP language file
+
+///  Fichier produit par PlugOnet
+// Module: paquet-icalendar
+// Langue: fr
+// Date: 19-10-2012 04:50:16
+// Items: 2
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+// I
+       'icalendar_description' => 'Faire des boucles iCalendar (format ics)',
+       'icalendar_slogan' => 'Faire des boucles iCalendar (format ics)',
+);
+?>
\ No newline at end of file
diff --git a/www/plugins/icalendar/lib/iCalcreator.class.php b/www/plugins/icalendar/lib/iCalcreator.class.php
new file mode 100644 (file)
index 0000000..dbb7a54
--- /dev/null
@@ -0,0 +1,10458 @@
+<?php
+/*********************************************************************************/
+/**
+ * iCalcreator v2.16.12
+ * copyright (c) 2007-2013 Kjell-Inge Gustafsson kigkonsult
+ * kigkonsult.se/iCalcreator/index.php
+ * ical@kigkonsult.se
+ *
+ * Description:
+ * This file is a PHP implementation of rfc2445/rfc5545.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/*********************************************************************************/
+/*********************************************************************************/
+/*         A little setup                                                        */
+/*********************************************************************************/
+            /* your local language code */
+// define( 'ICAL_LANG', 'sv' );
+            // alt. autosetting
+/*
+$langstr     = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
+$pos         = strpos( $langstr, ';' );
+if ($pos   !== false) {
+  $langstr   = substr( $langstr, 0, $pos );
+  $pos       = strpos( $langstr, ',' );
+  if ($pos !== false) {
+    $pos     = strpos( $langstr, ',' );
+    $langstr = substr( $langstr, 0, $pos );
+  }
+  define( 'ICAL_LANG', $langstr );
+}
+*/
+/*********************************************************************************/
+/*         version, do NOT remove!!                                              */
+define( 'ICALCREATOR_VERSION', 'iCalcreator 2.16.12' );
+/*********************************************************************************/
+/*********************************************************************************/
+/**
+ * vcalendar class
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.9.6 - 2011-05-14
+ */
+class vcalendar {
+            //  calendar property variables
+  var $calscale;
+  var $method;
+  var $prodid;
+  var $version;
+  var $xprop;
+            //  container for calendar components
+  var $components;
+            //  component config variables
+  var $allowEmpty;
+  var $unique_id;
+  var $language;
+  var $directory;
+  var $filename;
+  var $url;
+  var $delimiter;
+  var $nl;
+  var $format;
+  var $dtzid;
+            //  component internal variables
+  var $attributeDelimiter;
+  var $valueInit;
+            //  component xCal declaration container
+  var $xcaldecl;
+/**
+ * constructor for calendar object
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.9.6 - 2011-05-14
+ * @param array $config
+ * @return void
+ */
+  function vcalendar ( $config = array()) {
+    $this->_makeVersion();
+    $this->calscale   = null;
+    $this->method     = null;
+    $this->_makeUnique_id();
+    $this->prodid     = null;
+    $this->xprop      = array();
+    $this->language   = null;
+    $this->directory  = null;
+    $this->filename   = null;
+    $this->url        = null;
+    $this->dtzid      = null;
+/**
+ *   language = <Text identifying a language, as defined in [RFC 1766]>
+ */
+    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
+                                          $config['language']   = ICAL_LANG;
+    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
+    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
+    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
+    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
+    $this->setConfig( $config );
+
+    $this->xcaldecl   = array();
+    $this->components = array();
+  }
+/*********************************************************************************/
+/**
+ * Property Name: CALSCALE
+ */
+/**
+ * creates formatted output for calendar property calscale
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.10.16 - 2011-10-28
+ * @return string
+ */
+  function createCalscale() {
+    if( empty( $this->calscale )) return FALSE;
+    switch( $this->format ) {
+      case 'xcal':
+        return $this->nl.' calscale="'.$this->calscale.'"';
+        break;
+      default:
+        return 'CALSCALE:'.$this->calscale.$this->nl;
+        break;
+    }
+  }
+/**
+ * set calendar property calscale
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-10-21
+ * @param string $value
+ * @return void
+ */
+  function setCalscale( $value ) {
+    if( empty( $value )) return FALSE;
+    $this->calscale = $value;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: METHOD
+ */
+/**
+ * creates formatted output for calendar property method
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.10.16 - 2011-10-28
+ * @return string
+ */
+  function createMethod() {
+    if( empty( $this->method )) return FALSE;
+    switch( $this->format ) {
+      case 'xcal':
+        return $this->nl.' method="'.$this->method.'"';
+        break;
+      default:
+        return 'METHOD:'.$this->method.$this->nl;
+        break;
+    }
+  }
+/**
+ * set calendar property method
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-20-23
+ * @param string $value
+ * @return bool
+ */
+  function setMethod( $value ) {
+    if( empty( $value )) return FALSE;
+    $this->method = $value;
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: PRODID
+ *
+ *  The identifier is RECOMMENDED to be the identical syntax to the
+ * [RFC 822] addr-spec. A good method to assure uniqueness is to put the
+ * domain name or a domain literal IP address of the host on which.. .
+ */
+/**
+ * creates formatted output for calendar property prodid
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.12.11 - 2012-05-13
+ * @return string
+ */
+  function createProdid() {
+    if( !isset( $this->prodid ))
+      $this->_makeProdid();
+    switch( $this->format ) {
+      case 'xcal':
+        return $this->nl.' prodid="'.$this->prodid.'"';
+        break;
+      default:
+        $toolbox = new calendarComponent();
+        $toolbox->setConfig( $this->getConfig());
+        return $toolbox->_createElement( 'PRODID', '', $this->prodid );
+        break;
+    }
+  }
+/**
+ * make default value for calendar prodid
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.6.8 - 2009-12-30
+ * @return void
+ */
+  function _makeProdid() {
+    $this->prodid  = '-//'.$this->unique_id.'//NONSGML kigkonsult.se '.ICALCREATOR_VERSION.'//'.strtoupper( $this->language );
+  }
+/**
+ * Conformance: The property MUST be specified once in an iCalendar object.
+ * Description: The vendor of the implementation SHOULD assure that this
+ * is a globally unique identifier; using some technique such as an FPI
+ * value, as defined in [ISO 9070].
+ */
+/**
+ * make default unique_id for calendar prodid
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 0.3.0 - 2006-08-10
+ * @return void
+ */
+  function _makeUnique_id() {
+    $this->unique_id  = ( isset( $_SERVER['SERVER_NAME'] )) ? gethostbyname( $_SERVER['SERVER_NAME'] ) : 'localhost';
+  }
+/*********************************************************************************/
+/**
+ * Property Name: VERSION
+ *
+ * Description: A value of "2.0" corresponds to this memo.
+ */
+/**
+ * creates formatted output for calendar property version
+
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.10.16 - 2011-10-28
+ * @return string
+ */
+  function createVersion() {
+    if( empty( $this->version ))
+      $this->_makeVersion();
+    switch( $this->format ) {
+      case 'xcal':
+        return $this->nl.' version="'.$this->version.'"';
+        break;
+      default:
+        return 'VERSION:'.$this->version.$this->nl;
+        break;
+    }
+  }
+/**
+ * set default calendar version
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 0.3.0 - 2006-08-10
+ * @return void
+ */
+  function _makeVersion() {
+    $this->version = '2.0';
+  }
+/**
+ * set calendar version
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-10-23
+ * @param string $value
+ * @return void
+ */
+  function setVersion( $value ) {
+    if( empty( $value )) return FALSE;
+    $this->version = $value;
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: x-prop
+ */
+/**
+ * creates formatted output for calendar property x-prop, iCal format only
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.2 - 2012-12-18
+ * @return string
+ */
+  function createXprop() {
+    if( empty( $this->xprop ) || !is_array( $this->xprop )) return FALSE;
+    $output = null;
+    $toolbox = new calendarComponent();
+    $toolbox->setConfig( $this->getConfig());
+    foreach( $this->xprop as $label => $xpropPart ) {
+      if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) {
+        $output  .= $toolbox->_createElement( $label );
+        continue;
+      }
+      $attributes = $toolbox->_createParams( $xpropPart['params'], array( 'LANGUAGE' ));
+      if( is_array( $xpropPart['value'] )) {
+        foreach( $xpropPart['value'] as $pix => $theXpart )
+          $xpropPart['value'][$pix] = iCalUtilityFunctions::_strrep( $theXpart, $this->format, $this->nl );
+        $xpropPart['value']  = implode( ',', $xpropPart['value'] );
+      }
+      else
+        $xpropPart['value'] = iCalUtilityFunctions::_strrep( $xpropPart['value'], $this->format, $this->nl );
+      $output    .= $toolbox->_createElement( $label, $attributes, $xpropPart['value'] );
+      if( is_array( $toolbox->xcaldecl ) && ( 0 < count( $toolbox->xcaldecl ))) {
+        foreach( $toolbox->xcaldecl as $localxcaldecl )
+          $this->xcaldecl[] = $localxcaldecl;
+      }
+    }
+    return $output;
+  }
+/**
+ * set calendar property x-prop
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.11.9 - 2012-01-16
+ * @param string $label
+ * @param string $value
+ * @param array $params optional
+ * @return bool
+ */
+  function setXprop( $label, $value, $params=FALSE ) {
+    if( empty( $label ))
+      return FALSE;
+    if( 'X-' != strtoupper( substr( $label, 0, 2 )))
+      return FALSE;
+    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    $xprop           = array( 'value' => $value );
+    $xprop['params'] = iCalUtilityFunctions::_setParams( $params );
+    if( !is_array( $this->xprop )) $this->xprop = array();
+    $this->xprop[strtoupper( $label )] = $xprop;
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * delete calendar property value
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.8.8 - 2011-03-15
+ * @param mixed $propName, bool FALSE => X-property
+ * @param int $propix, optional, if specific property is wanted in case of multiply occurences
+ * @return bool, if successfull delete
+ */
+  function deleteProperty( $propName=FALSE, $propix=FALSE ) {
+    $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
+    if( !$propix )
+      $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1;
+    $this->propdelix[$propName] = --$propix;
+    $return = FALSE;
+    switch( $propName ) {
+      case 'CALSCALE':
+        if( isset( $this->calscale )) {
+          $this->calscale = null;
+          $return = TRUE;
+        }
+        break;
+      case 'METHOD':
+        if( isset( $this->method )) {
+          $this->method   = null;
+          $return = TRUE;
+        }
+        break;
+      default:
+        $reduced = array();
+        if( $propName != 'X-PROP' ) {
+          if( !isset( $this->xprop[$propName] )) { unset( $this->propdelix[$propName] ); return FALSE; }
+          foreach( $this->xprop as $k => $a ) {
+            if(( $k != $propName ) && !empty( $a ))
+              $reduced[$k] = $a;
+          }
+        }
+        else {
+          if( count( $this->xprop ) <= $propix )  return FALSE;
+          $xpropno = 0;
+          foreach( $this->xprop as $xpropkey => $xpropvalue ) {
+            if( $propix != $xpropno )
+              $reduced[$xpropkey] = $xpropvalue;
+            $xpropno++;
+          }
+        }
+        $this->xprop = $reduced;
+        if( empty( $this->xprop )) {
+          unset( $this->propdelix[$propName] );
+          return FALSE;
+        }
+        return TRUE;
+    }
+    return $return;
+  }
+/**
+ * get calendar property value/params
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.13.4 - 2012-08-08
+ * @param string $propName, optional
+ * @param int $propix, optional, if specific property is wanted in case of multiply occurences
+ * @param bool $inclParam=FALSE
+ * @return mixed
+ */
+  function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE ) {
+    $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
+    if( 'X-PROP' == $propName ) {
+      if( !$propix )
+        $propix  = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1;
+      $this->propix[$propName] = --$propix;
+    }
+    else
+      $mProps    = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' );
+    switch( $propName ) {
+      case 'ATTENDEE':
+      case 'CATEGORIES':
+      case 'CONTACT':
+      case 'DTSTART':
+      case 'GEOLOCATION':
+      case 'LOCATION':
+      case 'ORGANIZER':
+      case 'PRIORITY':
+      case 'RESOURCES':
+      case 'STATUS':
+      case 'SUMMARY':
+      case 'RECURRENCE-ID-UID':
+      case 'RELATED-TO':
+      case 'R-UID':
+      case 'UID':
+      case 'URL':
+        $output  = array();
+        foreach ( $this->components as $cix => $component) {
+          if( !in_array( $component->objName, array('vevent', 'vtodo', 'vjournal', 'vfreebusy' )))
+            continue;
+          if( in_array( strtoupper( $propName ), $mProps )) {
+            $component->_getProperties( $propName, $output );
+            continue;
+          }
+          elseif(( 3 < strlen( $propName )) && ( 'UID' == substr( $propName, -3 ))) {
+            if( FALSE !== ( $content = $component->getProperty( 'RECURRENCE-ID' )))
+              $content = $component->getProperty( 'UID' );
+          }
+          elseif( 'GEOLOCATION' == $propName ) {
+            $content = $component->getProperty( 'LOCATION' );
+            $content = ( !empty( $content )) ? $content.' ' : '';
+            if(( FALSE === ( $geo     = $component->getProperty( 'GEO' ))) || empty( $geo ))
+              continue;
+            if( 0.0 < $geo['latitude'] )
+              $sign   = '+';
+            else
+              $sign   = ( 0.0 > $geo['latitude'] ) ? '-' : '';
+            $content .= ' '.$sign.sprintf( "%09.6f", abs( $geo['latitude'] ));
+            $content  = rtrim( rtrim( $content, '0' ), '.' );
+            if( 0.0 < $geo['longitude'] )
+              $sign   = '+';
+            else
+              $sign   = ( 0.0 > $geo['longitude'] ) ? '-' : '';
+            $content .= $sign.sprintf( '%8.6f', abs( $geo['longitude'] )).'/';
+          }
+          elseif( FALSE === ( $content = $component->getProperty( $propName )))
+            continue;
+          if(( FALSE === $content ) || empty( $content ))
+            continue;
+          elseif( is_array( $content )) {
+            if( isset( $content['year'] )) {
+              $key  = sprintf( '%04d%02d%02d', $content['year'], $content['month'], $content['day'] );
+              if( !isset( $output[$key] ))
+                $output[$key] = 1;
+              else
+                $output[$key] += 1;
+            }
+            else {
+              foreach( $content as $partValue => $partCount ) {
+                if( !isset( $output[$partValue] ))
+                  $output[$partValue] = $partCount;
+                else
+                  $output[$partValue] += $partCount;
+              }
+            }
+          } // end elseif( is_array( $content )) {
+          elseif( !isset( $output[$content] ))
+            $output[$content] = 1;
+          else
+            $output[$content] += 1;
+        } // end foreach ( $this->components as $cix => $component)
+        if( !empty( $output ))
+          ksort( $output );
+        return $output;
+        break;
+      case 'CALSCALE':
+        return ( !empty( $this->calscale )) ? $this->calscale : FALSE;
+        break;
+      case 'METHOD':
+        return ( !empty( $this->method )) ? $this->method : FALSE;
+        break;
+      case 'PRODID':
+        if( empty( $this->prodid ))
+          $this->_makeProdid();
+        return $this->prodid;
+        break;
+      case 'VERSION':
+        return ( !empty( $this->version )) ? $this->version : FALSE;
+        break;
+      default:
+        if( $propName != 'X-PROP' ) {
+          if( !isset( $this->xprop[$propName] )) return FALSE;
+          return ( $inclParam ) ? array( $propName, $this->xprop[$propName] )
+                                : array( $propName, $this->xprop[$propName]['value'] );
+        }
+        else {
+          if( empty( $this->xprop )) return FALSE;
+          $xpropno = 0;
+          foreach( $this->xprop as $xpropkey => $xpropvalue ) {
+            if( $propix == $xpropno )
+              return ( $inclParam ) ? array( $xpropkey, $this->xprop[$xpropkey] )
+                                    : array( $xpropkey, $this->xprop[$xpropkey]['value'] );
+            else
+              $xpropno++;
+          }
+          unset( $this->propix[$propName] );
+          return FALSE; // not found ??
+        }
+    }
+    return FALSE;
+  }
+/**
+ * general vcalendar property setting
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.2.13 - 2007-11-04
+ * @param mixed $args variable number of function arguments,
+ *                    first argument is ALWAYS component name,
+ *                    second ALWAYS component value!
+ * @return bool
+ */
+  function setProperty () {
+    $numargs    = func_num_args();
+    if( 1 > $numargs )
+      return FALSE;
+    $arglist    = func_get_args();
+    $arglist[0] = strtoupper( $arglist[0] );
+    switch( $arglist[0] ) {
+      case 'CALSCALE':
+        return $this->setCalscale( $arglist[1] );
+      case 'METHOD':
+        return $this->setMethod( $arglist[1] );
+      case 'VERSION':
+        return $this->setVersion( $arglist[1] );
+      default:
+        if( !isset( $arglist[1] )) $arglist[1] = null;
+        if( !isset( $arglist[2] )) $arglist[2] = null;
+        return $this->setXprop( $arglist[0], $arglist[1], $arglist[2] );
+    }
+    return FALSE;
+  }
+/*********************************************************************************/
+/**
+ * get vcalendar config values or * calendar components
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.11.7 - 2012-01-12
+ * @param mixed $config
+ * @return value
+ */
+  function getConfig( $config = FALSE ) {
+    if( !$config ) {
+      $return = array();
+      $return['ALLOWEMPTY']  = $this->getConfig( 'ALLOWEMPTY' );
+      $return['DELIMITER']   = $this->getConfig( 'DELIMITER' );
+      $return['DIRECTORY']   = $this->getConfig( 'DIRECTORY' );
+      $return['FILENAME']    = $this->getConfig( 'FILENAME' );
+      $return['DIRFILE']     = $this->getConfig( 'DIRFILE' );
+      $return['FILESIZE']    = $this->getConfig( 'FILESIZE' );
+      $return['FORMAT']      = $this->getConfig( 'FORMAT' );
+      if( FALSE !== ( $lang  = $this->getConfig( 'LANGUAGE' )))
+        $return['LANGUAGE']  = $lang;
+      $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' );
+      $return['UNIQUE_ID']   = $this->getConfig( 'UNIQUE_ID' );
+      if( FALSE !== ( $url   = $this->getConfig( 'URL' )))
+        $return['URL']       = $url;
+      $return['TZID']        = $this->getConfig( 'TZID' );
+      return $return;
+    }
+    switch( strtoupper( $config )) {
+      case 'ALLOWEMPTY':
+        return $this->allowEmpty;
+        break;
+      case 'COMPSINFO':
+        unset( $this->compix );
+        $info = array();
+        foreach( $this->components as $cix => $component ) {
+          if( empty( $component )) continue;
+          $info[$cix]['ordno'] = $cix + 1;
+          $info[$cix]['type']  = $component->objName;
+          $info[$cix]['uid']   = $component->getProperty( 'uid' );
+          $info[$cix]['props'] = $component->getConfig( 'propinfo' );
+          $info[$cix]['sub']   = $component->getConfig( 'compsinfo' );
+        }
+        return $info;
+        break;
+      case 'DELIMITER':
+        return $this->delimiter;
+        break;
+      case 'DIRECTORY':
+        if( empty( $this->directory ) && ( '0' != $this->directory ))
+          $this->directory = '.';
+        return $this->directory;
+        break;
+      case 'DIRFILE':
+        return $this->getConfig( 'directory' ).$this->getConfig( 'delimiter' ).$this->getConfig( 'filename' );
+        break;
+      case 'FILEINFO':
+        return array( $this->getConfig( 'directory' )
+                    , $this->getConfig( 'filename' )
+                    , $this->getConfig( 'filesize' ));
+        break;
+      case 'FILENAME':
+        if( empty( $this->filename ) && ( '0' != $this->filename )) {
+          if( 'xcal' == $this->format )
+            $this->filename = date( 'YmdHis' ).'.xml'; // recommended xcs.. .
+          else
+            $this->filename = date( 'YmdHis' ).'.ics';
+        }
+        return $this->filename;
+        break;
+      case 'FILESIZE':
+        $size    = 0;
+        if( empty( $this->url )) {
+          $dirfile = $this->getConfig( 'dirfile' );
+          if( !is_file( $dirfile ) || ( FALSE === ( $size = filesize( $dirfile ))))
+            $size = 0;
+          clearstatcache();
+        }
+        return $size;
+        break;
+      case 'FORMAT':
+        return ( $this->format == 'xcal' ) ? 'xCal' : 'iCal';
+        break;
+      case 'LANGUAGE':
+         /* get language for calendar component as defined in [RFC 1766] */
+        return $this->language;
+        break;
+      case 'NL':
+      case 'NEWLINECHAR':
+        return $this->nl;
+        break;
+      case 'TZID':
+        return $this->dtzid;
+        break;
+      case 'UNIQUE_ID':
+        return $this->unique_id;
+        break;
+      case 'URL':
+        if( !empty( $this->url ))
+          return $this->url;
+        else
+          return FALSE;
+        break;
+    }
+  }
+/**
+ * general vcalendar config setting
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.7 - 2013-01-11
+ * @param mixed  $config
+ * @param string $value
+ * @return void
+ */
+  function setConfig( $config, $value = FALSE) {
+    if( is_array( $config )) {
+      $ak = array_keys( $config );
+      foreach( $ak as $k ) {
+        if( 'DIRECTORY' == strtoupper( $k )) {
+          if( FALSE === $this->setConfig( 'DIRECTORY', $config[$k] ))
+            return FALSE;
+          unset( $config[$k] );
+        }
+        elseif( 'NEWLINECHAR' == strtoupper( $k )) {
+          if( FALSE === $this->setConfig( 'NEWLINECHAR', $config[$k] ))
+            return FALSE;
+          unset( $config[$k] );
+        }
+      }
+      foreach( $config as $cKey => $cValue ) {
+        if( FALSE === $this->setConfig( $cKey, $cValue ))
+          return FALSE;
+      }
+      return TRUE;
+    }
+    $res = FALSE;
+    switch( strtoupper( $config )) {
+      case 'ALLOWEMPTY':
+        $this->allowEmpty = $value;
+        $subcfg  = array( 'ALLOWEMPTY' => $value );
+        $res = TRUE;
+        break;
+      case 'DELIMITER':
+        $this->delimiter = $value;
+        return TRUE;
+        break;
+      case 'DIRECTORY':
+        $value   = trim( $value );
+        $del     = $this->getConfig('delimiter');
+        if( $del == substr( $value, ( 0 - strlen( $del ))))
+          $value = substr( $value, 0, ( strlen( $value ) - strlen( $del )));
+        if( is_dir( $value )) {
+            /* local directory */
+          clearstatcache();
+          $this->directory = $value;
+          $this->url       = null;
+          return TRUE;
+        }
+        else
+          return FALSE;
+        break;
+      case 'FILENAME':
+        $value   = trim( $value );
+        if( !empty( $this->url )) {
+            /* remote directory+file -> URL */
+          $this->filename = $value;
+          return TRUE;
+        }
+        $dirfile = $this->getConfig( 'directory' ).$this->getConfig( 'delimiter' ).$value;
+        if( file_exists( $dirfile )) {
+            /* local file exists */
+          if( is_readable( $dirfile ) || is_writable( $dirfile )) {
+            clearstatcache();
+            $this->filename = $value;
+            return TRUE;
+          }
+          else
+            return FALSE;
+        }
+        elseif( is_readable($this->getConfig( 'directory' ) ) || is_writable( $this->getConfig( 'directory' ) )) {
+            /* read- or writable directory */
+          $this->filename = $value;
+          return TRUE;
+        }
+        else
+          return FALSE;
+        break;
+      case 'FORMAT':
+        $value   = trim( strtolower( $value ));
+        if( 'xcal' == $value ) {
+          $this->format             = 'xcal';
+          $this->attributeDelimiter = $this->nl;
+          $this->valueInit          = null;
+        }
+        else {
+          $this->format             = null;
+          $this->attributeDelimiter = ';';
+          $this->valueInit          = ':';
+        }
+        $subcfg  = array( 'FORMAT' => $value );
+        $res = TRUE;
+        break;
+      case 'LANGUAGE': // set language for calendar component as defined in [RFC 1766]
+        $value   = trim( $value );
+        $this->language = $value;
+        $this->_makeProdid();
+        $subcfg  = array( 'LANGUAGE' => $value );
+        $res = TRUE;
+        break;
+      case 'NL':
+      case 'NEWLINECHAR':
+        $this->nl = $value;
+        if( 'xcal' == $value ) {
+          $this->attributeDelimiter = $this->nl;
+          $this->valueInit          = null;
+        }
+        else {
+          $this->attributeDelimiter = ';';
+          $this->valueInit          = ':';
+        }
+        $subcfg  = array( 'NL' => $value );
+        $res = TRUE;
+        break;
+      case 'TZID':
+        $this->dtzid = $value;
+        $subcfg  = array( 'TZID' => $value );
+        $res = TRUE;
+        break;
+      case 'UNIQUE_ID':
+        $value   = trim( $value );
+        $this->unique_id = $value;
+        $this->_makeProdid();
+        $subcfg  = array( 'UNIQUE_ID' => $value );
+        $res = TRUE;
+        break;
+      case 'URL':
+            /* remote file - URL */
+        $value     = str_replace( array( 'HTTP://', 'WEBCAL://', 'webcal://' ), 'http://', trim( $value ));
+        if( 'http://' != substr( $value, 0, 7 ))
+          return FALSE;
+        $s1        = $this->url;
+        $this->url = $value;
+        $s2        = $this->directory;
+        $this->directory = null;
+        $parts     = pathinfo( $value );
+        if( FALSE === $this->setConfig( 'filename',  $parts['basename'] )) {
+          $this->url       = $s1;
+          $this->directory = $s2;
+          return FALSE;
+        }
+        else
+          return TRUE;
+        break;
+      default:  // any unvalid config key.. .
+        return TRUE;
+    }
+    if( !$res ) return FALSE;
+    if( isset( $subcfg ) && !empty( $this->components )) {
+      foreach( $subcfg as $cfgkey => $cfgvalue ) {
+        foreach( $this->components as $cix => $component ) {
+          $res = $component->setConfig( $cfgkey, $cfgvalue, TRUE );
+          if( !$res )
+            break 2;
+          $this->components[$cix] = $component->copy(); // PHP4 compliant
+        }
+      }
+    }
+    return $res;
+  }
+/*********************************************************************************/
+/**
+ * add calendar component to container
+ *
+ * alias to setComponent
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 1.x.x - 2007-04-24
+ * @param object $component calendar component
+ * @return void
+ */
+  function addComponent( $component ) {
+    $this->setComponent( $component );
+  }
+/**
+ * delete calendar component from container
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.8.8 - 2011-03-15
+ * @param mixed $arg1 ordno / component type / component uid
+ * @param mixed $arg2 optional, ordno if arg1 = component type
+ * @return void
+ */
+  function deleteComponent( $arg1, $arg2=FALSE  ) {
+    $argType = $index = null;
+    if ( ctype_digit( (string) $arg1 )) {
+      $argType = 'INDEX';
+      $index   = (int) $arg1 - 1;
+    }
+    elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
+      $argType = strtolower( $arg1 );
+      $index   = ( !empty( $arg2 ) && ctype_digit( (string) $arg2 )) ? (( int ) $arg2 - 1 ) : 0;
+    }
+    $cix1dC = 0;
+    foreach ( $this->components as $cix => $component) {
+      if( empty( $component )) continue;
+      if(( 'INDEX' == $argType ) && ( $index == $cix )) {
+        unset( $this->components[$cix] );
+        return TRUE;
+      }
+      elseif( $argType == $component->objName ) {
+        if( $index == $cix1dC ) {
+          unset( $this->components[$cix] );
+          return TRUE;
+        }
+        $cix1dC++;
+      }
+      elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) {
+        unset( $this->components[$cix] );
+        return TRUE;
+      }
+    }
+    return FALSE;
+  }
+/**
+ * get calendar component from container
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.13.5 - 2012-08-08
+ * @param mixed $arg1 optional, ordno/component type/ component uid
+ * @param mixed $arg2 optional, ordno if arg1 = component type
+ * @return object
+ */
+  function getComponent( $arg1=FALSE, $arg2=FALSE ) {
+    $index = $argType = null;
+    if ( !$arg1 ) { // first or next in component chain
+      $argType = 'INDEX';
+      $index   = $this->compix['INDEX'] = ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1;
+    }
+    elseif ( ctype_digit( (string) $arg1 )) { // specific component in chain
+      $argType = 'INDEX';
+      $index   = (int) $arg1;
+      unset( $this->compix );
+    }
+    elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
+      $arg2  = implode( '-', array_keys( $arg1 ));
+      $index = $this->compix[$arg2] = ( isset( $this->compix[$arg2] )) ? $this->compix[$arg2] + 1 : 1;
+      $dateProps  = array( 'DTSTART', 'DTEND', 'DUE', 'CREATED', 'COMPLETED', 'DTSTAMP', 'LAST-MODIFIED', 'RECURRENCE-ID' );
+      $otherProps = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' );
+      $mProps     = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' );
+    }
+    elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) { // object class name
+      unset( $this->compix['INDEX'] );
+      $argType = strtolower( $arg1 );
+      if( !$arg2 )
+        $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1;
+      elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 ))
+        $index = (int) $arg2;
+    }
+    elseif(( strlen( $arg1 ) > strlen( 'vfreebusy' )) && ( FALSE !== strpos( $arg1, '@' ))) { // UID as 1st argument
+      if( !$arg2 )
+        $index = $this->compix[$arg1] = ( isset( $this->compix[$arg1] )) ? $this->compix[$arg1] + 1 : 1;
+      elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 ))
+        $index = (int) $arg2;
+    }
+    if( isset( $index ))
+      $index  -= 1;
+    $ckeys = array_keys( $this->components );
+    if( !empty( $index) && ( $index > end(  $ckeys )))
+      return FALSE;
+    $cix1gC = 0;
+    foreach ( $this->components as $cix => $component) {
+      if( empty( $component )) continue;
+      if(( 'INDEX' == $argType ) && ( $index == $cix ))
+        return $component->copy();
+      elseif( $argType == $component->objName ) {
+        if( $index == $cix1gC )
+          return $component->copy();
+        $cix1gC++;
+      }
+      elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
+        $hit = array();
+        foreach( $arg1 as $pName => $pValue ) {
+          $pName = strtoupper( $pName );
+          if( !in_array( $pName, $dateProps ) && !in_array( $pName, $otherProps ))
+            continue;
+          if( in_array( $pName, $mProps )) { // multiple occurrence
+            $propValues = array();
+            $component->_getProperties( $pName, $propValues );
+            $propValues = array_keys( $propValues );
+            $hit[] = ( in_array( $pValue, $propValues )) ? TRUE : FALSE;
+            continue;
+          } // end   if(.. .// multiple occurrence
+          if( FALSE === ( $value = $component->getProperty( $pName ))) { // single occurrence
+            $hit[] = FALSE; // missing property
+            continue;
+          }
+          if( 'SUMMARY' == $pName ) { // exists within (any case)
+            $hit[] = ( FALSE !== stripos( $value, $pValue )) ? TRUE : FALSE;
+            continue;
+          }
+          if( in_array( strtoupper( $pName ), $dateProps )) {
+            $valuedate = sprintf( '%04d%02d%02d', $value['year'], $value['month'], $value['day'] );
+            if( 8 < strlen( $pValue )) {
+              if( isset( $value['hour'] )) {
+                if( 'T' == substr( $pValue, 8, 1 ))
+                  $pValue = str_replace( 'T', '', $pValue );
+                $valuedate .= sprintf( '%02d%02d%02d', $value['hour'], $value['min'], $value['sec'] );
+              }
+              else
+                $pValue = substr( $pValue, 0, 8 );
+            }
+            $hit[] = ( $pValue == $valuedate ) ? TRUE : FALSE;
+            continue;
+          }
+          elseif( !is_array( $value ))
+            $value = array( $value );
+          foreach( $value as $part ) {
+            $part = ( FALSE !== strpos( $part, ',' )) ? explode( ',', $part ) : array( $part );
+            foreach( $part as $subPart ) {
+              if( $pValue == $subPart ) {
+                $hit[] = TRUE;
+                continue 3;
+              }
+            }
+          } // end foreach( $value as $part )
+          $hit[] = FALSE; // no hit in property
+        } // end  foreach( $arg1 as $pName => $pValue )
+        if( in_array( TRUE, $hit )) {
+          if( $index == $cix1gC )
+            return $component->copy();
+          $cix1gC++;
+        }
+      } // end elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
+      elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) { // UID
+        if( $index == $cix1gC )
+          return $component->copy();
+        $cix1gC++;
+      }
+    } // end foreach ( $this->components.. .
+            /* not found.. . */
+    unset( $this->compix );
+    return FALSE;
+  }
+/**
+ * create new calendar component, already included within calendar
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.6.33 - 2011-01-03
+ * @param string $compType component type
+ * @return object (reference)
+ */
+  function & newComponent( $compType ) {
+    $config = $this->getConfig();
+    $keys   = array_keys( $this->components );
+    $ix     = end( $keys) + 1;
+    switch( strtoupper( $compType )) {
+      case 'EVENT':
+      case 'VEVENT':
+        $this->components[$ix] = new vevent( $config );
+        break;
+      case 'TODO':
+      case 'VTODO':
+        $this->components[$ix] = new vtodo( $config );
+        break;
+      case 'JOURNAL':
+      case 'VJOURNAL':
+        $this->components[$ix] = new vjournal( $config );
+        break;
+      case 'FREEBUSY':
+      case 'VFREEBUSY':
+        $this->components[$ix] = new vfreebusy( $config );
+        break;
+      case 'TIMEZONE':
+      case 'VTIMEZONE':
+        array_unshift( $this->components, new vtimezone( $config ));
+        $ix = 0;
+        break;
+      default:
+        return FALSE;
+    }
+    return $this->components[$ix];
+  }
+/**
+ * select components from calendar on date or selectOption basis
+ *
+ * Ensure DTSTART is set for every component.
+ * No date controls occurs.
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.12 - 2013-02-10
+ * @param mixed $startY optional, start Year,  default current Year ALT. array selecOptions ( *[ <propName> => <uniqueValue> ] )
+ * @param int   $startM optional, start Month, default current Month
+ * @param int   $startD optional, start Day,   default current Day
+ * @param int   $endY   optional, end   Year,  default $startY
+ * @param int   $endY   optional, end   Month, default $startM
+ * @param int   $endY   optional, end   Day,   default $startD
+ * @param mixed $cType  optional, calendar component type(-s), default FALSE=all else string/array type(-s)
+ * @param bool  $flat   optional, FALSE (default) => output : array[Year][Month][Day][]
+ *                                TRUE            => output : array[] (ignores split)
+ * @param bool  $any    optional, TRUE (default) - select component(-s) that occurs within period
+ *                                FALSE          - only component(-s) that starts within period
+ * @param bool  $split  optional, TRUE (default) - one component copy every DAY it occurs during the
+ *                                                 period (implies flat=FALSE)
+ *                                FALSE          - one occurance of component only in output array
+ * @return array or FALSE
+ */
+  function selectComponents( $startY=FALSE, $startM=FALSE, $startD=FALSE, $endY=FALSE, $endM=FALSE, $endD=FALSE, $cType=FALSE, $flat=FALSE, $any=TRUE, $split=TRUE ) {
+            /* check  if empty calendar */
+    if( 0 >= count( $this->components )) return FALSE;
+    if( is_array( $startY ))
+      return $this->selectComponents2( $startY );
+            /* check default dates */
+    if( !$startY ) $startY = date( 'Y' );
+    if( !$startM ) $startM = date( 'm' );
+    if( !$startD ) $startD = date( 'd' );
+    $startDate = mktime( 0, 0, 0, $startM, $startD, $startY );
+    if( !$endY )   $endY   = $startY;
+    if( !$endM )   $endM   = $startM;
+    if( !$endD )   $endD   = $startD;
+    $endDate   = mktime( 23, 59, 59, $endM, $endD, $endY );
+// echo 'selectComp arg='.date( 'Y-m-d H:i:s', $startDate).' -- '.date( 'Y-m-d H:i:s', $endDate)."<br />\n"; $tcnt = 0;// test ###
+            /* check component types */
+    $validTypes = array('vevent', 'vtodo', 'vjournal', 'vfreebusy' );
+    if( is_array( $cType )) {
+      foreach( $cType as $cix => $theType ) {
+        $cType[$cix] = $theType = strtolower( $theType );
+        if( !in_array( $theType, $validTypes ))
+          $cType[$cix] = 'vevent';
+      }
+      $cType = array_unique( $cType );
+    }
+    elseif( !empty( $cType )) {
+      $cType = strtolower( $cType );
+      if( !in_array( $cType, $validTypes ))
+        $cType = array( 'vevent' );
+      else
+        $cType = array( $cType );
+    }
+    else
+      $cType = $validTypes;
+    if( 0 >= count( $cType ))
+      $cType = $validTypes;
+    if(( FALSE === $flat ) && ( FALSE === $any )) // invalid combination
+      $split = FALSE;
+    if(( TRUE === $flat ) && ( TRUE === $split )) // invalid combination
+      $split = FALSE;
+            /* iterate components */
+    $result       = array();
+    $this->sort( 'UID' );
+    $compUIDcmp   = null;
+    $recurridList = array();
+    foreach ( $this->components as $cix => $component ) {
+      if( empty( $component )) continue;
+      unset( $start );
+            /* deselect unvalid type components */
+      if( !in_array( $component->objName, $cType ))
+        continue;
+      $start = $component->getProperty( 'dtstart' );
+            /* select due when dtstart is missing */
+      if( empty( $start ) && ( $component->objName == 'vtodo' ) && ( FALSE === ( $start = $component->getProperty( 'due' ))))
+        continue;
+      if( empty( $start ))
+        continue;
+      $compUID      = $component->getProperty( 'UID' );
+      if( $compUIDcmp != $compUID ) {
+        $compUIDcmp = $compUID;
+        unset( $exdatelist, $recurridList );
+      }
+      $dtendExist = $dueExist = $durationExist = $endAllDayEvent = $recurrid = FALSE;
+      unset( $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $workstart, $workend, $endDateFormat ); // clean up
+      $startWdate = iCalUtilityFunctions::_date2timestamp( $start );
+      $startDateFormat = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
+            /* get end date from dtend/due/duration properties */
+      $end = $component->getProperty( 'dtend' );
+      if( !empty( $end )) {
+        $dtendExist = TRUE;
+        $endDateFormat = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
+      }
+      if( empty( $end ) && ( $component->objName == 'vtodo' )) {
+        $end = $component->getProperty( 'due' );
+        if( !empty( $end )) {
+          $dueExist = TRUE;
+          $endDateFormat = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
+        }
+      }
+      if( !empty( $end ) && !isset( $end['hour'] )) {
+          /* a DTEND without time part regards an event that ends the day before,
+             for an all-day event DTSTART=20071201 DTEND=20071202 (taking place 20071201!!! */
+        $endAllDayEvent = TRUE;
+        $endWdate = mktime( 23, 59, 59, $end['month'], ($end['day'] - 1), $end['year'] );
+        $end['year']  = date( 'Y', $endWdate );
+        $end['month'] = date( 'm', $endWdate );
+        $end['day']   = date( 'd', $endWdate );
+        $end['hour']  = 23;
+        $end['min']   = $end['sec'] = 59;
+      }
+      if( empty( $end )) {
+        $end = $component->getProperty( 'duration', FALSE, FALSE, TRUE );// in dtend (array) format
+        if( !empty( $end ))
+          $durationExist = TRUE;
+          $endDateFormat = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
+// if( !empty($end))  echo 'selectComp 4 start='.implode('-',$start).' end='.implode('-',$end)."<br />\n"; // test ###
+      }
+      if( empty( $end )) { // assume one day duration if missing end date
+        $end = array( 'year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59 );
+      }
+// if( isset($end))  echo 'selectComp 5 start='.implode('-',$start).' end='.implode('-',$end)."<br />\n"; // test ###
+      $endWdate = iCalUtilityFunctions::_date2timestamp( $end );
+      if( $endWdate < $startWdate ) { // MUST be after start date!!
+        $end = array( 'year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59 );
+        $endWdate = iCalUtilityFunctions::_date2timestamp( $end );
+      }
+      $rdurWsecs  = $endWdate - $startWdate; // compute event (component) duration in seconds
+            /* make a list of optional exclude dates for component occurence from exrule and exdate */
+      $exdatelist = array();
+      $workstart  = iCalUtilityFunctions::_timestamp2date(( $startDate - $rdurWsecs ), 6);
+      $workend    = iCalUtilityFunctions::_timestamp2date(( $endDate + $rdurWsecs ), 6);
+      while( FALSE !== ( $exrule = $component->getProperty( 'exrule' )))    // check exrule
+        iCalUtilityFunctions::_recur2date( $exdatelist, $exrule, $start, $workstart, $workend );
+      while( FALSE !== ( $exdate = $component->getProperty( 'exdate' ))) {  // check exdate
+        foreach( $exdate as $theExdate ) {
+          $exWdate = iCalUtilityFunctions::_date2timestamp( $theExdate );
+          $exWdate = mktime( 0, 0, 0, date( 'm', $exWdate ), date( 'd', $exWdate ), date( 'Y', $exWdate )); // on a day-basis !!!
+          if((( $startDate - $rdurWsecs ) <= $exWdate ) && ( $endDate >= $exWdate ))
+            $exdatelist[$exWdate] = TRUE;
+        } // end - foreach( $exdate as $theExdate )
+      }  // end - check exdate
+            /* check recurrence-id (note, a missing sequence=0, don't test foer sequence), remove hit with reccurr-id date */
+      if( FALSE !== ( $recurrid = $component->getProperty( 'recurrence-id' ))) {
+// echo "adding ".$recurrid['year'].'-'.$recurrid['month'].'-'.$recurrid['day']." to recurridList<br>\n"; // test ###
+        $recurrid = iCalUtilityFunctions::_date2timestamp( $recurrid );
+        $recurrid = mktime( 0, 0, 0, date( 'm', $recurrid ), date( 'd', $recurrid ), date( 'Y', $recurrid )); // on a day-basis !!!
+        $recurridList[$recurrid] = TRUE; // no recurring to start this day
+      } // end recurrence-id/sequence test
+            /* select only components with.. . */
+      if(( !$any && ( $startWdate >= $startDate ) && ( $startWdate <= $endDate )) || // (dt)start within the period
+         (  $any && ( $startWdate < $endDate ) && ( $endWdate >= $startDate ))) {    // occurs within the period
+            /* add the selected component (WITHIN valid dates) to output array */
+        if( $flat ) { // any=true/false, ignores split
+          if( !$recurrid )
+            $result[$compUID] = $component->copy(); // copy original to output (but not anyone with recurrence-id)
+        }
+        elseif( $split ) { // split the original component
+          if( $endWdate > $endDate )
+            $endWdate = $endDate;     // use period end date
+          $rstart   = $startWdate;
+          if( $rstart < $startDate )
+            $rstart = $startDate; // use period start date
+          $startYMD = $rstartYMD = date( 'Ymd', $rstart );
+          $endYMD   = date( 'Ymd', $endWdate );
+          $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
+// echo "start org comp = $rstartYMD, endYMD=$endYMD<br />\n"; // test ###
+          if( !isset( $exdatelist[$checkDate] )) { // exclude any recurrence START date, found in exdatelist
+            while( $rstartYMD <= $endYMD ) { // iterate
+              if( isset( $exdatelist[$checkDate] )) { // exclude any recurrence date, found in exdatelist
+                $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
+                $rstartYMD = date( 'Ymd', $rstart );
+                continue;
+              }
+              if( $rstartYMD > $startYMD ) // date after dtstart
+                $datestring = date( $startDateFormat, $checkDate ); // mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart )));
+              else
+                $datestring = date( $startDateFormat, $rstart );
+              if( isset( $start['tz'] ))
+                $datestring .= ' '.$start['tz'];
+// echo "split org comp rstartYMD=$rstartYMD (datestring=$datestring)<br />\n"; // test ###
+              $component->setProperty( 'X-CURRENT-DTSTART', $datestring );
+              if( $dtendExist || $dueExist || $durationExist ) {
+                if( $rstartYMD < $endYMD ) // not the last day
+                  $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ));
+                else
+                  $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
+                if( $endAllDayEvent && $dtendExist )
+                  $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
+                $datestring = date( $endDateFormat, $tend );
+                if( isset( $end['tz'] ))
+                  $datestring .= ' '.$end['tz'];
+                $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
+                $component->setProperty( $propName, $datestring );
+              } // end if( $dtendExist || $dueExist || $durationExist )
+              $wd        = getdate( $rstart );
+              $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output
+              $rstart    = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
+              $rstartYMD = date( 'Ymd', $rstart );
+              $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
+            } // end while( $rstart <= $endWdate )
+          }
+        } // end elseif( $split )   -  else use component date
+        elseif( $recurrid && !$flat && !$any && !$split )
+          $continue = TRUE;
+        else { // !$flat && !$split, i.e. no flat array and DTSTART within period
+          $checkDate = mktime( 0, 0, 0, date( 'm', $startWdate ), date( 'd', $startWdate ), date( 'Y', $startWdate ) ); // on a day-basis !!!
+          if( !$any || !isset( $exdatelist[$checkDate] )) { // exclude any recurrence date, found in exdatelist
+            $wd = getdate( $startWdate );
+            $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output
+          }
+        }
+      } // end if(( $startWdate >= $startDate ) && ( $startWdate <= $endDate ))
+            /* if 'any' components, check components with reccurrence rules, removing all excluding dates */
+      if( TRUE === $any ) {
+            /* make a list of optional repeating dates for component occurence, rrule, rdate */
+        $recurlist = array();
+        while( FALSE !== ( $rrule = $component->getProperty( 'rrule' )))    // check rrule
+          iCalUtilityFunctions::_recur2date( $recurlist, $rrule, $start, $workstart, $workend );
+        foreach( $recurlist as $recurkey => $recurvalue ) // key=match date as timestamp
+          $recurlist[$recurkey] = $rdurWsecs; // add duration in seconds
+        while( FALSE !== ( $rdate = $component->getProperty( 'rdate' ))) {  // check rdate
+          foreach( $rdate as $theRdate ) {
+            if( is_array( $theRdate ) && ( 2 == count( $theRdate )) &&  // all days within PERIOD
+                   array_key_exists( '0', $theRdate ) &&  array_key_exists( '1', $theRdate )) {
+              $rstart = iCalUtilityFunctions::_date2timestamp( $theRdate[0] );
+              if(( $rstart < ( $startDate - $rdurWsecs )) || ( $rstart > $endDate ))
+                continue;
+              if( isset( $theRdate[1]['year'] )) // date-date period
+                $rend = iCalUtilityFunctions::_date2timestamp( $theRdate[1] );
+              else {                             // date-duration period
+                $rend = iCalUtilityFunctions::_duration2date( $theRdate[0], $theRdate[1] );
+                $rend = iCalUtilityFunctions::_date2timestamp( $rend );
+              }
+              while( $rstart < $rend ) {
+                $recurlist[$rstart] = $rdurWsecs; // set start date for recurrence instance + rdate duration in seconds
+                $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
+              }
+            } // PERIOD end
+            else { // single date
+              $theRdate = iCalUtilityFunctions::_date2timestamp( $theRdate );
+              if((( $startDate - $rdurWsecs ) <= $theRdate ) && ( $endDate >= $theRdate ))
+                $recurlist[$theRdate] = $rdurWsecs; // set start date for recurrence instance + event duration in seconds
+            }
+          }
+        }  // end - check rdate
+        foreach( $recurlist as $recurkey => $durvalue ) { // remove all recurrence START dates found in the exdatelist
+          $checkDate = mktime( 0, 0, 0, date( 'm', $recurkey ), date( 'd', $recurkey ), date( 'Y', $recurkey ) ); // on a day-basis !!!
+          if( isset( $exdatelist[$checkDate] )) // no recurring to start this day
+            unset( $recurlist[$recurkey] );
+        }
+        if( 0 < count( $recurlist )) {
+          ksort( $recurlist );
+          $xRecurrence = 1;
+          $component2  = $component->copy();
+          $compUID     = $component2->getProperty( 'UID' );
+          foreach( $recurlist as $recurkey => $durvalue ) {
+// echo "recurKey=".date( 'Y-m-d H:i:s', $recurkey ).' dur='.iCalUtilityFunctions::offsetSec2His( $durvalue )."<br />\n"; // test ###;
+            if((( $startDate - $rdurWsecs ) > $recurkey ) || ( $endDate < $recurkey )) // not within period
+              continue;
+            $checkDate = mktime( 0, 0, 0, date( 'm', $recurkey ), date( 'd', $recurkey ), date( 'Y', $recurkey ) ); // on a day-basis !!!
+            if( isset( $recurridList[$checkDate] )) // no recurring to start this day
+              continue;
+            if( isset( $exdatelist[$checkDate] )) // check excluded dates
+              continue;
+            if( $startWdate >= $recurkey ) // exclude component start date
+              continue;
+            $rstart = $recurkey;
+            $rend   = $recurkey + $durvalue;
+           /* add repeating components within valid dates to output array, only start date set */
+            if( $flat ) {
+              if( !isset( $result[$compUID] )) // only one comp
+                $result[$compUID] = $component2->copy(); // copy to output
+            }
+           /* add repeating components within valid dates to output array, one each day */
+            elseif( $split ) {
+              $xRecurrence += 1;
+              if( $rend > $endDate )
+                $rend = $endDate;
+              $startYMD = $rstartYMD = date( 'Ymd', $rstart );
+              $endYMD   = date( 'Ymd', $rend );
+// echo "splitStart=".date( 'Y-m-d H:i:s', $rstart ).' end='.date( 'Y-m-d H:i:s', $rend )."<br />\n"; // test ###;
+              while( $rstart <= $rend ) { // iterate.. .
+                $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
+                if( isset( $recurridList[$checkDate] )) // no recurring to start this day
+                  break;
+                if( isset( $exdatelist[$checkDate] ))  // exclude any recurrence START date, found in exdatelist
+                  break;
+// echo "checking date after startdate=".date( 'Y-m-d H:i:s', $rstart ).' mot '.date( 'Y-m-d H:i:s', $startDate )."<br />"; // test ###;
+                if( $rstart >= $startDate ) {    // date after dtstart
+                  if( $rstartYMD > $startYMD ) // date after dtstart
+                    $datestring = date( $startDateFormat, $checkDate );
+                  else
+                    $datestring = date( $startDateFormat, $rstart );
+                  if( isset( $start['tz'] ))
+                    $datestring .= ' '.$start['tz'];
+// echo "spliting = $datestring<BR>\n"; // test ###
+                  $component2->setProperty( 'X-CURRENT-DTSTART', $datestring );
+                  if( $dtendExist || $dueExist || $durationExist ) {
+                    if( $rstartYMD < $endYMD ) // not the last day
+                      $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ));
+                    else
+                      $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
+                    if( $endAllDayEvent && $dtendExist )
+                      $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
+                    $datestring = date( $endDateFormat, $tend );
+                    if( isset( $end['tz'] ))
+                      $datestring .= ' '.$end['tz'];
+                    $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
+                    $component2->setProperty( $propName, $datestring );
+                  } // end if( $dtendExist || $dueExist || $durationExist )
+                  $component2->setProperty( 'X-RECURRENCE', $xRecurrence );
+                  $wd = getdate( $rstart );
+                  $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output
+                } // end if( $checkDate > $startYMD ) {    // date after dtstart
+                $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
+                $rstartYMD = date( 'Ymd', $rstart );
+              } // end while( $rstart <= $rend )
+            } // end elseif( $split )
+            elseif( $rstart >= $startDate ) {     // date within period   //* flat=FALSE && split=FALSE => one comp every recur startdate *//
+              $xRecurrence += 1;
+              $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
+              if( !isset( $exdatelist[$checkDate] )) { // exclude any recurrence START date, found in exdatelist
+                $datestring = date( $startDateFormat, $rstart );
+                if( isset( $start['tz'] ))
+                  $datestring .= ' '.$start['tz'];
+//echo "X-CURRENT-DTSTART 2 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."<br />";$component2->setProperty( 'X-CNT', $tcnt ); // test ###
+                $component2->setProperty( 'X-CURRENT-DTSTART', $datestring );
+                if( $dtendExist || $dueExist || $durationExist ) {
+                  $tend = $rstart + $rdurWsecs;
+                  if( date( 'Ymd', $tend ) < date( 'Ymd', $endWdate ))
+                    $tend = mktime( 23, 59, 59, date( 'm', $tend ), date( 'd', $tend ), date( 'Y', $tend ));
+                  else
+                    $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $tend ), date( 'd', $tend ), date( 'Y', $tend ) ); // on a day-basis !!!
+                  if( $endAllDayEvent && $dtendExist )
+                    $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
+                  $datestring = date( $endDateFormat, $tend );
+                  if( isset( $end['tz'] ))
+                    $datestring .= ' '.$end['tz'];
+                  $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
+                  $component2->setProperty( $propName, $datestring );
+                } // end if( $dtendExist || $dueExist || $durationExist )
+                $component2->setProperty( 'X-RECURRENCE', $xRecurrence );
+                $wd = getdate( $rstart );
+                $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output
+              } // end if( !isset( $exdatelist[$checkDate] ))
+            } // end elseif( $rstart >= $startDate )
+          } // end foreach( $recurlist as $recurkey => $durvalue )
+          unset( $component2 );
+        } // end if( 0 < count( $recurlist ))
+            /* deselect components with startdate/enddate not within period */
+        if(( $endWdate < $startDate ) || ( $startWdate > $endDate ))
+          continue;
+      } // end if( TRUE === $any )
+    } // end foreach ( $this->components as $cix => $component )
+    unset( $dtendExist, $dueExist, $durationExist, $endAllDayEvent, $recurrid, $recurridList,
+           $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $exdatelist, $recurlist, $workstart, $workend, $endDateFormat ); // clean up
+    if( 0 >= count( $result )) return FALSE;
+    elseif( !$flat ) {
+      foreach( $result as $y => $yeararr ) {
+        foreach( $yeararr as $m => $montharr ) {
+          foreach( $montharr as $d => $dayarr ) {
+            if( empty( $result[$y][$m][$d] ))
+                unset( $result[$y][$m][$d] );
+            else
+              $result[$y][$m][$d] = array_values( $dayarr ); // skip tricky UID-index, hoping they are in hour order.. .
+          }
+          if( empty( $result[$y][$m] ))
+              unset( $result[$y][$m] );
+          else
+            ksort( $result[$y][$m] );
+        }
+        if( empty( $result[$y] ))
+            unset( $result[$y] );
+        else
+          ksort( $result[$y] );
+      }
+      if( empty( $result ))
+          unset( $result );
+      else
+        ksort( $result );
+    } // end elseif( !$flat )
+    if( 0 >= count( $result ))
+      return FALSE;
+    return $result;
+  }
+/**
+ * select components from calendar on based on specific property value(-s)
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.6 - 2012-12-26
+ * @param array $selectOptions, (string) key => (mixed) value, (key=propertyName)
+ * @return array
+ */
+  function selectComponents2( $selectOptions ) {
+    $output = array();
+    $allowedComps      = array('vevent', 'vtodo', 'vjournal', 'vfreebusy' );
+    $allowedProperties = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' );
+    foreach( $this->components as $cix => $component3 ) {
+      if( !in_array( $component3->objName, $allowedComps ))
+        continue;
+      $uid = $component3->getProperty( 'UID' );
+      foreach( $selectOptions as $propName => $pvalue ) {
+        $propName = strtoupper( $propName );
+        if( !in_array( $propName, $allowedProperties ))
+          continue;
+        if( !is_array( $pvalue ))
+          $pvalue = array( $pvalue );
+        if(( 'UID' == $propName ) && in_array( $uid, $pvalue )) {
+          $output[$uid][] = $component3->copy();
+          continue;
+        }
+        elseif(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'CONTACT' == $propName ) || ( 'RELATED-TO' == $propName ) || ( 'RESOURCES' == $propName )) { // multiple occurrence?
+          $propValues = array();
+          $component3->_getProperties( $propName, $propValues );
+          $propValues = array_keys( $propValues );
+          foreach( $pvalue as $theValue ) {
+            if( in_array( $theValue, $propValues )) { //  && !isset( $output[$uid] )) {
+              $output[$uid][] = $component3->copy();
+              break;
+            }
+          }
+          continue;
+        } // end   elseif( // multiple occurrence?
+        elseif( FALSE === ( $d = $component3->getProperty( $propName ))) // single occurrence
+          continue;
+        if( is_array( $d )) {
+          foreach( $d as $part ) {
+            if( in_array( $part, $pvalue ) && !isset( $output[$uid] ))
+              $output[$uid][] = $component3->copy();
+          }
+        }
+        elseif(( 'SUMMARY' == $propName ) && !isset( $output[$uid] )) {
+          foreach( $pvalue as $pval ) {
+            if( FALSE !== stripos( $d, $pval )) {
+              $output[$uid][] = $component3->copy();
+              break;
+            }
+          }
+        }
+        elseif( in_array( $d, $pvalue ) && !isset( $output[$uid] ))
+          $output[$uid][] = $component3->copy();
+      } // end foreach( $selectOptions as $propName => $pvalue ) {
+    } // end foreach( $this->components as $cix => $component3 ) {
+    if( !empty( $output )) {
+      ksort( $output ); // uid order
+      $output2 = array();
+      foreach( $output as $uid => $components ) {
+        foreach( $components as $component )
+          $output2[] = $component;
+      }
+      $output = $output2;
+    }
+    return $output;
+  }
+/**
+ * add calendar component to container
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.8.8 - 2011-03-15
+ * @param object $component calendar component
+ * @param mixed $arg1 optional, ordno/component type/ component uid
+ * @param mixed $arg2 optional, ordno if arg1 = component type
+ * @return void
+ */
+  function setComponent( $component, $arg1=FALSE, $arg2=FALSE  ) {
+    $component->setConfig( $this->getConfig(), FALSE, TRUE );
+    if( !in_array( $component->objName, array( 'valarm', 'vtimezone' ))) {
+            /* make sure dtstamp and uid is set */
+      $dummy1 = $component->getProperty( 'dtstamp' );
+      $dummy2 = $component->getProperty( 'uid' );
+    }
+    if( !$arg1 ) { // plain insert, last in chain
+      $this->components[] = $component->copy();
+      return TRUE;
+    }
+    $argType = $index = null;
+    if ( ctype_digit( (string) $arg1 )) { // index insert/replace
+      $argType = 'INDEX';
+      $index   = (int) $arg1 - 1;
+    }
+    elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) {
+      $argType = strtolower( $arg1 );
+      $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0;
+    }
+    // else if arg1 is set, arg1 must be an UID
+    $cix1sC = 0;
+    foreach ( $this->components as $cix => $component2) {
+      if( empty( $component2 )) continue;
+      if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace
+        $this->components[$cix] = $component->copy();
+        return TRUE;
+      }
+      elseif( $argType == $component2->objName ) { // component Type index insert/replace
+        if( $index == $cix1sC ) {
+          $this->components[$cix] = $component->copy();
+          return TRUE;
+        }
+        $cix1sC++;
+      }
+      elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace
+        $this->components[$cix] = $component->copy();
+        return TRUE;
+      }
+    }
+            /* arg1=index and not found.. . insert at index .. .*/
+    if( 'INDEX' == $argType ) {
+      $this->components[$index] = $component->copy();
+      ksort( $this->components, SORT_NUMERIC );
+    }
+    else    /* not found.. . insert last in chain anyway .. .*/
+      $this->components[] = $component->copy();
+    return TRUE;
+  }
+/**
+ * sort iCal compoments
+ *
+ * ascending sort on properties (if exist) x-current-dtstart, dtstart,
+ * x-current-dtend, dtend, x-current-due, due, duration, created, dtstamp, uid if called without arguments,
+ * otherwise sorting on specific (argument) property values
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.4 - 2012-12-17
+ * @param string $sortArg, optional
+ * @return void
+ *
+ */
+  function sort( $sortArg=FALSE ) {
+    if( is_array( $this->components )) {
+      if( $sortArg ) {
+        $sortArg = strtoupper( $sortArg );
+        if( !in_array( $sortArg, array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'DTSTAMP', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' )))
+          $sortArg = FALSE;
+      }
+            /* set sort parameters for each component */
+      foreach( $this->components as $cix => & $c ) {
+        $c->srtk = array( '0', '0', '0', '0' );
+        if( 'vtimezone' == $c->objName ) {
+          if( FALSE === ( $c->srtk[0] = $c->getProperty( 'tzid' )))
+            $c->srtk[0] = 0;
+          continue;
+        }
+        elseif( $sortArg ) {
+          if(( 'ATTENDEE' == $sortArg ) || ( 'CATEGORIES' == $sortArg ) || ( 'CONTACT' == $sortArg ) || ( 'RELATED-TO' == $sortArg ) || ( 'RESOURCES' == $sortArg )) {
+            $propValues = array();
+            $c->_getProperties( $sortArg, $propValues );
+            if( !empty( $propValues )) {
+              $sk         = array_keys( $propValues );
+              $c->srtk[0] = $sk[0];
+              if( 'RELATED-TO'  == $sortArg )
+                $c->srtk[0] .= $c->getProperty( 'uid' );
+            }
+            elseif( 'RELATED-TO'  == $sortArg )
+              $c->srtk[0] = $c->getProperty( 'uid' );
+          }
+          elseif( FALSE !== ( $d = $c->getProperty( $sortArg ))) {
+            $c->srtk[0] = $d;
+            if( 'UID' == $sortArg ) {
+              if( FALSE !== ( $d = $c->getProperty( 'recurrence-id' ))) {
+                $c->srtk[1] = iCalUtilityFunctions::_date2strdate( $d );
+                if( FALSE === ( $c->srtk[2] = $c->getProperty( 'sequence' )))
+                  $c->srtk[2] = PHP_INT_MAX;
+              }
+              else
+                $c->srtk[1] = $c->srtk[2] = PHP_INT_MAX;
+            }
+          }
+          continue;
+        }
+        if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTSTART' ))) {
+          $c->srtk[0] = iCalUtilityFunctions::_strdate2date( $d[1] );
+          unset( $c->srtk[0]['unparsedtext'] );
+        }
+        elseif( FALSE === ( $c->srtk[0] = $c->getProperty( 'dtstart' )))
+          $c->srtk[1] = 0;                                                  // sortkey 0 : dtstart
+        if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTEND' ))) {
+          $c->srtk[1] = iCalUtilityFunctions::_strdate2date( $d[1] );   // sortkey 1 : dtend/due(/dtstart+duration)
+          unset( $c->srtk[1]['unparsedtext'] );
+        }
+        elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'dtend' ))) {
+          if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DUE' ))) {
+            $c->srtk[1] = iCalUtilityFunctions::_strdate2date( $d[1] );
+            unset( $c->srtk[1]['unparsedtext'] );
+          }
+          elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'due' )))
+            if( FALSE === ( $c->srtk[1] = $c->getProperty( 'duration', FALSE, FALSE, TRUE )))
+              $c->srtk[1] = 0;
+        }
+        if( FALSE === ( $c->srtk[2] = $c->getProperty( 'created' )))      // sortkey 2 : created/dtstamp
+          if( FALSE === ( $c->srtk[2] = $c->getProperty( 'dtstamp' )))
+            $c->srtk[2] = 0;
+        if( FALSE === ( $c->srtk[3] = $c->getProperty( 'uid' )))          // sortkey 3 : uid
+          $c->srtk[3] = 0;
+      } // end foreach( $this->components as & $c
+            /* sort */
+      usort( $this->components, array( 'iCalUtilityFunctions', '_cmpfcn' ));
+    }
+  }
+/**
+ * parse iCal text/file into vcalendar, components, properties and parameters
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.2 - 2012-12-18
+ * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of property strings
+ * @return bool FALSE if error occurs during parsing
+ *
+ */
+  function parse( $unparsedtext=FALSE ) {
+    $nl = $this->getConfig( 'nl' );
+    if(( FALSE === $unparsedtext ) || empty( $unparsedtext )) {
+            /* directory+filename is set previously via setConfig directory+filename or url */
+      if( FALSE === ( $filename = $this->getConfig( 'url' )))
+        $filename = $this->getConfig( 'dirfile' );
+            /* READ FILE */
+      if( FALSE === ( $rows = file_get_contents( $filename )))
+        return FALSE;                 /* err 1 */
+    }
+    elseif( is_array( $unparsedtext ))
+      $rows =  implode( '\n'.$nl, $unparsedtext );
+    else
+      $rows = & $unparsedtext;
+            /* fix line folding */
+    $rows = explode( $nl, iCalUtilityFunctions::convEolChar( $rows, $nl ));
+            /* skip leading (empty/invalid) lines */
+    foreach( $rows as $lix => $line ) {
+      if( FALSE !== stripos( $line, 'BEGIN:VCALENDAR' ))
+        break;
+      unset( $rows[$lix] );
+    }
+    $rcnt = count( $rows );
+    if( 3 > $rcnt )                  /* err 10 */
+      return FALSE;
+            /* skip trailing empty lines and ensure an end row */
+    $lix  = array_keys( $rows );
+    $lix  = end( $lix );
+    while( 3 < $lix ) {
+      $tst = trim( $rows[$lix] );
+      if(( '\n' == $tst ) || empty( $tst )) {
+        unset( $rows[$lix] );
+        $lix--;
+        continue;
+      }
+      if( FALSE === stripos( $rows[$lix], 'END:VCALENDAR' ))
+        $rows[] = 'END:VCALENDAR';
+      break;
+    }
+    $comp    = & $this;
+    $calsync = $compsync = 0;
+            /* identify components and update unparsed data within component */
+    $config = $this->getConfig();
+    $endtxt = array( 'END:VE', 'END:VF', 'END:VJ', 'END:VT' );
+    foreach( $rows as $lix => $line ) {
+      if(     'BEGIN:VCALENDAR' == strtoupper( substr( $line, 0, 15 ))) {
+        $calsync++;
+        continue;
+      }
+      elseif( 'END:VCALENDAR'   == strtoupper( substr( $line, 0, 13 ))) {
+        if( 0 < $compsync )
+          $this->components[] = $comp->copy();
+        $compsync--;
+        $calsync--;
+        break;
+      }
+      elseif( 1 != $calsync )
+        return FALSE;                 /* err 20 */
+      elseif( in_array( strtoupper( substr( $line, 0, 6 )), $endtxt )) {
+        $this->components[] = $comp->copy();
+        $compsync--;
+        continue;
+      }
+      if(     'BEGIN:VEVENT'    == strtoupper( substr( $line, 0, 12 ))) {
+        $comp = new vevent( $config );
+        $compsync++;
+      }
+      elseif( 'BEGIN:VFREEBUSY' == strtoupper( substr( $line, 0, 15 ))) {
+        $comp = new vfreebusy( $config );
+        $compsync++;
+      }
+      elseif( 'BEGIN:VJOURNAL'  == strtoupper( substr( $line, 0, 14 ))) {
+        $comp = new vjournal( $config );
+        $compsync++;
+      }
+      elseif( 'BEGIN:VTODO'     == strtoupper( substr( $line, 0, 11 ))) {
+        $comp = new vtodo( $config );
+        $compsync++;
+      }
+      elseif( 'BEGIN:VTIMEZONE' == strtoupper( substr( $line, 0, 15 ))) {
+        $comp = new vtimezone( $config );
+        $compsync++;
+      }
+      else { /* update component with unparsed data */
+        $comp->unparsed[] = $line;
+      }
+    } // end foreach( $rows as $line )
+    unset( $config, $endtxt );
+            /* parse data for calendar (this) object */
+    if( isset( $this->unparsed ) && is_array( $this->unparsed ) && ( 0 < count( $this->unparsed ))) {
+            /* concatenate property values spread over several lines */
+      $propnames = array( 'calscale','method','prodid','version','x-' );
+      $proprows  = array();
+      for( $i = 0; $i < count( $this->unparsed ); $i++ ) { // concatenate lines
+        $line = rtrim( $this->unparsed[$i], $nl );
+        while( isset( $this->unparsed[$i+1] ) && !empty( $this->unparsed[$i+1] ) && ( ' ' == $this->unparsed[$i+1]{0} ))
+          $line .= rtrim( substr( $this->unparsed[++$i], 1 ), $nl );
+        $proprows[] = $line;
+      }
+      $paramMStz   = array( 'utc-', 'utc+', 'gmt-', 'gmt+' );
+      $paramProto3 = array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' );
+      $paramProto4 = array( 'crid:', 'news:', 'pres:' );
+      foreach( $proprows as $line ) {
+        if( '\n' == substr( $line, -2 ))
+          $line = substr( $line, 0, -2 );
+            /* get property name */
+        $propname  = '';
+        $cix       = 0;
+        while( FALSE !== ( $char = substr( $line, $cix, 1 ))) {
+          if( in_array( $char, array( ':', ';' )))
+            break;
+          else
+            $propname .= $char;
+          $cix++;
+        }
+            /* skip non standard property names */
+        if(( 'x-' != strtolower( substr( $propname, 0, 2 ))) && !in_array( strtolower( $propname ), $propnames ))
+          continue;
+            /* ignore version/prodid properties */
+        if( in_array( strtolower( $propname ), array( 'version', 'prodid' )))
+          continue;
+            /* rest of the line is opt.params and value */
+        $line = substr( $line, $cix);
+            /* separate attributes from value */
+        $attr         = array();
+        $attrix       = -1;
+        $strlen       = strlen( $line );
+        $WithinQuotes = FALSE;
+        $cix          = 0;
+        while( FALSE !== substr( $line, $cix, 1 )) {
+          if(                       ( ':'  == $line[$cix] )                         &&
+                                    ( substr( $line,$cix,     3 )  != '://' )       &&
+             ( !in_array( strtolower( substr( $line,$cix - 6, 4 )), $paramMStz ))   &&
+             ( !in_array( strtolower( substr( $line,$cix - 3, 4 )), $paramProto3 )) &&
+             ( !in_array( strtolower( substr( $line,$cix - 4, 5 )), $paramProto4 )) &&
+                        ( strtolower( substr( $line,$cix - 6, 7 )) != 'mailto:' )   &&
+               !$WithinQuotes ) {
+            $attrEnd = TRUE;
+            if(( $cix < ( $strlen - 4 )) &&
+                 ctype_digit( substr( $line, $cix+1, 4 ))) { // an URI with a (4pos) portnr??
+              for( $c2ix = $cix; 3 < $c2ix; $c2ix-- ) {
+                if( '://' == substr( $line, $c2ix - 2, 3 )) {
+                  $attrEnd = FALSE;
+                  break; // an URI with a portnr!!
+                }
+              }
+            }
+            if( $attrEnd) {
+              $line = substr( $line, ( $cix + 1 ));
+              break;
+            }
+          }
+          if( '"' == $line[$cix] )
+            $WithinQuotes = ( FALSE === $WithinQuotes ) ? TRUE : FALSE;
+          if( ';' == $line[$cix] )
+            $attr[++$attrix] = null;
+          else
+            $attr[$attrix] .= $line[$cix];
+          $cix++;
+        }
+            /* make attributes in array format */
+        $propattr = array();
+        foreach( $attr as $attribute ) {
+          $attrsplit = explode( '=', $attribute, 2 );
+          if( 1 < count( $attrsplit ))
+            $propattr[$attrsplit[0]] = $attrsplit[1];
+          else
+            $propattr[] = $attribute;
+        }
+            /* update Property */
+        if( FALSE !== strpos( $line, ',' )) {
+          $content  = array( 0 => '' );
+          $cix = $lix = 0;
+          while( FALSE !== substr( $line, $lix, 1 )) {
+            if(( 0 < $lix ) && ( ',' == $line[$lix] ) && ( "\\" != $line[( $lix - 1 )])) {
+              $cix++;
+              $content[$cix] = '';
+            }
+            else
+              $content[$cix] .= $line[$lix];
+            $lix++;
+          }
+          if( 1 < count( $content )) {
+            foreach( $content as $cix => $contentPart )
+              $content[$cix] = iCalUtilityFunctions::_strunrep( $contentPart );
+            $this->setProperty( $propname, $content, $propattr );
+            continue;
+          }
+          else
+            $line = reset( $content );
+          $line = iCalUtilityFunctions::_strunrep( $line );
+        }
+        $this->setProperty( $propname, rtrim( $line, "\x00..\x1F" ), $propattr );
+      } // end - foreach( $this->unparsed.. .
+    } // end - if( is_array( $this->unparsed.. .
+    unset( $unparsedtext, $rows, $this->unparsed, $proprows );
+            /* parse Components */
+    if( is_array( $this->components ) && ( 0 < count( $this->components ))) {
+      $ckeys = array_keys( $this->components );
+      foreach( $ckeys as $ckey ) {
+        if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) {
+          $this->components[$ckey]->parse();
+        }
+      }
+    }
+    else
+      return FALSE;                   /* err 91 or something.. . */
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * creates formatted output for calendar object instance
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.10.16 - 2011-10-28
+ * @return string
+ */
+  function createCalendar() {
+    $calendarInit = $calendarxCaldecl = $calendarStart = $calendar = '';
+    switch( $this->format ) {
+      case 'xcal':
+        $calendarInit  = '<?xml version="1.0" encoding="UTF-8"?>'.$this->nl.
+                         '<!DOCTYPE vcalendar PUBLIC "-//IETF//DTD XCAL/iCalendar XML//EN"'.$this->nl.
+                         '"http://www.ietf.org/internet-drafts/draft-ietf-calsch-many-xcal-01.txt"';
+        $calendarStart = '>'.$this->nl.'<vcalendar';
+        break;
+      default:
+        $calendarStart = 'BEGIN:VCALENDAR'.$this->nl;
+        break;
+    }
+    $calendarStart .= $this->createVersion();
+    $calendarStart .= $this->createProdid();
+    $calendarStart .= $this->createCalscale();
+    $calendarStart .= $this->createMethod();
+    if( 'xcal' == $this->format )
+      $calendarStart .= '>'.$this->nl;
+    $calendar .= $this->createXprop();
+
+    foreach( $this->components as $component ) {
+      if( empty( $component )) continue;
+      $component->setConfig( $this->getConfig(), FALSE, TRUE );
+      $calendar .= $component->createComponent( $this->xcaldecl );
+    }
+    if(( 'xcal' == $this->format ) && ( 0 < count( $this->xcaldecl ))) { // xCal only
+      $calendarInit .= ' [';
+      $old_xcaldecl  = array();
+      foreach( $this->xcaldecl as $declix => $declPart ) {
+        if(( 0 < count( $old_xcaldecl))    &&
+             isset( $declPart['uri'] )     && isset( $declPart['external'] )     &&
+             isset( $old_xcaldecl['uri'] ) && isset( $old_xcaldecl['external'] ) &&
+           ( in_array( $declPart['uri'],      $old_xcaldecl['uri'] ))            &&
+           ( in_array( $declPart['external'], $old_xcaldecl['external'] )))
+          continue; // no duplicate uri and ext. references
+        if(( 0 < count( $old_xcaldecl))    &&
+            !isset( $declPart['uri'] )     && !isset( $declPart['uri'] )         &&
+             isset( $declPart['ref'] )     && isset( $old_xcaldecl['ref'] )      &&
+           ( in_array( $declPart['ref'],      $old_xcaldecl['ref'] )))
+          continue; // no duplicate element declarations
+        $calendarxCaldecl .= $this->nl.'<!';
+        foreach( $declPart as $declKey => $declValue ) {
+          switch( $declKey ) {                    // index
+            case 'xmldecl':                       // no 1
+              $calendarxCaldecl .= $declValue.' ';
+              break;
+            case 'uri':                           // no 2
+              $calendarxCaldecl .= $declValue.' ';
+              $old_xcaldecl['uri'][] = $declValue;
+              break;
+            case 'ref':                           // no 3
+              $calendarxCaldecl .= $declValue.' ';
+              $old_xcaldecl['ref'][] = $declValue;
+              break;
+            case 'external':                      // no 4
+              $calendarxCaldecl .= '"'.$declValue.'" ';
+              $old_xcaldecl['external'][] = $declValue;
+              break;
+            case 'type':                          // no 5
+              $calendarxCaldecl .= $declValue.' ';
+              break;
+            case 'type2':                         // no 6
+              $calendarxCaldecl .= $declValue;
+              break;
+          }
+        }
+        $calendarxCaldecl .= '>';
+      }
+      $calendarxCaldecl .= $this->nl.']';
+    }
+    switch( $this->format ) {
+      case 'xcal':
+        $calendar .= '</vcalendar>'.$this->nl;
+        break;
+      default:
+        $calendar .= 'END:VCALENDAR'.$this->nl;
+        break;
+    }
+    return $calendarInit.$calendarxCaldecl.$calendarStart.$calendar;
+  }
+/**
+ * a HTTP redirect header is sent with created, updated and/or parsed calendar
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.10.24 - 2011-12-23
+ * @param bool $utf8Encode
+ * @param bool $gzip
+ * @return redirect
+ */
+  function returnCalendar( $utf8Encode=FALSE, $gzip=FALSE ) {
+    $filename = $this->getConfig( 'filename' );
+    $output   = $this->createCalendar();
+    if( $utf8Encode )
+      $output = utf8_encode( $output );
+    if( $gzip ) {
+      $output = gzencode( $output, 9 );
+      header( 'Content-Encoding: gzip' );
+      header( 'Vary: *' );
+      header( 'Content-Length: '.strlen( $output ));
+    }
+    if( 'xcal' == $this->format )
+      header( 'Content-Type: application/calendar+xml; charset=utf-8' );
+    else
+      header( 'Content-Type: text/calendar; charset=utf-8' );
+    header( 'Content-Disposition: attachment; filename="'.$filename.'"' );
+    header( 'Cache-Control: max-age=10' );
+    die( $output );
+  }
+/**
+ * save content in a file
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.2.12 - 2007-12-30
+ * @param string $directory optional
+ * @param string $filename optional
+ * @param string $delimiter optional
+ * @return bool
+ */
+  function saveCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE ) {
+    if( $directory )
+      $this->setConfig( 'directory', $directory );
+    if( $filename )
+      $this->setConfig( 'filename',  $filename );
+    if( $delimiter && ($delimiter != DIRECTORY_SEPARATOR ))
+      $this->setConfig( 'delimiter', $delimiter );
+    if( FALSE === ( $dirfile = $this->getConfig( 'url' )))
+      $dirfile = $this->getConfig( 'dirfile' );
+    $iCalFile = @fopen( $dirfile, 'w' );
+    if( $iCalFile ) {
+      if( FALSE === fwrite( $iCalFile, $this->createCalendar() ))
+        return FALSE;
+      fclose( $iCalFile );
+      return TRUE;
+    }
+    else
+      return FALSE;
+  }
+/**
+ * if recent version of calendar file exists (default one hour), an HTTP redirect header is sent
+ * else FALSE is returned
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.2.12 - 2007-10-28
+ * @param string $directory optional alt. int timeout
+ * @param string $filename optional
+ * @param string $delimiter optional
+ * @param int timeout optional, default 3600 sec
+ * @return redirect/FALSE
+ */
+  function useCachedCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE, $timeout=3600) {
+    if ( $directory && ctype_digit( (string) $directory ) && !$filename ) {
+      $timeout   = (int) $directory;
+      $directory = FALSE;
+    }
+    if( $directory )
+      $this->setConfig( 'directory', $directory );
+    if( $filename )
+      $this->setConfig( 'filename',  $filename );
+    if( $delimiter && ( $delimiter != DIRECTORY_SEPARATOR ))
+      $this->setConfig( 'delimiter', $delimiter );
+    $filesize    = $this->getConfig( 'filesize' );
+    if( 0 >= $filesize )
+      return FALSE;
+    $dirfile     = $this->getConfig( 'dirfile' );
+    if( time() - filemtime( $dirfile ) < $timeout) {
+      clearstatcache();
+      $dirfile   = $this->getConfig( 'dirfile' );
+      $filename  = $this->getConfig( 'filename' );
+//    if( headers_sent( $filename, $linenum ))
+//      die( "Headers already sent in $filename on line $linenum\n" );
+      if( 'xcal' == $this->format )
+        header( 'Content-Type: application/calendar+xml; charset=utf-8' );
+      else
+        header( 'Content-Type: text/calendar; charset=utf-8' );
+      header( 'Content-Length: '.$filesize );
+      header( 'Content-Disposition: attachment; filename="'.$filename.'"' );
+      header( 'Cache-Control: max-age=10' );
+      $fp = @fopen( $dirfile, 'r' );
+      if( $fp ) {
+        fpassthru( $fp );
+        fclose( $fp );
+      }
+      die();
+    }
+    else
+      return FALSE;
+  }
+}
+/*********************************************************************************/
+/*********************************************************************************/
+/**
+ *  abstract class for calendar components
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.9.6 - 2011-05-14
+ */
+class calendarComponent {
+            //  component property variables
+  var $uid;
+  var $dtstamp;
+
+            //  component config variables
+  var $allowEmpty;
+  var $language;
+  var $nl;
+  var $unique_id;
+  var $format;
+  var $objName; // created automatically at instance creation
+  var $dtzid;   // default (local) timezone
+            //  component internal variables
+  var $componentStart1;
+  var $componentStart2;
+  var $componentEnd1;
+  var $componentEnd2;
+  var $elementStart1;
+  var $elementStart2;
+  var $elementEnd1;
+  var $elementEnd2;
+  var $intAttrDelimiter;
+  var $attributeDelimiter;
+  var $valueInit;
+            //  component xCal declaration container
+  var $xcaldecl;
+/**
+ * constructor for calendar component object
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.9.6 - 2011-05-17
+ */
+  function calendarComponent() {
+    $this->objName         = ( isset( $this->timezonetype )) ?
+                          strtolower( $this->timezonetype )  :  get_class ( $this );
+    $this->uid             = array();
+    $this->dtstamp         = array();
+
+    $this->language        = null;
+    $this->nl              = null;
+    $this->unique_id       = null;
+    $this->format          = null;
+    $this->dtzid           = null;
+    $this->allowEmpty      = TRUE;
+    $this->xcaldecl        = array();
+
+    $this->_createFormat();
+    $this->_makeDtstamp();
+  }
+/*********************************************************************************/
+/**
+ * Property Name: ACTION
+ */
+/**
+ * creates formatted output for calendar component property action
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-10-22
+ * @return string
+ */
+  function createAction() {
+    if( empty( $this->action )) return FALSE;
+    if( empty( $this->action['value'] ))
+      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'ACTION' ) : FALSE;
+    $attributes = $this->_createParams( $this->action['params'] );
+    return $this->_createElement( 'ACTION', $attributes, $this->action['value'] );
+  }
+/**
+ * set calendar component property action
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-11-04
+ * @param string $value  "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE"
+ * @param mixed $params
+ * @return bool
+ */
+  function setAction( $value, $params=FALSE ) {
+    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    $this->action = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: ATTACH
+ */
+/**
+ * creates formatted output for calendar component property attach
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.11.16 - 2012-02-04
+ * @return string
+ */
+  function createAttach() {
+    if( empty( $this->attach )) return FALSE;
+    $output       = null;
+    foreach( $this->attach as $attachPart ) {
+      if( !empty( $attachPart['value'] )) {
+        $attributes = $this->_createParams( $attachPart['params'] );
+        if(( 'xcal' != $this->format ) && isset( $attachPart['params']['VALUE'] ) && ( 'BINARY' == $attachPart['params']['VALUE'] )) {
+          $attributes = str_replace( $this->intAttrDelimiter, $this->attributeDelimiter, $attributes );
+          $str        = 'ATTACH'.$attributes.$this->valueInit.$attachPart['value'];
+          $output     = substr( $str, 0, 75 ).$this->nl;
+          $str        = substr( $str, 75 );
+          $output    .= ' '.chunk_split( $str, 74, $this->nl.' ' );
+          if( ' ' == substr( $output, -1 ))
+            $output   = rtrim( $output );
+          if( $this->nl != substr( $output, ( 0 - strlen( $this->nl ))))
+            $output  .= $this->nl;
+          return $output;
+        }
+        $output    .= $this->_createElement( 'ATTACH', $attributes, $attachPart['value'] );
+      }
+      elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'ATTACH' );
+    }
+    return $output;
+  }
+/**
+ * set calendar component property attach
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-11-06
+ * @param string $value
+ * @param array $params, optional
+ * @param integer $index, optional
+ * @return bool
+ */
+  function setAttach( $value, $params=FALSE, $index=FALSE ) {
+    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    iCalUtilityFunctions::_setMval( $this->attach, $value, $params, FALSE, $index );
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: ATTENDEE
+ */
+/**
+ * creates formatted output for calendar component property attendee
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.11.12 - 2012-01-31
+ * @return string
+ */
+  function createAttendee() {
+    if( empty( $this->attendee )) return FALSE;
+    $output = null;
+    foreach( $this->attendee as $attendeePart ) {                      // start foreach 1
+      if( empty( $attendeePart['value'] )) {
+        if( $this->getConfig( 'allowEmpty' ))
+          $output .= $this->_createElement( 'ATTENDEE' );
+        continue;
+      }
+      $attendee1 = $attendee2 = null;
+      foreach( $attendeePart as $paramlabel => $paramvalue ) {         // start foreach 2
+        if( 'value' == $paramlabel )
+          $attendee2     .= $paramvalue;
+        elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue ))) { // start elseif
+          $mParams = array( 'MEMBER', 'DELEGATED-TO', 'DELEGATED-FROM' );
+          foreach( $paramvalue as $pKey => $pValue ) {                 // fix (opt) quotes
+            if( is_array( $pValue ) || in_array( $pKey, $mParams ))
+              continue;
+            if(( FALSE !== strpos( $pValue, ':' )) ||
+               ( FALSE !== strpos( $pValue, ';' )) ||
+               ( FALSE !== strpos( $pValue, ',' )))
+              $paramvalue[$pKey] = '"'.$pValue.'"';
+          }
+        // set attenddee parameters in rfc2445 order
+          if( isset( $paramvalue['CUTYPE'] ))
+            $attendee1   .= $this->intAttrDelimiter.'CUTYPE='.$paramvalue['CUTYPE'];
+          if( isset( $paramvalue['MEMBER'] )) {
+            $attendee1   .= $this->intAttrDelimiter.'MEMBER=';
+            foreach( $paramvalue['MEMBER'] as $cix => $opv )
+              $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
+          }
+          if( isset( $paramvalue['ROLE'] ))
+            $attendee1   .= $this->intAttrDelimiter.'ROLE='.$paramvalue['ROLE'];
+          if( isset( $paramvalue['PARTSTAT'] ))
+            $attendee1   .= $this->intAttrDelimiter.'PARTSTAT='.$paramvalue['PARTSTAT'];
+          if( isset( $paramvalue['RSVP'] ))
+            $attendee1   .= $this->intAttrDelimiter.'RSVP='.$paramvalue['RSVP'];
+          if( isset( $paramvalue['DELEGATED-TO'] )) {
+            $attendee1   .= $this->intAttrDelimiter.'DELEGATED-TO=';
+            foreach( $paramvalue['DELEGATED-TO'] as $cix => $opv )
+              $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
+          }
+          if( isset( $paramvalue['DELEGATED-FROM'] )) {
+            $attendee1   .= $this->intAttrDelimiter.'DELEGATED-FROM=';
+            foreach( $paramvalue['DELEGATED-FROM'] as $cix => $opv )
+              $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
+          }
+          if( isset( $paramvalue['SENT-BY'] ))
+            $attendee1   .= $this->intAttrDelimiter.'SENT-BY='.$paramvalue['SENT-BY'];
+          if( isset( $paramvalue['CN'] ))
+            $attendee1   .= $this->intAttrDelimiter.'CN='.$paramvalue['CN'];
+          if( isset( $paramvalue['DIR'] )) {
+            $delim = ( FALSE === strpos( $paramvalue['DIR'], '"' )) ? '"' : '';
+            $attendee1   .= $this->intAttrDelimiter.'DIR='.$delim.$paramvalue['DIR'].$delim;
+          }
+          if( isset( $paramvalue['LANGUAGE'] ))
+            $attendee1   .= $this->intAttrDelimiter.'LANGUAGE='.$paramvalue['LANGUAGE'];
+          $xparams = array();
+          foreach( $paramvalue as $optparamlabel => $optparamvalue ) { // start foreach 3
+            if( ctype_digit( (string) $optparamlabel )) {
+              $xparams[]  = $optparamvalue;
+              continue;
+            }
+            if( !in_array( $optparamlabel, array( 'CUTYPE', 'MEMBER', 'ROLE', 'PARTSTAT', 'RSVP', 'DELEGATED-TO', 'DELEGATED-FROM', 'SENT-BY', 'CN', 'DIR', 'LANGUAGE' )))
+              $xparams[$optparamlabel] = $optparamvalue;
+          } // end foreach 3
+          ksort( $xparams, SORT_STRING );
+          foreach( $xparams as $paramKey => $paramValue ) {
+            if( ctype_digit( (string) $paramKey ))
+              $attendee1 .= $this->intAttrDelimiter.$paramValue;
+            else
+              $attendee1 .= $this->intAttrDelimiter."$paramKey=$paramValue";
+          }      // end foreach 3
+        }        // end elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue )))
+      }          // end foreach 2
+      $output .= $this->_createElement( 'ATTENDEE', $attendee1, $attendee2 );
+    }              // end foreach 1
+    return $output;
+  }
+/**
+ * set calendar component property attach
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.12.18 - 2012-07-13
+ * @param string $value
+ * @param array $params, optional
+ * @param integer $index, optional
+ * @return bool
+ */
+  function setAttendee( $value, $params=FALSE, $index=FALSE ) {
+    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+          // ftp://, http://, mailto:, file://, gopher://, news:, nntp://, telnet://, wais://, prospero://  may exist.. . also in params
+    if( !empty( $value )) {
+      if( FALSE === ( $pos = strpos( substr( $value, 0, 9 ), ':' )))
+        $value = 'MAILTO:'.$value;
+      elseif( !empty( $value ))
+        $value = strtolower( substr( $value, 0, $pos )).substr( $value, $pos );
+      $value = str_replace( 'mailto:', 'MAILTO:', $value );
+    }
+    $params2 = array();
+    if( is_array($params )) {
+      $optarrays = array();
+      foreach( $params as $optparamlabel => $optparamvalue ) {
+        $optparamlabel = strtoupper( $optparamlabel );
+        switch( $optparamlabel ) {
+          case 'MEMBER':
+          case 'DELEGATED-TO':
+          case 'DELEGATED-FROM':
+            if( !is_array( $optparamvalue ))
+              $optparamvalue = array( $optparamvalue );
+            foreach( $optparamvalue as $part ) {
+              $part = trim( $part );
+              if(( '"' == substr( $part, 0, 1 )) &&
+                 ( '"' == substr( $part, -1 )))
+                $part = substr( $part, 1, ( strlen( $part ) - 2 ));
+              if( 'mailto:' != strtolower( substr( $part, 0, 7 )))
+                $part = "MAILTO:$part";
+              else
+                $part = 'MAILTO:'.substr( $part, 7 );
+              $optarrays[$optparamlabel][] = $part;
+            }
+            break;
+          default:
+            if(( '"' == substr( $optparamvalue, 0, 1 )) &&
+               ( '"' == substr( $optparamvalue, -1 )))
+              $optparamvalue = substr( $optparamvalue, 1, ( strlen( $optparamvalue ) - 2 ));
+            if( 'SENT-BY' ==  $optparamlabel ) {
+              if( 'mailto:' != strtolower( substr( $optparamvalue, 0, 7 )))
+                $optparamvalue = "MAILTO:$optparamvalue";
+              else
+                $optparamvalue = 'MAILTO:'.substr( $optparamvalue, 7 );
+            }
+            $params2[$optparamlabel] = $optparamvalue;
+            break;
+        } // end switch( $optparamlabel.. .
+      } // end foreach( $optparam.. .
+      foreach( $optarrays as $optparamlabel => $optparams )
+        $params2[$optparamlabel] = $optparams;
+    }
+        // remove defaults
+    iCalUtilityFunctions::_existRem( $params2, 'CUTYPE',   'INDIVIDUAL' );
+    iCalUtilityFunctions::_existRem( $params2, 'PARTSTAT', 'NEEDS-ACTION' );
+    iCalUtilityFunctions::_existRem( $params2, 'ROLE',     'REQ-PARTICIPANT' );
+    iCalUtilityFunctions::_existRem( $params2, 'RSVP',     'FALSE' );
+        // check language setting
+    if( isset( $params2['CN' ] )) {
+      $lang = $this->getConfig( 'language' );
+      if( !isset( $params2['LANGUAGE' ] ) && !empty( $lang ))
+        $params2['LANGUAGE' ] = $lang;
+    }
+    iCalUtilityFunctions::_setMval( $this->attendee, $value, $params2, FALSE, $index );
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: CATEGORIES
+ */
+/**
+ * creates formatted output for calendar component property categories
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.2 - 2012-12-18
+ * @return string
+ */
+  function createCategories() {
+    if( empty( $this->categories )) return FALSE;
+    $output = null;
+    foreach( $this->categories as $category ) {
+      if( empty( $category['value'] )) {
+        if ( $this->getConfig( 'allowEmpty' ))
+          $output .= $this->_createElement( 'CATEGORIES' );
+        continue;
+      }
+      $attributes = $this->_createParams( $category['params'], array( 'LANGUAGE' ));
+      if( is_array( $category['value'] )) {
+        foreach( $category['value'] as $cix => $categoryPart )
+          $category['value'][$cix] = iCalUtilityFunctions::_strrep( $categoryPart, $this->format, $this->nl );
+        $content  = implode( ',', $category['value'] );
+      }
+      else
+        $content  = iCalUtilityFunctions::_strrep( $category['value'], $this->format, $this->nl );
+      $output    .= $this->_createElement( 'CATEGORIES', $attributes, $content );
+    }
+    return $output;
+  }
+/**
+ * set calendar component property categories
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-11-06
+ * @param mixed $value
+ * @param array $params, optional
+ * @param integer $index, optional
+ * @return bool
+ */
+  function setCategories( $value, $params=FALSE, $index=FALSE ) {
+    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    iCalUtilityFunctions::_setMval( $this->categories, $value, $params, FALSE, $index );
+    return TRUE;
+ }
+/*********************************************************************************/
+/**
+ * Property Name: CLASS
+ */
+/**
+ * creates formatted output for calendar component property class
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 0.9.7 - 2006-11-20
+ * @return string
+ */
+  function createClass() {
+    if( empty( $this->class )) return FALSE;
+    if( empty( $this->class['value'] ))
+      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'CLASS' ) : FALSE;
+    $attributes = $this->_createParams( $this->class['params'] );
+    return $this->_createElement( 'CLASS', $attributes, $this->class['value'] );
+  }
+/**
+ * set calendar component property class
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-11-04
+ * @param string $value "PUBLIC" / "PRIVATE" / "CONFIDENTIAL" / iana-token / x-name
+ * @param array $params optional
+ * @return bool
+ */
+  function setClass( $value, $params=FALSE ) {
+    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    $this->class = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: COMMENT
+ */
+/**
+ * creates formatted output for calendar component property comment
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.2 - 2012-12-18
+ * @return string
+ */
+  function createComment() {
+    if( empty( $this->comment )) return FALSE;
+    $output = null;
+    foreach( $this->comment as $commentPart ) {
+      if( empty( $commentPart['value'] )) {
+        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'COMMENT' );
+        continue;
+      }
+      $attributes = $this->_createParams( $commentPart['params'], array( 'ALTREP', 'LANGUAGE' ));
+      $content    = iCalUtilityFunctions::_strrep( $commentPart['value'], $this->format, $this->nl );
+      $output    .= $this->_createElement( 'COMMENT', $attributes, $content );
+    }
+    return $output;
+  }
+/**
+ * set calendar component property comment
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-11-06
+ * @param string $value
+ * @param array $params, optional
+ * @param integer $index, optional
+ * @return bool
+ */
+  function setComment( $value, $params=FALSE, $index=FALSE ) {
+    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    iCalUtilityFunctions::_setMval( $this->comment, $value, $params, FALSE, $index );
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: COMPLETED
+ */
+/**
+ * creates formatted output for calendar component property completed
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-10-22
+ * @return string
+ */
+  function createCompleted( ) {
+    if( empty( $this->completed )) return FALSE;
+    if( !isset( $this->completed['value']['year'] )  &&
+        !isset( $this->completed['value']['month'] ) &&
+        !isset( $this->completed['value']['day'] )   &&
+        !isset( $this->completed['value']['hour'] )  &&
+        !isset( $this->completed['value']['min'] )   &&
+        !isset( $this->completed['value']['sec'] ))
+      if( $this->getConfig( 'allowEmpty' ))
+        return $this->_createElement( 'COMPLETED' );
+      else return FALSE;
+    $formatted  = iCalUtilityFunctions::_date2strdate( $this->completed['value'], 7 );
+    $attributes = $this->_createParams( $this->completed['params'] );
+    return $this->_createElement( 'COMPLETED', $attributes, $formatted );
+  }
+/**
+ * set calendar component property completed
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-10-23
+ * @param mixed $year
+ * @param mixed $month optional
+ * @param int $day optional
+ * @param int $hour optional
+ * @param int $min optional
+ * @param int $sec optional
+ * @param array $params optional
+ * @return bool
+ */
+  function setCompleted( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
+    if( empty( $year )) {
+      if( $this->getConfig( 'allowEmpty' )) {
+        $this->completed = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
+        return TRUE;
+      }
+      else
+        return FALSE;
+    }
+    $this->completed = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: CONTACT
+ */
+/**
+ * creates formatted output for calendar component property contact
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.2 - 2012-12-18
+ * @return string
+ */
+  function createContact() {
+    if( empty( $this->contact )) return FALSE;
+    $output = null;
+    foreach( $this->contact as $contact ) {
+      if( !empty( $contact['value'] )) {
+        $attributes = $this->_createParams( $contact['params'], array( 'ALTREP', 'LANGUAGE' ));
+        $content    = iCalUtilityFunctions::_strrep( $contact['value'], $this->format, $this->nl );
+        $output    .= $this->_createElement( 'CONTACT', $attributes, $content );
+      }
+      elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'CONTACT' );
+    }
+    return $output;
+  }
+/**
+ * set calendar component property contact
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-11-05
+ * @param string $value
+ * @param array $params, optional
+ * @param integer $index, optional
+ * @return bool
+ */
+  function setContact( $value, $params=FALSE, $index=FALSE ) {
+    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    iCalUtilityFunctions::_setMval( $this->contact, $value, $params, FALSE, $index );
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: CREATED
+ */
+/**
+ * creates formatted output for calendar component property created
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-10-21
+ * @return string
+ */
+  function createCreated() {
+    if( empty( $this->created )) return FALSE;
+    $formatted  = iCalUtilityFunctions::_date2strdate( $this->created['value'], 7 );
+    $attributes = $this->_createParams( $this->created['params'] );
+    return $this->_createElement( 'CREATED', $attributes, $formatted );
+  }
+/**
+ * set calendar component property created
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-10-23
+ * @param mixed $year optional
+ * @param mixed $month optional
+ * @param int $day optional
+ * @param int $hour optional
+ * @param int $min optional
+ * @param int $sec optional
+ * @param mixed $params optional
+ * @return bool
+ */
+  function setCreated( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
+    if( !isset( $year )) {
+      $year = date('Ymd\THis', mktime( date( 'H' ), date( 'i' ), date( 's' ) - date( 'Z'), date( 'm' ), date( 'd' ), date( 'Y' )));
+    }
+    $this->created = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: DESCRIPTION
+ */
+/**
+ * creates formatted output for calendar component property description
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.2 - 2012-12-18
+ * @return string
+ */
+  function createDescription() {
+    if( empty( $this->description )) return FALSE;
+    $output       = null;
+    foreach( $this->description as $description ) {
+      if( !empty( $description['value'] )) {
+        $attributes = $this->_createParams( $description['params'], array( 'ALTREP', 'LANGUAGE' ));
+        $content    = iCalUtilityFunctions::_strrep( $description['value'], $this->format, $this->nl );
+        $output    .= $this->_createElement( 'DESCRIPTION', $attributes, $content );
+      }
+      elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'DESCRIPTION' );
+    }
+    return $output;
+  }
+/**
+ * set calendar component property description
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.6.24 - 2010-11-06
+ * @param string $value
+ * @param array $params, optional
+ * @param integer $index, optional
+ * @return bool
+ */
+  function setDescription( $value, $params=FALSE, $index=FALSE ) {
+    if( empty( $value )) { if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; }
+    if( 'vjournal' != $this->objName )
+      $index = 1;
+    iCalUtilityFunctions::_setMval( $this->description, $value, $params, FALSE, $index );
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: DTEND
+ */
+/**
+ * creates formatted output for calendar component property dtend
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.4 - 2012-09-26
+ * @return string
+ */
+  function createDtend() {
+    if( empty( $this->dtend )) return FALSE;
+    if( !isset( $this->dtend['value']['year'] )  &&
+        !isset( $this->dtend['value']['month'] ) &&
+        !isset( $this->dtend['value']['day'] )   &&
+        !isset( $this->dtend['value']['hour'] )  &&
+        !isset( $this->dtend['value']['min'] )   &&
+        !isset( $this->dtend['value']['sec'] ))
+      if( $this->getConfig( 'allowEmpty' ))
+        return $this->_createElement( 'DTEND' );
+      else return FALSE;
+    $parno      = ( isset( $this->dtend['params']['VALUE'] ) && ( 'DATE' == $this->dtend['params']['VALUE'] )) ? 3 : null;
+    $formatted  = iCalUtilityFunctions::_date2strdate( $this->dtend['value'], $parno );
+    $attributes = $this->_createParams( $this->dtend['params'] );
+    return $this->_createElement( 'DTEND', $attributes, $formatted );
+  }
+/**
+ * set calendar component property dtend
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.9.6 - 2011-05-14
+ * @param mixed $year
+ * @param mixed $month optional
+ * @param int $day optional
+ * @param int $hour optional
+ * @param int $min optional
+ * @param int $sec optional
+ * @param string $tz optional
+ * @param array params optional
+ * @return bool
+ */
+  function setDtend( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
+    if( empty( $year )) {
+      if( $this->getConfig( 'allowEmpty' )) {
+        $this->dtend = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
+        return TRUE;
+      }
+      else
+        return FALSE;
+    }
+    $this->dtend = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: DTSTAMP
+ */
+/**
+ * creates formatted output for calendar component property dtstamp
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.4 - 2008-03-07
+ * @return string
+ */
+  function createDtstamp() {
+    if( !isset( $this->dtstamp['value']['year'] )  &&
+        !isset( $this->dtstamp['value']['month'] ) &&
+        !isset( $this->dtstamp['value']['day'] )   &&
+        !isset( $this->dtstamp['value']['hour'] )  &&
+        !isset( $this->dtstamp['value']['min'] )   &&
+        !isset( $this->dtstamp['value']['sec'] ))
+      $this->_makeDtstamp();
+    $formatted  = iCalUtilityFunctions::_date2strdate( $this->dtstamp['value'], 7 );
+    $attributes = $this->_createParams( $this->dtstamp['params'] );
+    return $this->_createElement( 'DTSTAMP', $attributes, $formatted );
+  }
+/**
+ * computes datestamp for calendar component object instance dtstamp
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.1 - 2012-09-29
+ * @return void
+ */
+  function _makeDtstamp() {
+    $d    = date( 'Y-m-d-H-i-s', mktime( date('H'), date('i'), (date('s') - date( 'Z' )), date('m'), date('d'), date('Y')));
+    $date = explode( '-', $d );
+    $this->dtstamp['value'] = array( 'year' => $date[0], 'month' => $date[1], 'day' => $date[2], 'hour' => $date[3], 'min' => $date[4], 'sec' => $date[5], 'tz' => 'Z' );
+    $this->dtstamp['params'] = null;
+  }
+/**
+ * set calendar component property dtstamp
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-10-23
+ * @param mixed $year
+ * @param mixed $month optional
+ * @param int $day optional
+ * @param int $hour optional
+ * @param int $min optional
+ * @param int $sec optional
+ * @param array $params optional
+ * @return TRUE
+ */
+  function setDtstamp( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
+    if( empty( $year ))
+      $this->_makeDtstamp();
+    else
+      $this->dtstamp = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: DTSTART
+ */
+/**
+ * creates formatted output for calendar component property dtstart
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.4 - 2012-09-26
+ * @return string
+ */
+  function createDtstart() {
+    if( empty( $this->dtstart )) return FALSE;
+    if( !isset( $this->dtstart['value']['year'] )  &&
+        !isset( $this->dtstart['value']['month'] ) &&
+        !isset( $this->dtstart['value']['day'] )   &&
+        !isset( $this->dtstart['value']['hour'] )  &&
+        !isset( $this->dtstart['value']['min'] )   &&
+        !isset( $this->dtstart['value']['sec'] )) {
+      if( $this->getConfig( 'allowEmpty' ))
+        return $this->_createElement( 'DTSTART' );
+      else return FALSE;
+    }
+    if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' )))
+       unset( $this->dtstart['value']['tz'], $this->dtstart['params']['TZID'] );
+    $parno      = ( isset( $this->dtstart['params']['VALUE'] ) && ( 'DATE' == $this->dtstart['params']['VALUE'] )) ? 3 : null;
+    $formatted  = iCalUtilityFunctions::_date2strdate( $this->dtstart['value'], $parno );
+    $attributes = $this->_createParams( $this->dtstart['params'] );
+    return $this->_createElement( 'DTSTART', $attributes, $formatted );
+  }
+/**
+ * set calendar component property dtstart
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.6.22 - 2010-09-22
+ * @param mixed $year
+ * @param mixed $month optional
+ * @param int $day optional
+ * @param int $hour optional
+ * @param int $min optional
+ * @param int $sec optional
+ * @param string $tz optional
+ * @param array $params optional
+ * @return bool
+ */
+  function setDtstart( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
+    if( empty( $year )) {
+      if( $this->getConfig( 'allowEmpty' )) {
+        $this->dtstart = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
+        return TRUE;
+      }
+      else
+        return FALSE;
+    }
+    $this->dtstart = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, 'dtstart', $this->objName, $this->getConfig( 'TZID' ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: DUE
+ */
+/**
+ * creates formatted output for calendar component property due
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.4 - 2012-09-26
+ * @return string
+ */
+  function createDue() {
+    if( empty( $this->due )) return FALSE;
+    if( !isset( $this->due['value']['year'] )  &&
+        !isset( $this->due['value']['month'] ) &&
+        !isset( $this->due['value']['day'] )   &&
+        !isset( $this->due['value']['hour'] )  &&
+        !isset( $this->due['value']['min'] )   &&
+        !isset( $this->due['value']['sec'] )) {
+      if( $this->getConfig( 'allowEmpty' ))
+        return $this->_createElement( 'DUE' );
+      else
+       return FALSE;
+    }
+    $parno      = ( isset( $this->due['params']['VALUE'] ) && ( 'DATE' == $this->due['params']['VALUE'] )) ? 3 : null;
+    $formatted  = iCalUtilityFunctions::_date2strdate( $this->due['value'], $parno );
+    $attributes = $this->_createParams( $this->due['params'] );
+    return $this->_createElement( 'DUE', $attributes, $formatted );
+  }
+/**
+ * set calendar component property due
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-11-04
+ * @param mixed $year
+ * @param mixed $month optional
+ * @param int $day optional
+ * @param int $hour optional
+ * @param int $min optional
+ * @param int $sec optional
+ * @param array $params optional
+ * @return bool
+ */
+  function setDue( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
+    if( empty( $year )) {
+      if( $this->getConfig( 'allowEmpty' )) {
+        $this->due = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
+        return TRUE;
+      }
+      else
+        return FALSE;
+    }
+    $this->due = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: DURATION
+ */
+/**
+ * creates formatted output for calendar component property duration
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-10-21
+ * @return string
+ */
+  function createDuration() {
+    if( empty( $this->duration )) return FALSE;
+    if( !isset( $this->duration['value']['week'] ) &&
+        !isset( $this->duration['value']['day'] )  &&
+        !isset( $this->duration['value']['hour'] ) &&
+        !isset( $this->duration['value']['min'] )  &&
+        !isset( $this->duration['value']['sec'] ))
+      if( $this->getConfig( 'allowEmpty' ))
+        return $this->_createElement( 'DURATION', array(), null );
+      else return FALSE;
+    $attributes = $this->_createParams( $this->duration['params'] );
+    return $this->_createElement( 'DURATION', $attributes, iCalUtilityFunctions::_duration2str( $this->duration['value'] ));
+  }
+/**
+ * set calendar component property duration
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-11-04
+ * @param mixed $week
+ * @param mixed $day optional
+ * @param int $hour optional
+ * @param int $min optional
+ * @param int $sec optional
+ * @param array $params optional
+ * @return bool
+ */
+  function setDuration( $week, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
+    if( empty( $week )) if( $this->getConfig( 'allowEmpty' )) $week = null; else return FALSE;
+    if( is_array( $week ) && ( 1 <= count( $week )))
+      $this->duration = array( 'value' => iCalUtilityFunctions::_duration2arr( $week ), 'params' => iCalUtilityFunctions::_setParams( $day ));
+    elseif( is_string( $week ) && ( 3 <= strlen( trim( $week )))) {
+      $week = trim( $week );
+      if( in_array( substr( $week, 0, 1 ), array( '+', '-' )))
+        $week = substr( $week, 1 );
+      $this->duration = array( 'value' => iCalUtilityFunctions::_durationStr2arr( $week ), 'params' => iCalUtilityFunctions::_setParams( $day ));
+    }
+    elseif( empty( $week ) && empty( $day ) && empty( $hour ) && empty( $min ) && empty( $sec ))
+      return FALSE;
+    else
+      $this->duration = array( 'value' => iCalUtilityFunctions::_duration2arr( array( $week, $day, $hour, $min, $sec )), 'params' => iCalUtilityFunctions::_setParams( $params ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: EXDATE
+ */
+/**
+ * creates formatted output for calendar component property exdate
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.5 - 2012-12-28
+ * @return string
+ */
+  function createExdate() {
+    if( empty( $this->exdate )) return FALSE;
+    $output  = null;
+    $exdates = array();
+    foreach( $this->exdate as $theExdate ) {
+      if( empty( $theExdate['value'] )) {
+        if( $this->getConfig( 'allowEmpty' ))
+          $output .= $this->_createElement( 'EXDATE' );
+        continue;
+      }
+      if( 1 < count( $theExdate['value'] ))
+        usort( $theExdate['value'], array( 'iCalUtilityFunctions', '_sortExdate1' ));
+      $exdates[] = $theExdate;
+    }
+    if( 1 < count( $exdates ))
+      usort( $exdates, array( 'iCalUtilityFunctions', '_sortExdate2' ));
+    foreach( $exdates as $theExdate ) {
+      $content = $attributes = null;
+      foreach( $theExdate['value'] as $eix => $exdatePart ) {
+        $parno = count( $exdatePart );
+        $formatted = iCalUtilityFunctions::_date2strdate( $exdatePart, $parno );
+        if( isset( $theExdate['params']['TZID'] ))
+          $formatted = str_replace( 'Z', '', $formatted);
+        if( 0 < $eix ) {
+          if( isset( $theExdate['value'][0]['tz'] )) {
+            if( ctype_digit( substr( $theExdate['value'][0]['tz'], -4 )) ||
+               ( 'Z' == $theExdate['value'][0]['tz'] )) {
+              if( 'Z' != substr( $formatted, -1 ))
+                $formatted .= 'Z';
+            }
+            else
+              $formatted = str_replace( 'Z', '', $formatted );
+          }
+          else
+            $formatted = str_replace( 'Z', '', $formatted );
+        } // end if( 0 < $eix )
+        $content .= ( 0 < $eix ) ? ','.$formatted : $formatted;
+      } // end foreach( $theExdate['value'] as $eix => $exdatePart )
+      $attributes .= $this->_createParams( $theExdate['params'] );
+      $output .= $this->_createElement( 'EXDATE', $attributes, $content );
+    } // end foreach( $exdates as $theExdate )
+    return $output;
+  }
+/**
+ * set calendar component property exdate
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.1 - 2012-10-02
+ * @param array exdates
+ * @param array $params, optional
+ * @param integer $index, optional
+ * @return bool
+ */
+  function setExdate( $exdates, $params=FALSE, $index=FALSE ) {
+    if( empty( $exdates )) {
+      if( $this->getConfig( 'allowEmpty' )) {
+        iCalUtilityFunctions::_setMval( $this->exdate, null, $params, FALSE, $index );
+        return TRUE;
+      }
+      else
+        return FALSE;
+    }
+    $input  = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )));
+    $toZ = ( isset( $input['params']['TZID'] ) && in_array( strtoupper( $input['params']['TZID'] ), array( 'GMT', 'UTC', 'Z' ))) ? TRUE : FALSE;
+            /* ev. check 1:st date and save ev. timezone **/
+    iCalUtilityFunctions::_chkdatecfg( reset( $exdates ), $parno, $input['params'] );
+    iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default parameter
+    foreach( $exdates as $eix => $theExdate ) {
+      iCalUtilityFunctions::_strDate2arr( $theExdate );
+      if( iCalUtilityFunctions::_isArrayTimestampDate( $theExdate )) {
+        if( isset( $theExdate['tz'] ) && !iCalUtilityFunctions::_isOffset( $theExdate['tz'] )) {
+          if( isset( $input['params']['TZID'] ))
+            $theExdate['tz'] = $input['params']['TZID'];
+          else
+            $input['params']['TZID'] = $theExdate['tz'];
+        }
+        $exdatea = iCalUtilityFunctions::_timestamp2date( $theExdate, $parno );
+      }
+      elseif(  is_array( $theExdate )) {
+        $d = iCalUtilityFunctions::_chkDateArr( $theExdate, $parno );
+        if( isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) {
+          $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
+          $exdatea = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
+          unset( $exdatea['unparsedtext'] );
+        }
+        else
+          $exdatea = $d;
+      }
+      elseif( 8 <= strlen( trim( $theExdate ))) { // ex. 2006-08-03 10:12:18
+        $exdatea = iCalUtilityFunctions::_strdate2date( $theExdate, $parno );
+        unset( $exdatea['unparsedtext'] );
+      }
+      if( 3 == $parno )
+        unset( $exdatea['hour'], $exdatea['min'], $exdatea['sec'], $exdatea['tz'] );
+      elseif( isset( $exdatea['tz'] ))
+        $exdatea['tz'] = (string) $exdatea['tz'];
+      if(  isset( $input['params']['TZID'] ) ||
+         ( isset( $exdatea['tz'] ) && !iCalUtilityFunctions::_isOffset( $exdatea['tz'] )) ||
+         ( isset( $input['value'][0] ) && ( !isset( $input['value'][0]['tz'] ))) ||
+         ( isset( $input['value'][0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value'][0]['tz'] )))
+        unset( $exdatea['tz'] );
+      if( $toZ ) // time zone Z
+        $exdatea['tz'] = 'Z';
+      $input['value'][] = $exdatea;
+    }
+    if( 0 >= count( $input['value'] ))
+      return FALSE;
+    if( 3 == $parno ) {
+      $input['params']['VALUE'] = 'DATE';
+      unset( $input['params']['TZID'] );
+    }
+    if( $toZ ) // time zone Z
+      unset( $input['params']['TZID'] );
+    iCalUtilityFunctions::_setMval( $this->exdate, $input['value'], $input['params'], FALSE, $index );
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: EXRULE
+ */
+/**
+ * creates formatted output for calendar component property exrule
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-10-22
+ * @return string
+ */
+  function createExrule() {
+    if( empty( $this->exrule )) return FALSE;
+    return $this->_format_recur( 'EXRULE', $this->exrule );
+  }
+/**
+ * set calendar component property exdate
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-11-05
+ * @param array $exruleset
+ * @param array $params, optional
+ * @param integer $index, optional
+ * @return bool
+ */
+  function setExrule( $exruleset, $params=FALSE, $index=FALSE ) {
+    if( empty( $exruleset )) if( $this->getConfig( 'allowEmpty' )) $exruleset = null; else return FALSE;
+    iCalUtilityFunctions::_setMval( $this->exrule, iCalUtilityFunctions::_setRexrule( $exruleset ), $params, FALSE, $index );
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: FREEBUSY
+ */
+/**
+ * creates formatted output for calendar component property freebusy
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.1.23 - 2012-02-16
+ * @return string
+ */
+  function createFreebusy() {
+    if( empty( $this->freebusy )) return FALSE;
+    $output = null;
+    foreach( $this->freebusy as $freebusyPart ) {
+      if( empty( $freebusyPart['value'] ) || (( 1 == count( $freebusyPart['value'] )) && isset( $freebusyPart['value']['fbtype'] ))) {
+        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'FREEBUSY' );
+        continue;
+      }
+      $attributes = $content = null;
+      if( isset( $freebusyPart['value']['fbtype'] )) {
+          $attributes .= $this->intAttrDelimiter.'FBTYPE='.$freebusyPart['value']['fbtype'];
+        unset( $freebusyPart['value']['fbtype'] );
+        $freebusyPart['value'] = array_values( $freebusyPart['value'] );
+      }
+      else
+        $attributes .= $this->intAttrDelimiter.'FBTYPE=BUSY';
+      $attributes .= $this->_createParams( $freebusyPart['params'] );
+      $fno = 1;
+      $cnt = count( $freebusyPart['value']);
+      foreach( $freebusyPart['value'] as $periodix => $freebusyPeriod ) {
+        $formatted   = iCalUtilityFunctions::_date2strdate( $freebusyPeriod[0] );
+        $content .= $formatted;
+        $content .= '/';
+        $cnt2 = count( $freebusyPeriod[1]);
+        if( array_key_exists( 'year', $freebusyPeriod[1] ))      // date-time
+          $cnt2 = 7;
+        elseif( array_key_exists( 'week', $freebusyPeriod[1] ))  // duration
+          $cnt2 = 5;
+        if(( 7 == $cnt2 )   &&    // period=  -> date-time
+            isset( $freebusyPeriod[1]['year'] )  &&
+            isset( $freebusyPeriod[1]['month'] ) &&
+            isset( $freebusyPeriod[1]['day'] )) {
+          $content .= iCalUtilityFunctions::_date2strdate( $freebusyPeriod[1] );
+        }
+        else {                                  // period=  -> dur-time
+          $content .= iCalUtilityFunctions::_duration2str( $freebusyPeriod[1] );
+        }
+        if( $fno < $cnt )
+          $content .= ',';
+        $fno++;
+      }
+      $output .= $this->_createElement( 'FREEBUSY', $attributes, $content );
+    }
+    return $output;
+  }
+/**
+ * set calendar component property freebusy
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.10.30 - 2012-01-16
+ * @param string $fbType
+ * @param array $fbValues
+ * @param array $params, optional
+ * @param integer $index, optional
+ * @return bool
+ */
+  function setFreebusy( $fbType, $fbValues, $params=FALSE, $index=FALSE ) {
+    if( empty( $fbValues )) {
+      if( $this->getConfig( 'allowEmpty' )) {
+        iCalUtilityFunctions::_setMval( $this->freebusy, null, $params, FALSE, $index );
+        return TRUE;
+      }
+      else
+        return FALSE;
+    }
+    $fbType = strtoupper( $fbType );
+    if(( !in_array( $fbType, array( 'FREE', 'BUSY', 'BUSY-UNAVAILABLE', 'BUSY-TENTATIVE' ))) &&
+       ( 'X-' != substr( $fbType, 0, 2 )))
+      $fbType = 'BUSY';
+    $input = array( 'fbtype' => $fbType );
+    foreach( $fbValues as $fbPeriod ) {   // periods => period
+      if( empty( $fbPeriod ))
+        continue;
+      $freebusyPeriod = array();
+      foreach( $fbPeriod as $fbMember ) { // pairs => singlepart
+        $freebusyPairMember = array();
+        if( is_array( $fbMember )) {
+          if( iCalUtilityFunctions::_isArrayDate( $fbMember )) { // date-time value
+            $freebusyPairMember       = iCalUtilityFunctions::_chkDateArr( $fbMember, 7 );
+            $freebusyPairMember['tz'] = 'Z';
+          }
+          elseif( iCalUtilityFunctions::_isArrayTimestampDate( $fbMember )) { // timestamp value
+            $freebusyPairMember       = iCalUtilityFunctions::_timestamp2date( $fbMember['timestamp'], 7 );
+            $freebusyPairMember['tz'] = 'Z';
+          }
+          else {                                         // array format duration
+            $freebusyPairMember = iCalUtilityFunctions::_duration2arr( $fbMember );
+          }
+        }
+        elseif(( 3 <= strlen( trim( $fbMember ))) &&    // string format duration
+               ( in_array( $fbMember{0}, array( 'P', '+', '-' )))) {
+          if( 'P' != $fbMember{0} )
+            $fbmember = substr( $fbMember, 1 );
+          $freebusyPairMember = iCalUtilityFunctions::_durationStr2arr( $fbMember );
+        }
+        elseif( 8 <= strlen( trim( $fbMember ))) { // text date ex. 2006-08-03 10:12:18
+          $freebusyPairMember       = iCalUtilityFunctions::_strdate2date( $fbMember, 7 );
+          unset( $freebusyPairMember['unparsedtext'] );
+          $freebusyPairMember['tz'] = 'Z';
+        }
+        $freebusyPeriod[]   = $freebusyPairMember;
+      }
+      $input[]              = $freebusyPeriod;
+    }
+    iCalUtilityFunctions::_setMval( $this->freebusy, $input, $params, FALSE, $index );
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: GEO
+ */
+/**
+ * creates formatted output for calendar component property geo
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.12.6 - 2012-04-21
+ * @return string
+ */
+  function createGeo() {
+    if( empty( $this->geo )) return FALSE;
+    if( empty( $this->geo['value'] ))
+      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'GEO' ) : FALSE;
+    $attributes = $this->_createParams( $this->geo['params'] );
+    if( 0.0 < $this->geo['value']['latitude'] )
+      $sign   = '+';
+    else
+      $sign   = ( 0.0 > $this->geo['value']['latitude'] ) ? '-' : '';
+    $content  = $sign.sprintf( "%09.6f", abs( $this->geo['value']['latitude'] ));       // sprintf && lpad && float && sign !"#¤%&/(
+    $content  = rtrim( rtrim( $content, '0' ), '.' );
+    if( 0.0 < $this->geo['value']['longitude'] )
+      $sign   = '+';
+    else
+      $sign   = ( 0.0 > $this->geo['value']['longitude'] ) ? '-' : '';
+    $content .= ';'.$sign.sprintf( '%8.6f', abs( $this->geo['value']['longitude'] ));   // sprintf && lpad && float && sign !"#¤%&/(
+    $content  = rtrim( rtrim( $content, '0' ), '.' );
+    return $this->_createElement( 'GEO', $attributes, $content );
+  }
+/**
+ * set calendar component property geo
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.12.5 - 2012-04-21
+ * @param float $latitude
+ * @param float $longitude
+ * @param array $params optional
+ * @return bool
+ */
+  function setGeo( $latitude, $longitude, $params=FALSE ) {
+    if(( !empty( $latitude )  || ( 0 == $latitude )) &&
+       ( !empty( $longitude ) || ( 0 == $longitude ))) {
+      if( !is_array( $this->geo )) $this->geo = array();
+      $this->geo['value']['latitude']  = (float) $latitude;
+      $this->geo['value']['longitude'] = (float) $longitude;
+      $this->geo['params'] = iCalUtilityFunctions::_setParams( $params );
+    }
+    elseif( $this->getConfig( 'allowEmpty' ))
+      $this->geo = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ) );
+    else
+      return FALSE;
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: LAST-MODIFIED
+ */
+/**
+ * creates formatted output for calendar component property last-modified
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-10-21
+ * @return string
+ */
+  function createLastModified() {
+    if( empty( $this->lastmodified )) return FALSE;
+    $attributes = $this->_createParams( $this->lastmodified['params'] );
+    $formatted  = iCalUtilityFunctions::_date2strdate( $this->lastmodified['value'], 7 );
+    return $this->_createElement( 'LAST-MODIFIED', $attributes, $formatted );
+  }
+/**
+ * set calendar component property completed
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-10-23
+ * @param mixed $year optional
+ * @param mixed $month optional
+ * @param int $day optional
+ * @param int $hour optional
+ * @param int $min optional
+ * @param int $sec optional
+ * @param array $params optional
+ * @return boll
+ */
+  function setLastModified( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
+    if( empty( $year ))
+      $year = date('Ymd\THis', mktime( date( 'H' ), date( 'i' ), date( 's' ) - date( 'Z'), date( 'm' ), date( 'd' ), date( 'Y' )));
+    $this->lastmodified = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: LOCATION
+ */
+/**
+ * creates formatted output for calendar component property location
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.2 - 2012-12-18
+ * @return string
+ */
+  function createLocation() {
+    if( empty( $this->location )) return FALSE;
+    if( empty( $this->location['value'] ))
+      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'LOCATION' ) : FALSE;
+    $attributes = $this->_createParams( $this->location['params'], array( 'ALTREP', 'LANGUAGE' ));
+    $content    = iCalUtilityFunctions::_strrep( $this->location['value'], $this->format, $this->nl );
+    return $this->_createElement( 'LOCATION', $attributes, $content );
+  }
+/**
+ * set calendar component property location
+ '
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-11-04
+ * @param string $value
+ * @param array params optional
+ * @return bool
+ */
+  function setLocation( $value, $params=FALSE ) {
+    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    $this->location = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: ORGANIZER
+ */
+/**
+ * creates formatted output for calendar component property organizer
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.6.33 - 2010-12-17
+ * @return string
+ */
+  function createOrganizer() {
+    if( empty( $this->organizer )) return FALSE;
+    if( empty( $this->organizer['value'] ))
+      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'ORGANIZER' ) : FALSE;
+    $attributes = $this->_createParams( $this->organizer['params']
+                                      , array( 'CN', 'DIR', 'SENT-BY', 'LANGUAGE' ));
+    return $this->_createElement( 'ORGANIZER', $attributes, $this->organizer['value'] );
+  }
+/**
+ * set calendar component property organizer
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.12.18 - 2012-07-13
+ * @param string $value
+ * @param array params optional
+ * @return bool
+ */
+  function setOrganizer( $value, $params=FALSE ) {
+    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    if( !empty( $value )) {
+      if( FALSE === ( $pos = strpos( substr( $value, 0, 9 ), ':' )))
+        $value = 'MAILTO:'.$value;
+      elseif( !empty( $value ))
+        $value = strtolower( substr( $value, 0, $pos )).substr( $value, $pos );
+      $value = str_replace( 'mailto:', 'MAILTO:', $value );
+    }
+    $this->organizer = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
+    if( isset( $this->organizer['params']['SENT-BY'] )){
+      if( 'mailto:' !== strtolower( substr( $this->organizer['params']['SENT-BY'], 0, 7 )))
+        $this->organizer['params']['SENT-BY'] = 'MAILTO:'.$this->organizer['params']['SENT-BY'];
+      else
+        $this->organizer['params']['SENT-BY'] = 'MAILTO:'.substr( $this->organizer['params']['SENT-BY'], 7 );
+    }
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: PERCENT-COMPLETE
+ */
+/**
+ * creates formatted output for calendar component property percent-complete
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.9.3 - 2011-05-14
+ * @return string
+ */
+  function createPercentComplete() {
+    if( !isset($this->percentcomplete) || ( empty( $this->percentcomplete ) && !is_numeric( $this->percentcomplete ))) return FALSE;
+    if( !isset( $this->percentcomplete['value'] ) || ( empty( $this->percentcomplete['value'] ) && !is_numeric( $this->percentcomplete['value'] )))
+      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PERCENT-COMPLETE' ) : FALSE;
+    $attributes = $this->_createParams( $this->percentcomplete['params'] );
+    return $this->_createElement( 'PERCENT-COMPLETE', $attributes, $this->percentcomplete['value'] );
+  }
+/**
+ * set calendar component property percent-complete
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.9.3 - 2011-05-14
+ * @param int $value
+ * @param array $params optional
+ * @return bool
+ */
+  function setPercentComplete( $value, $params=FALSE ) {
+    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    $this->percentcomplete = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: PRIORITY
+ */
+/**
+ * creates formatted output for calendar component property priority
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.9.3 - 2011-05-14
+ * @return string
+ */
+  function createPriority() {
+    if( !isset($this->priority) || ( empty( $this->priority ) && !is_numeric( $this->priority ))) return FALSE;
+    if( !isset( $this->priority['value'] ) || ( empty( $this->priority['value'] ) && !is_numeric( $this->priority['value'] )))
+      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PRIORITY' ) : FALSE;
+    $attributes = $this->_createParams( $this->priority['params'] );
+    return $this->_createElement( 'PRIORITY', $attributes, $this->priority['value'] );
+  }
+/**
+ * set calendar component property priority
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.9.3 - 2011-05-14
+ * @param int $value
+ * @param array $params optional
+ * @return bool
+ */
+  function setPriority( $value, $params=FALSE  ) {
+    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    $this->priority = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: RDATE
+ */
+/**
+ * creates formatted output for calendar component property rdate
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.9 - 2013-01-09
+ * @return string
+ */
+  function createRdate() {
+    if( empty( $this->rdate )) return FALSE;
+    $utctime = ( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE;
+    $output = null;
+    $rdates = array();
+    foreach( $this->rdate as $rpix => $theRdate ) {
+      if( empty( $theRdate['value'] )) {
+        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'RDATE' );
+        continue;
+      }
+      if( $utctime  )
+        unset( $theRdate['params']['TZID'] );
+      if( 1 < count( $theRdate['value'] ))
+        usort( $theRdate['value'], array( 'iCalUtilityFunctions', '_sortRdate1' ));
+      $rdates[] = $theRdate;
+    }
+    if( 1 < count( $rdates ))
+      usort( $rdates, array( 'iCalUtilityFunctions', '_sortRdate2' ));
+    foreach( $rdates as $rpix => $theRdate ) {
+      $attributes = $this->_createParams( $theRdate['params'] );
+      $cnt = count( $theRdate['value'] );
+      $content = null;
+      $rno = 1;
+      foreach( $theRdate['value'] as $rix => $rdatePart ) {
+        $contentPart = null;
+        if( is_array( $rdatePart ) &&
+            isset( $theRdate['params']['VALUE'] ) && ( 'PERIOD' == $theRdate['params']['VALUE'] )) { // PERIOD
+          if( $utctime )
+            unset( $rdatePart[0]['tz'] );
+          $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart[0] ); // PERIOD part 1
+          if( $utctime || !empty( $theRdate['params']['TZID'] ))
+            $formatted = str_replace( 'Z', '', $formatted);
+          $contentPart .= $formatted;
+          $contentPart .= '/';
+          $cnt2 = count( $rdatePart[1]);
+          if( array_key_exists( 'year', $rdatePart[1] )) {
+            if( array_key_exists( 'hour', $rdatePart[1] ))
+              $cnt2 = 7;                                      // date-time
+            else
+              $cnt2 = 3;                                      // date
+          }
+          elseif( array_key_exists( 'week', $rdatePart[1] ))  // duration
+            $cnt2 = 5;
+          if(( 7 == $cnt2 )   &&    // period=  -> date-time
+              isset( $rdatePart[1]['year'] )  &&
+              isset( $rdatePart[1]['month'] ) &&
+              isset( $rdatePart[1]['day'] )) {
+            if( $utctime )
+              unset( $rdatePart[1]['tz'] );
+            $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart[1] ); // PERIOD part 2
+            if( $utctime || !empty( $theRdate['params']['TZID'] ))
+              $formatted = str_replace( 'Z', '', $formatted );
+           $contentPart .= $formatted;
+          }
+          else {                                  // period=  -> dur-time
+            $contentPart .= iCalUtilityFunctions::_duration2str( $rdatePart[1] );
+          }
+        } // PERIOD end
+        else { // SINGLE date start
+          if( $utctime )
+            unset( $rdatePart['tz'] );
+          $parno = ( isset( $theRdate['params']['VALUE'] ) && ( 'DATE' == isset( $theRdate['params']['VALUE'] ))) ? 3 : null;
+          $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart, $parno );
+          if( $utctime || !empty( $theRdate['params']['TZID'] ))
+            $formatted = str_replace( 'Z', '', $formatted);
+          $contentPart .= $formatted;
+        }
+        $content .= $contentPart;
+        if( $rno < $cnt )
+          $content .= ',';
+        $rno++;
+      }
+      $output    .= $this->_createElement( 'RDATE', $attributes, $content );
+    }
+    return $output;
+  }
+/**
+ * set calendar component property rdate
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.1 - 2012-10-04
+ * @param array $rdates
+ * @param array $params, optional
+ * @param integer $index, optional
+ * @return bool
+ */
+  function setRdate( $rdates, $params=FALSE, $index=FALSE ) {
+    if( empty( $rdates )) {
+      if( $this->getConfig( 'allowEmpty' )) {
+        iCalUtilityFunctions::_setMval( $this->rdate, null, $params, FALSE, $index );
+        return TRUE;
+      }
+      else
+        return FALSE;
+    }
+    $input = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )));
+    if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) {
+      unset( $input['params']['TZID'] );
+      $input['params']['VALUE'] = 'DATE-TIME';
+    }
+    $zArr = array( 'GMT', 'UTC', 'Z' );
+    $toZ = ( isset( $params['TZID'] ) && in_array( strtoupper( $params['TZID'] ), $zArr )) ? TRUE : FALSE;
+            /*  check if PERIOD, if not set */
+    if((!isset( $input['params']['VALUE'] ) || !in_array( $input['params']['VALUE'], array( 'DATE', 'PERIOD' ))) &&
+          isset( $rdates[0] )    && is_array( $rdates[0] ) && ( 2 == count( $rdates[0] )) &&
+          isset( $rdates[0][0] ) &&    isset( $rdates[0][1] ) && !isset( $rdates[0]['timestamp'] ) &&
+    (( is_array( $rdates[0][0] ) && ( isset( $rdates[0][0]['timestamp'] ) ||
+                                      iCalUtilityFunctions::_isArrayDate( $rdates[0][0] ))) ||
+                                    ( is_string( $rdates[0][0] ) && ( 8 <= strlen( trim( $rdates[0][0] )))))  &&
+     ( is_array( $rdates[0][1] ) || ( is_string( $rdates[0][1] ) && ( 3 <= strlen( trim( $rdates[0][1] ))))))
+      $input['params']['VALUE'] = 'PERIOD';
+            /* check 1:st date, upd. $parno (opt) and save ev. timezone **/
+    $date  = reset( $rdates );
+    if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) // PERIOD
+      $date  = reset( $date );
+    iCalUtilityFunctions::_chkdatecfg( $date, $parno, $input['params'] );
+    iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default
+    foreach( $rdates as $rpix => $theRdate ) {
+      $inputa = null;
+      iCalUtilityFunctions::_strDate2arr( $theRdate );
+      if( is_array( $theRdate )) {
+        if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) { // PERIOD
+          foreach( $theRdate as $rix => $rPeriod ) {
+            iCalUtilityFunctions::_strDate2arr( $theRdate );
+            if( is_array( $rPeriod )) {
+              if( iCalUtilityFunctions::_isArrayTimestampDate( $rPeriod )) {    // timestamp
+                if( isset( $rPeriod['tz'] ) && !iCalUtilityFunctions::_isOffset( $rPeriod['tz'] )) {
+                  if( isset( $input['params']['TZID'] ))
+                    $rPeriod['tz'] = $input['params']['TZID'];
+                  else
+                    $input['params']['TZID'] = $rPeriod['tz'];
+                }
+                $inputab = iCalUtilityFunctions::_timestamp2date( $rPeriod, $parno );
+              }
+              elseif( iCalUtilityFunctions::_isArrayDate( $rPeriod )) {
+                $d = ( 3 < count ( $rPeriod )) ? iCalUtilityFunctions::_chkDateArr( $rPeriod, $parno ) : iCalUtilityFunctions::_chkDateArr( $rPeriod, 6 );
+                if( isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) {
+                  $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
+                  $inputab = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
+                  unset( $inputab['unparsedtext'] );
+                }
+                else
+                  $inputab = $d;
+              }
+              elseif (( 1 == count( $rPeriod )) && ( 8 <= strlen( reset( $rPeriod )))) { // text-date
+                $inputab   = iCalUtilityFunctions::_strdate2date( reset( $rPeriod ), $parno );
+                unset( $inputab['unparsedtext'] );
+              }
+              else                                               // array format duration
+                $inputab   = iCalUtilityFunctions::_duration2arr( $rPeriod );
+            }
+            elseif(( 3 <= strlen( trim( $rPeriod ))) &&          // string format duration
+                   ( in_array( $rPeriod[0], array( 'P', '+', '-' )))) {
+              if( 'P' != $rPeriod[0] )
+                $rPeriod   = substr( $rPeriod, 1 );
+              $inputab     = iCalUtilityFunctions::_durationStr2arr( $rPeriod );
+            }
+            elseif( 8 <= strlen( trim( $rPeriod ))) {            // text date ex. 2006-08-03 10:12:18
+              $inputab     = iCalUtilityFunctions::_strdate2date( $rPeriod, $parno );
+              unset( $inputab['unparsedtext'] );
+            }
+            if(( 0 == $rpix ) && ( 0 == $rix )) {
+              if( isset( $inputab['tz'] ) && in_array( strtoupper( $inputab['tz'] ), $zArr )) {
+                $inputab['tz'] = 'Z';
+                $toZ = TRUE;
+              }
+            }
+            else {
+              if( isset( $inputa[0]['tz'] ) && ( 'Z' == $inputa[0]['tz'] ) && isset( $inputab['year'] ))
+                $inputab['tz'] = 'Z';
+              else
+                unset( $inputab['tz'] );
+            }
+            if( $toZ && isset( $inputab['year'] ) )
+              $inputab['tz'] = 'Z';
+            $inputa[]      = $inputab;
+          }
+        } // PERIOD end
+        elseif ( iCalUtilityFunctions::_isArrayTimestampDate( $theRdate )) {    // timestamp
+          if( isset( $theRdate['tz'] ) && !iCalUtilityFunctions::_isOffset( $theRdate['tz'] )) {
+            if( isset( $input['params']['TZID'] ))
+              $theRdate['tz'] = $input['params']['TZID'];
+            else
+              $input['params']['TZID'] = $theRdate['tz'];
+          }
+          $inputa = iCalUtilityFunctions::_timestamp2date( $theRdate, $parno );
+        }
+        else {                                                                  // date[-time]
+          $inputa = iCalUtilityFunctions::_chkDateArr( $theRdate, $parno );
+          if( isset( $inputa['tz'] ) && ( 'Z' != $inputa['tz'] ) && iCalUtilityFunctions::_isOffset( $inputa['tz'] )) {
+            $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $inputa['year'], $inputa['month'], $inputa['day'], $inputa['hour'], $inputa['min'], $inputa['sec'], $inputa['tz'] );
+            $inputa  = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
+            unset( $inputa['unparsedtext'] );
+          }
+        }
+      }
+      elseif( 8 <= strlen( trim( $theRdate ))) {                 // text date ex. 2006-08-03 10:12:18
+        $inputa       = iCalUtilityFunctions::_strdate2date( $theRdate, $parno );
+        unset( $inputa['unparsedtext'] );
+        if( $toZ )
+          $inputa['tz'] = 'Z';
+      }
+      if( !isset( $input['params']['VALUE'] ) || ( 'PERIOD' != $input['params']['VALUE'] )) { // no PERIOD
+        if(( 0 == $rpix ) && !$toZ )
+          $toZ = ( isset( $inputa['tz'] ) && in_array( strtoupper( $inputa['tz'] ), $zArr )) ? TRUE : FALSE;
+        if( $toZ )
+          $inputa['tz']    = 'Z';
+        if( 3 == $parno )
+          unset( $inputa['hour'], $inputa['min'], $inputa['sec'], $inputa['tz'] );
+        elseif( isset( $inputa['tz'] ))
+          $inputa['tz']    = (string) $inputa['tz'];
+        if( isset( $input['params']['TZID'] ) || ( isset( $input['value'][0] ) && ( !isset( $input['value'][0]['tz'] ))))
+          if( !$toZ )
+            unset( $inputa['tz'] );
+      }
+      $input['value'][]    = $inputa;
+    }
+    if( 3 == $parno ) {
+      $input['params']['VALUE'] = 'DATE';
+      unset( $input['params']['TZID'] );
+    }
+    if( $toZ )
+      unset( $input['params']['TZID'] );
+    iCalUtilityFunctions::_setMval( $this->rdate, $input['value'], $input['params'], FALSE, $index );
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: RECURRENCE-ID
+ */
+/**
+ * creates formatted output for calendar component property recurrence-id
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.4 - 2012-09-26
+ * @return string
+ */
+  function createRecurrenceid() {
+    if( empty( $this->recurrenceid )) return FALSE;
+    if( empty( $this->recurrenceid['value'] ))
+      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'RECURRENCE-ID' ) : FALSE;
+    $parno      = ( isset( $this->recurrenceid['params']['VALUE'] ) && ( 'DATE' == $this->recurrenceid['params']['VALUE'] )) ? 3 : null;
+    $formatted  = iCalUtilityFunctions::_date2strdate( $this->recurrenceid['value'], $parno );
+    $attributes = $this->_createParams( $this->recurrenceid['params'] );
+    return $this->_createElement( 'RECURRENCE-ID', $attributes, $formatted );
+  }
+/**
+ * set calendar component property recurrence-id
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.9.6 - 2011-05-15
+ * @param mixed $year
+ * @param mixed $month optional
+ * @param int $day optional
+ * @param int $hour optional
+ * @param int $min optional
+ * @param int $sec optional
+ * @param array $params optional
+ * @return bool
+ */
+  function setRecurrenceid( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
+    if( empty( $year )) {
+      if( $this->getConfig( 'allowEmpty' )) {
+        $this->recurrenceid = array( 'value' => null, 'params' => null );
+        return TRUE;
+      }
+      else
+        return FALSE;
+    }
+    $this->recurrenceid = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: RELATED-TO
+ */
+/**
+ * creates formatted output for calendar component property related-to
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.2 - 2012-12-18
+ * @return string
+ */
+  function createRelatedTo() {
+    if( empty( $this->relatedto )) return FALSE;
+    $output = null;
+    foreach( $this->relatedto as $relation ) {
+      if( !empty( $relation['value'] ))
+        $output .= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] ), iCalUtilityFunctions::_strrep( $relation['value'], $this->format, $this->nl ));
+      elseif( $this->getConfig( 'allowEmpty' ))
+        $output .= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] ));
+    }
+    return $output;
+  }
+/**
+ * set calendar component property related-to
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.11.24 - 2012-02-23
+ * @param float $relid
+ * @param array $params, optional
+ * @param index $index, optional
+ * @return bool
+ */
+  function setRelatedTo( $value, $params=FALSE, $index=FALSE ) {
+    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    iCalUtilityFunctions::_existRem( $params, 'RELTYPE', 'PARENT', TRUE ); // remove default
+    iCalUtilityFunctions::_setMval( $this->relatedto, $value, $params, FALSE, $index );
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: REPEAT
+ */
+/**
+ * creates formatted output for calendar component property repeat
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.9.3 - 2011-05-14
+ * @return string
+ */
+  function createRepeat() {
+    if( !isset( $this->repeat ) || ( empty( $this->repeat ) && !is_numeric( $this->repeat ))) return FALSE;
+    if( !isset( $this->repeat['value']) || ( empty( $this->repeat['value'] ) && !is_numeric( $this->repeat['value'] )))
+      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'REPEAT' ) : FALSE;
+    $attributes = $this->_createParams( $this->repeat['params'] );
+    return $this->_createElement( 'REPEAT', $attributes, $this->repeat['value'] );
+  }
+/**
+ * set calendar component property repeat
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.9.3 - 2011-05-14
+ * @param string $value
+ * @param array $params optional
+ * @return void
+ */
+  function setRepeat( $value, $params=FALSE ) {
+    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    $this->repeat = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: REQUEST-STATUS
+ */
+/**
+ * creates formatted output for calendar component property request-status
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.2 - 2012-12-18
+ * @return string
+ */
+  function createRequestStatus() {
+    if( empty( $this->requeststatus )) return FALSE;
+    $output = null;
+    foreach( $this->requeststatus as $rstat ) {
+      if( empty( $rstat['value']['statcode'] )) {
+        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'REQUEST-STATUS' );
+        continue;
+      }
+      $attributes  = $this->_createParams( $rstat['params'], array( 'LANGUAGE' ));
+      $content     = number_format( (float) $rstat['value']['statcode'], 2, '.', '');
+      $content    .= ';'.iCalUtilityFunctions::_strrep( $rstat['value']['text'], $this->format, $this->nl );
+      if( isset( $rstat['value']['extdata'] ))
+        $content  .= ';'.iCalUtilityFunctions::_strrep( $rstat['value']['extdata'], $this->format, $this->nl );
+      $output     .= $this->_createElement( 'REQUEST-STATUS', $attributes, $content );
+    }
+    return $output;
+  }
+/**
+ * set calendar component property request-status
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-11-05
+ * @param float $statcode
+ * @param string $text
+ * @param string $extdata, optional
+ * @param array $params, optional
+ * @param integer $index, optional
+ * @return bool
+ */
+  function setRequestStatus( $statcode, $text, $extdata=FALSE, $params=FALSE, $index=FALSE ) {
+    if( empty( $statcode ) || empty( $text )) if( $this->getConfig( 'allowEmpty' )) $statcode = $text = null; else return FALSE;
+    $input              = array( 'statcode' => $statcode, 'text' => $text );
+    if( $extdata )
+      $input['extdata'] = $extdata;
+    iCalUtilityFunctions::_setMval( $this->requeststatus, $input, $params, FALSE, $index );
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: RESOURCES
+ */
+/**
+ * creates formatted output for calendar component property resources
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.2 - 2012-12-18
+ * @return string
+ */
+  function createResources() {
+    if( empty( $this->resources )) return FALSE;
+    $output = null;
+    foreach( $this->resources as $resource ) {
+      if( empty( $resource['value'] )) {
+        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'RESOURCES' );
+        continue;
+      }
+      $attributes  = $this->_createParams( $resource['params'], array( 'ALTREP', 'LANGUAGE' ));
+      if( is_array( $resource['value'] )) {
+        foreach( $resource['value'] as $rix => $resourcePart )
+          $resource['value'][$rix] = iCalUtilityFunctions::_strrep( $resourcePart, $this->format, $this->nl );
+        $content   = implode( ',', $resource['value'] );
+      }
+      else
+        $content   = iCalUtilityFunctions::_strrep( $resource['value'], $this->format, $this->nl );
+      $output     .= $this->_createElement( 'RESOURCES', $attributes, $content );
+    }
+    return $output;
+  }
+/**
+ * set calendar component property recources
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-11-05
+ * @param mixed $value
+ * @param array $params, optional
+ * @param integer $index, optional
+ * @return bool
+ */
+  function setResources( $value, $params=FALSE, $index=FALSE ) {
+    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    iCalUtilityFunctions::_setMval( $this->resources, $value, $params, FALSE, $index );
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: RRULE
+ */
+/**
+ * creates formatted output for calendar component property rrule
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-10-21
+ * @return string
+ */
+  function createRrule() {
+    if( empty( $this->rrule )) return FALSE;
+    return $this->_format_recur( 'RRULE', $this->rrule );
+  }
+/**
+ * set calendar component property rrule
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-11-05
+ * @param array $rruleset
+ * @param array $params, optional
+ * @param integer $index, optional
+ * @return void
+ */
+  function setRrule( $rruleset, $params=FALSE, $index=FALSE ) {
+    if( empty( $rruleset )) if( $this->getConfig( 'allowEmpty' )) $rruleset = null; else return FALSE;
+    iCalUtilityFunctions::_setMval( $this->rrule, iCalUtilityFunctions::_setRexrule( $rruleset ), $params, FALSE, $index );
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: SEQUENCE
+ */
+/**
+ * creates formatted output for calendar component property sequence
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.9.3 - 2011-05-14
+ * @return string
+ */
+  function createSequence() {
+    if( !isset( $this->sequence ) || ( empty( $this->sequence ) && !is_numeric( $this->sequence ))) return FALSE;
+    if(( !isset($this->sequence['value'] ) || ( empty( $this->sequence['value'] ) && !is_numeric( $this->sequence['value'] ))) &&
+       ( '0' != $this->sequence['value'] ))
+      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SEQUENCE' ) : FALSE;
+    $attributes = $this->_createParams( $this->sequence['params'] );
+    return $this->_createElement( 'SEQUENCE', $attributes, $this->sequence['value'] );
+  }
+/**
+ * set calendar component property sequence
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.10.8 - 2011-09-19
+ * @param int $value optional
+ * @param array $params optional
+ * @return bool
+ */
+  function setSequence( $value=FALSE, $params=FALSE ) {
+    if(( empty( $value ) && !is_numeric( $value )) && ( '0' != $value ))
+      $value = ( isset( $this->sequence['value'] ) && ( -1 < $this->sequence['value'] )) ? $this->sequence['value'] + 1 : '0';
+    $this->sequence = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: STATUS
+ */
+/**
+ * creates formatted output for calendar component property status
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-10-21
+ * @return string
+ */
+  function createStatus() {
+    if( empty( $this->status )) return FALSE;
+    if( empty( $this->status['value'] ))
+      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'STATUS' ) : FALSE;
+    $attributes = $this->_createParams( $this->status['params'] );
+    return $this->_createElement( 'STATUS', $attributes, $this->status['value'] );
+  }
+/**
+ * set calendar component property status
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-11-04
+ * @param string $value
+ * @param array $params optional
+ * @return bool
+ */
+  function setStatus( $value, $params=FALSE ) {
+    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    $this->status = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: SUMMARY
+ */
+/**
+ * creates formatted output for calendar component property summary
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.2 - 2012-12-18
+ * @return string
+ */
+  function createSummary() {
+    if( empty( $this->summary )) return FALSE;
+    if( empty( $this->summary['value'] ))
+      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SUMMARY' ) : FALSE;
+    $attributes = $this->_createParams( $this->summary['params'], array( 'ALTREP', 'LANGUAGE' ));
+    $content    = iCalUtilityFunctions::_strrep( $this->summary['value'], $this->format, $this->nl );
+    return $this->_createElement( 'SUMMARY', $attributes, $content );
+  }
+/**
+ * set calendar component property summary
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-11-04
+ * @param string $value
+ * @param string $params optional
+ * @return bool
+ */
+  function setSummary( $value, $params=FALSE ) {
+    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    $this->summary = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: TRANSP
+ */
+/**
+ * creates formatted output for calendar component property transp
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-10-21
+ * @return string
+ */
+  function createTransp() {
+    if( empty( $this->transp )) return FALSE;
+    if( empty( $this->transp['value'] ))
+      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TRANSP' ) : FALSE;
+    $attributes = $this->_createParams( $this->transp['params'] );
+    return $this->_createElement( 'TRANSP', $attributes, $this->transp['value'] );
+  }
+/**
+ * set calendar component property transp
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-11-04
+ * @param string $value
+ * @param string $params optional
+ * @return bool
+ */
+  function setTransp( $value, $params=FALSE ) {
+    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    $this->transp = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: TRIGGER
+ */
+/**
+ * creates formatted output for calendar component property trigger
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.16 - 2008-10-21
+ * @return string
+ */
+  function createTrigger() {
+    if( empty( $this->trigger )) return FALSE;
+    if( empty( $this->trigger['value'] ))
+      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TRIGGER' ) : FALSE;
+    $content = $attributes = null;
+    if( isset( $this->trigger['value']['year'] )   &&
+        isset( $this->trigger['value']['month'] )  &&
+        isset( $this->trigger['value']['day'] ))
+      $content      .= iCalUtilityFunctions::_date2strdate( $this->trigger['value'] );
+    else {
+      if( TRUE !== $this->trigger['value']['relatedStart'] )
+        $attributes .= $this->intAttrDelimiter.'RELATED=END';
+      if( $this->trigger['value']['before'] )
+        $content    .= '-';
+      $content      .= iCalUtilityFunctions::_duration2str( $this->trigger['value'] );
+    }
+    $attributes     .= $this->_createParams( $this->trigger['params'] );
+    return $this->_createElement( 'TRIGGER', $attributes, $content );
+  }
+/**
+ * set calendar component property trigger
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.1 - 2012-09-20
+ * @param mixed $year
+ * @param mixed $month optional
+ * @param int $day optional
+ * @param int $week optional
+ * @param int $hour optional
+ * @param int $min optional
+ * @param int $sec optional
+ * @param bool $relatedStart optional
+ * @param bool $before optional
+ * @param array $params optional
+ * @return bool
+ */
+  function setTrigger( $year, $month=null, $day=null, $week=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $relatedStart=TRUE, $before=TRUE, $params=FALSE ) {
+    if( empty( $year ) && empty( $month ) && empty( $day ) && empty( $week ) && empty( $hour ) && empty( $min ) && empty( $sec ))
+      if( $this->getConfig( 'allowEmpty' )) {
+        $this->trigger = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ) );
+        return TRUE;
+      }
+      else
+        return FALSE;
+    if( iCalUtilityFunctions::_isArrayTimestampDate( $year )) { // timestamp UTC
+      $params = iCalUtilityFunctions::_setParams( $month );
+      $date   = iCalUtilityFunctions::_timestamp2date( $year, 7 );
+      foreach( $date as $k => $v )
+        $$k = $v;
+    }
+    elseif( is_array( $year ) && ( is_array( $month ) || empty( $month ))) {
+      $params = iCalUtilityFunctions::_setParams( $month );
+      if(!(array_key_exists( 'year',  $year ) &&   // exclude date-time
+           array_key_exists( 'month', $year ) &&
+           array_key_exists( 'day',   $year ))) {  // when this must be a duration
+        if( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] )))
+          $relatedStart = FALSE;
+        else
+          $relatedStart = ( array_key_exists( 'relatedStart', $year ) && ( TRUE !== $year['relatedStart'] )) ? FALSE : TRUE;
+        $before         = ( array_key_exists( 'before', $year )       && ( TRUE !== $year['before'] ))       ? FALSE : TRUE;
+      }
+      $SSYY  = ( array_key_exists( 'year',  $year )) ? $year['year']  : null;
+      $month = ( array_key_exists( 'month', $year )) ? $year['month'] : null;
+      $day   = ( array_key_exists( 'day',   $year )) ? $year['day']   : null;
+      $week  = ( array_key_exists( 'week',  $year )) ? $year['week']  : null;
+      $hour  = ( array_key_exists( 'hour',  $year )) ? $year['hour']  : 0; //null;
+      $min   = ( array_key_exists( 'min',   $year )) ? $year['min']   : 0; //null;
+      $sec   = ( array_key_exists( 'sec',   $year )) ? $year['sec']   : 0; //null;
+      $year  = $SSYY;
+    }
+    elseif(is_string( $year ) && ( is_array( $month ) || empty( $month ))) {  // duration or date in a string
+      $params = iCalUtilityFunctions::_setParams( $month );
+      if( in_array( $year[0], array( 'P', '+', '-' ))) { // duration
+        $relatedStart = ( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] ))) ? FALSE : TRUE;
+        $before       = ( '-'  == $year[0] ) ? TRUE : FALSE;
+        if(     'P'  != $year[0] )
+          $year       = substr( $year, 1 );
+        $date         = iCalUtilityFunctions::_durationStr2arr( $year);
+      }
+      else   // date
+        $date    = iCalUtilityFunctions::_strdate2date( $year, 7 );
+      unset( $year, $month, $day, $date['unparsedtext'] );
+      if( empty( $date ))
+        $sec = 0;
+      else
+        foreach( $date as $k => $v )
+          $$k = $v;
+    }
+    else // single values in function input parameters
+      $params = iCalUtilityFunctions::_setParams( $params );
+    if( !empty( $year ) && !empty( $month ) && !empty( $day )) { // date
+      $params['VALUE'] = 'DATE-TIME';
+      $hour = ( $hour ) ? $hour : 0;
+      $min  = ( $min  ) ? $min  : 0;
+      $sec  = ( $sec  ) ? $sec  : 0;
+      $this->trigger = array( 'params' => $params );
+      $this->trigger['value'] = array( 'year'  => $year
+                                     , 'month' => $month
+                                     , 'day'   => $day
+                                     , 'hour'  => $hour
+                                     , 'min'   => $min
+                                     , 'sec'   => $sec
+                                     , 'tz'    => 'Z' );
+      return TRUE;
+    }
+    elseif(( empty( $year ) && empty( $month )) &&    // duration
+           (( !empty( $week ) || ( 0 == $week )) ||
+            ( !empty( $day )  || ( 0 == $day  )) ||
+            ( !empty( $hour ) || ( 0 == $hour )) ||
+            ( !empty( $min )  || ( 0 == $min  )) ||
+            ( !empty( $sec )  || ( 0 == $sec  )))) {
+      unset( $params['RELATED'] ); // set at output creation (END only)
+      unset( $params['VALUE'] );   // 'DURATION' default
+      $this->trigger = array( 'params' => $params );
+      $this->trigger['value']  = array();
+      if( !empty( $week )) $this->trigger['value']['week'] = $week;
+      if( !empty( $day  )) $this->trigger['value']['day']  = $day;
+      if( !empty( $hour )) $this->trigger['value']['hour'] = $hour;
+      if( !empty( $min  )) $this->trigger['value']['min']  = $min;
+      if( !empty( $sec  )) $this->trigger['value']['sec']  = $sec;
+      if( empty( $this->trigger['value'] )) {
+        $this->trigger['value']['sec'] = 0;
+        $before                        = FALSE;
+      }
+      $relatedStart = ( FALSE !== $relatedStart ) ? TRUE : FALSE;
+      $before       = ( FALSE !== $before )       ? TRUE : FALSE;
+      $this->trigger['value']['relatedStart'] = $relatedStart;
+      $this->trigger['value']['before']       = $before;
+      return TRUE;
+    }
+    return FALSE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: TZID
+ */
+/**
+ * creates formatted output for calendar component property tzid
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.2 - 2012-12-18
+ * @return string
+ */
+  function createTzid() {
+    if( empty( $this->tzid )) return FALSE;
+    if( empty( $this->tzid['value'] ))
+      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZID' ) : FALSE;
+    $attributes = $this->_createParams( $this->tzid['params'] );
+    return $this->_createElement( 'TZID', $attributes, iCalUtilityFunctions::_strrep( $this->tzid['value'], $this->format, $this->nl ));
+  }
+/**
+ * set calendar component property tzid
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-11-04
+ * @param string $value
+ * @param array $params optional
+ * @return bool
+ */
+  function setTzid( $value, $params=FALSE ) {
+    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    $this->tzid = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * .. .
+ * Property Name: TZNAME
+ */
+/**
+ * creates formatted output for calendar component property tzname
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.2 - 2012-12-18
+ * @return string
+ */
+  function createTzname() {
+    if( empty( $this->tzname )) return FALSE;
+    $output = null;
+    foreach( $this->tzname as $theName ) {
+      if( !empty( $theName['value'] )) {
+        $attributes = $this->_createParams( $theName['params'], array( 'LANGUAGE' ));
+        $output    .= $this->_createElement( 'TZNAME', $attributes, iCalUtilityFunctions::_strrep( $theName['value'], $this->format, $this->nl ));
+      }
+      elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'TZNAME' );
+    }
+    return $output;
+  }
+/**
+ * set calendar component property tzname
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-11-05
+ * @param string $value
+ * @param string $params, optional
+ * @param integer $index, optional
+ * @return bool
+ */
+  function setTzname( $value, $params=FALSE, $index=FALSE ) {
+    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    iCalUtilityFunctions::_setMval( $this->tzname, $value, $params, FALSE, $index );
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: TZOFFSETFROM
+ */
+/**
+ * creates formatted output for calendar component property tzoffsetfrom
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-10-21
+ * @return string
+ */
+  function createTzoffsetfrom() {
+    if( empty( $this->tzoffsetfrom )) return FALSE;
+    if( empty( $this->tzoffsetfrom['value'] ))
+      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZOFFSETFROM' ) : FALSE;
+    $attributes = $this->_createParams( $this->tzoffsetfrom['params'] );
+    return $this->_createElement( 'TZOFFSETFROM', $attributes, $this->tzoffsetfrom['value'] );
+  }
+/**
+ * set calendar component property tzoffsetfrom
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-11-04
+ * @param string $value
+ * @param string $params optional
+ * @return bool
+ */
+  function setTzoffsetfrom( $value, $params=FALSE ) {
+    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    $this->tzoffsetfrom = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: TZOFFSETTO
+ */
+/**
+ * creates formatted output for calendar component property tzoffsetto
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-10-21
+ * @return string
+ */
+  function createTzoffsetto() {
+    if( empty( $this->tzoffsetto )) return FALSE;
+    if( empty( $this->tzoffsetto['value'] ))
+      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZOFFSETTO' ) : FALSE;
+    $attributes = $this->_createParams( $this->tzoffsetto['params'] );
+    return $this->_createElement( 'TZOFFSETTO', $attributes, $this->tzoffsetto['value'] );
+  }
+/**
+ * set calendar component property tzoffsetto
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-11-04
+ * @param string $value
+ * @param string $params optional
+ * @return bool
+ */
+  function setTzoffsetto( $value, $params=FALSE ) {
+    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    $this->tzoffsetto = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: TZURL
+ */
+/**
+ * creates formatted output for calendar component property tzurl
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-10-21
+ * @return string
+ */
+  function createTzurl() {
+    if( empty( $this->tzurl )) return FALSE;
+    if( empty( $this->tzurl['value'] ))
+      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZURL' ) : FALSE;
+    $attributes = $this->_createParams( $this->tzurl['params'] );
+    return $this->_createElement( 'TZURL', $attributes, $this->tzurl['value'] );
+  }
+/**
+ * set calendar component property tzurl
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-11-04
+ * @param string $value
+ * @param string $params optional
+ * @return boll
+ */
+  function setTzurl( $value, $params=FALSE ) {
+    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    $this->tzurl = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: UID
+ */
+/**
+ * creates formatted output for calendar component property uid
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 0.9.7 - 2006-11-20
+ * @return string
+ */
+  function createUid() {
+    if( 0 >= count( $this->uid ))
+      $this->_makeuid();
+    $attributes = $this->_createParams( $this->uid['params'] );
+    return $this->_createElement( 'UID', $attributes, $this->uid['value'] );
+  }
+/**
+ * create an unique id for this calendar component object instance
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.2.7 - 2007-09-04
+ * @return void
+ */
+  function _makeUid() {
+    $date   = date('Ymd\THisT');
+    $unique = substr(microtime(), 2, 4);
+    $base   = 'aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPrRsStTuUvVxXuUvVwWzZ1234567890';
+    $start  = 0;
+    $end    = strlen( $base ) - 1;
+    $length = 6;
+    $str    = null;
+    for( $p = 0; $p < $length; $p++ )
+      $unique .= $base{mt_rand( $start, $end )};
+    $this->uid = array( 'params' => null );
+    $this->uid['value']  = $date.'-'.$unique.'@'.$this->getConfig( 'unique_id' );
+  }
+/**
+ * set calendar component property uid
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-11-04
+ * @param string $value
+ * @param string $params optional
+ * @return bool
+ */
+  function setUid( $value, $params=FALSE ) {
+    if( empty( $value )) return FALSE; // no allowEmpty check here !!!!
+    $this->uid = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: URL
+ */
+/**
+ * creates formatted output for calendar component property url
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.8 - 2008-10-21
+ * @return string
+ */
+  function createUrl() {
+    if( empty( $this->url )) return FALSE;
+    if( empty( $this->url['value'] ))
+      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'URL' ) : FALSE;
+    $attributes = $this->_createParams( $this->url['params'] );
+    return $this->_createElement( 'URL', $attributes, $this->url['value'] );
+  }
+/**
+ * set calendar component property url
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.7 - 2013-01-11
+ * @param string $value
+ * @param string $params optional
+ * @return bool
+ */
+  function setUrl( $value, $params=FALSE ) {
+    if( !empty( $value )) {
+      if( !filter_var( $value, FILTER_VALIDATE_URL ) && ( 'urn' != strtolower( substr( $value, 0, 3 ))))
+        return FALSE;
+    }
+    elseif( $this->getConfig( 'allowEmpty' ))
+      $value = null;
+    else
+      return FALSE;
+    $this->url = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
+    return TRUE;
+  }
+/*********************************************************************************/
+/**
+ * Property Name: x-prop
+ */
+/**
+ * creates formatted output for calendar component property x-prop
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.2 - 2012-12-18
+ * @return string
+ */
+  function createXprop() {
+    if( empty( $this->xprop )) return FALSE;
+    $output = null;
+    foreach( $this->xprop as $label => $xpropPart ) {
+      if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) {
+        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $label );
+        continue;
+      }
+      $attributes = $this->_createParams( $xpropPart['params'], array( 'LANGUAGE' ));
+      if( is_array( $xpropPart['value'] )) {
+        foreach( $xpropPart['value'] as $pix => $theXpart )
+          $xpropPart['value'][$pix] = iCalUtilityFunctions::_strrep( $theXpart, $this->format, $this->format );
+        $xpropPart['value']  = implode( ',', $xpropPart['value'] );
+      }
+      else
+        $xpropPart['value'] = iCalUtilityFunctions::_strrep( $xpropPart['value'], $this->format, $this->nl );
+      $output    .= $this->_createElement( $label, $attributes, $xpropPart['value'] );
+    }
+    return $output;
+  }
+/**
+ * set calendar component property x-prop
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.11.9 - 2012-01-16
+ * @param string $label
+ * @param mixed $value
+ * @param array $params optional
+ * @return bool
+ */
+  function setXprop( $label, $value, $params=FALSE ) {
+    if( empty( $label ))
+      return FALSE;
+    if( 'X-' != strtoupper( substr( $label, 0, 2 )))
+      return FALSE;
+    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
+    $xprop           = array( 'value' => $value );
+    $xprop['params'] = iCalUtilityFunctions::_setParams( $params );
+    if( !is_array( $this->xprop )) $this->xprop = array();
+    $this->xprop[strtoupper( $label )] = $xprop;
+    return TRUE;
+  }
+/*********************************************************************************/
+/*********************************************************************************/
+/**
+ * create element format parts
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.0.6 - 2006-06-20
+ * @return string
+ */
+  function _createFormat() {
+    $objectname                   = null;
+    switch( $this->format ) {
+      case 'xcal':
+        $objectname               = ( isset( $this->timezonetype )) ?
+                                 strtolower( $this->timezonetype )  :  strtolower( $this->objName );
+        $this->componentStart1    = $this->elementStart1 = '<';
+        $this->componentStart2    = $this->elementStart2 = '>';
+        $this->componentEnd1      = $this->elementEnd1   = '</';
+        $this->componentEnd2      = $this->elementEnd2   = '>'.$this->nl;
+        $this->intAttrDelimiter   = '<!-- -->';
+        $this->attributeDelimiter = $this->nl;
+        $this->valueInit          = null;
+        break;
+      default:
+        $objectname               = ( isset( $this->timezonetype )) ?
+                                 strtoupper( $this->timezonetype )  :  strtoupper( $this->objName );
+        $this->componentStart1    = 'BEGIN:';
+        $this->componentStart2    = null;
+        $this->componentEnd1      = 'END:';
+        $this->componentEnd2      = $this->nl;
+        $this->elementStart1      = null;
+        $this->elementStart2      = null;
+        $this->elementEnd1        = null;
+        $this->elementEnd2        = $this->nl;
+        $this->intAttrDelimiter   = '<!-- -->';
+        $this->attributeDelimiter = ';';
+        $this->valueInit          = ':';
+        break;
+    }
+    return $objectname;
+  }
+/**
+ * creates formatted output for calendar component property
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.2 - 2012-12-18
+ * @param string $label property name
+ * @param string $attributes property attributes
+ * @param string $content property content (optional)
+ * @return string
+ */
+  function _createElement( $label, $attributes=null, $content=FALSE ) {
+    switch( $this->format ) {
+      case 'xcal':
+        $label = strtolower( $label );
+        break;
+      default:
+        $label = strtoupper( $label );
+        break;
+    }
+    $output = $this->elementStart1.$label;
+    $categoriesAttrLang = null;
+    $attachInlineBinary = FALSE;
+    $attachfmttype      = null;
+    if (( 'xcal' == $this->format) && ( 'x-' == substr( $label, 0, 2 ))) {
+      $this->xcaldecl[] = array( 'xmldecl'  => 'ELEMENT'
+                               , 'ref'      => $label
+                               , 'type2'    => '(#PCDATA)' );
+    }
+    if( !empty( $attributes ))  {
+      $attributes  = trim( $attributes );
+      if ( 'xcal' == $this->format ) {
+        $attributes2 = explode( $this->intAttrDelimiter, $attributes );
+        $attributes  = null;
+        foreach( $attributes2 as $aix => $attribute ) {
+          $attrKVarr = explode( '=', $attribute );
+          if( empty( $attrKVarr[0] ))
+            continue;
+          if( !isset( $attrKVarr[1] )) {
+            $attrValue = $attrKVarr[0];
+            $attrKey   = $aix;
+          }
+          elseif( 2 == count( $attrKVarr)) {
+            $attrKey   = strtolower( $attrKVarr[0] );
+            $attrValue = $attrKVarr[1];
+          }
+          else {
+            $attrKey   = strtolower( $attrKVarr[0] );
+            unset( $attrKVarr[0] );
+            $attrValue = implode( '=', $attrKVarr );
+          }
+          if(( 'attach' == $label ) && ( in_array( $attrKey, array( 'fmttype', 'encoding', 'value' )))) {
+            $attachInlineBinary = TRUE;
+            if( 'fmttype' == $attrKey )
+              $attachfmttype = $attrKey.'='.$attrValue;
+            continue;
+          }
+          elseif(( 'categories' == $label ) && ( 'language' == $attrKey ))
+            $categoriesAttrLang = $attrKey.'='.$attrValue;
+          else {
+            $attributes .= ( empty( $attributes )) ? ' ' : $this->attributeDelimiter.' ';
+            $attributes .= ( !empty( $attrKey )) ? $attrKey.'=' : null;
+            if(( '"' == substr( $attrValue, 0, 1 )) && ( '"' == substr( $attrValue, -1 ))) {
+              $attrValue = substr( $attrValue, 1, ( strlen( $attrValue ) - 2 ));
+              $attrValue = str_replace( '"', '', $attrValue );
+            }
+            $attributes .= '"'.htmlspecialchars( $attrValue ).'"';
+          }
+        }
+      }
+      else {
+        $attributes = str_replace( $this->intAttrDelimiter, $this->attributeDelimiter, $attributes );
+      }
+    }
+    if(( 'xcal' == $this->format) &&
+       ((( 'attach' == $label ) && !$attachInlineBinary ) || ( in_array( $label, array( 'tzurl', 'url' ))))) {
+      $pos = strrpos($content, "/");
+      $docname = ( $pos !== false) ? substr( $content, (1 - strlen( $content ) + $pos )) : $content;
+      $this->xcaldecl[] = array( 'xmldecl'  => 'ENTITY'
+                               , 'uri'      => $docname
+                               , 'ref'      => 'SYSTEM'
+                               , 'external' => $content
+                               , 'type'     => 'NDATA'
+                               , 'type2'    => 'BINERY' );
+      $attributes .= ( empty( $attributes )) ? ' ' : $this->attributeDelimiter.' ';
+      $attributes .= 'uri="'.$docname.'"';
+      $content = null;
+      if( 'attach' == $label ) {
+        $attributes = str_replace( $this->attributeDelimiter, $this->intAttrDelimiter, $attributes );
+        $content = $this->nl.$this->_createElement( 'extref', $attributes, null );
+        $attributes = null;
+      }
+    }
+    elseif(( 'xcal' == $this->format) && ( 'attach' == $label ) && $attachInlineBinary ) {
+      $content = $this->nl.$this->_createElement( 'b64bin', $attachfmttype, $content ); // max one attribute
+    }
+    $output .= $attributes;
+    if( !$content && ( '0' != $content )) {
+      switch( $this->format ) {
+        case 'xcal':
+          $output .= ' /';
+          $output .= $this->elementStart2.$this->nl;
+          return $output;
+          break;
+        default:
+          $output .= $this->elementStart2.$this->valueInit;
+          return iCalUtilityFunctions::_size75( $output, $this->nl );
+          break;
+      }
+    }
+    $output .= $this->elementStart2;
+    $output .= $this->valueInit.$content;
+    switch( $this->format ) {
+      case 'xcal':
+        return $output.$this->elementEnd1.$label.$this->elementEnd2;
+        break;
+      default:
+        return iCalUtilityFunctions::_size75( $output, $this->nl );
+        break;
+    }
+  }
+/**
+ * creates formatted output for calendar component property parameters
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.10.27 - 2012-01-16
+ * @param array $params  optional
+ * @param array $ctrKeys optional
+ * @return string
+ */
+  function _createParams( $params=array(), $ctrKeys=array() ) {
+    if( !is_array( $params ) || empty( $params ))
+      $params = array();
+    $attrLANG = $attr1 = $attr2 = $lang = null;
+    $CNattrKey   = ( in_array( 'CN',       $ctrKeys )) ? TRUE : FALSE ;
+    $LANGattrKey = ( in_array( 'LANGUAGE', $ctrKeys )) ? TRUE : FALSE ;
+    $CNattrExist = $LANGattrExist = FALSE;
+    $xparams = array();
+    foreach( $params as $paramKey => $paramValue ) {
+      if(( FALSE !== strpos( $paramValue, ':' )) ||
+         ( FALSE !== strpos( $paramValue, ';' )) ||
+         ( FALSE !== strpos( $paramValue, ',' )))
+        $paramValue = '"'.$paramValue.'"';
+      if( ctype_digit( (string) $paramKey )) {
+        $xparams[]          = $paramValue;
+        continue;
+      }
+      $paramKey             = strtoupper( $paramKey );
+      if( !in_array( $paramKey, array( 'ALTREP', 'CN', 'DIR', 'ENCODING', 'FMTTYPE', 'LANGUAGE', 'RANGE', 'RELTYPE', 'SENT-BY', 'TZID', 'VALUE' )))
+        $xparams[$paramKey] = $paramValue;
+      else
+        $params[$paramKey]  = $paramValue;
+    }
+    ksort( $xparams, SORT_STRING );
+    foreach( $xparams as $paramKey => $paramValue ) {
+      if( ctype_digit( (string) $paramKey ))
+        $attr2             .= $this->intAttrDelimiter.$paramValue;
+      else
+        $attr2             .= $this->intAttrDelimiter."$paramKey=$paramValue";
+    }
+    if( isset( $params['FMTTYPE'] )  && !in_array( 'FMTTYPE', $ctrKeys )) {
+      $attr1               .= $this->intAttrDelimiter.'FMTTYPE='.$params['FMTTYPE'].$attr2;
+      $attr2                = null;
+    }
+    if( isset( $params['ENCODING'] ) && !in_array( 'ENCODING',   $ctrKeys )) {
+      if( !empty( $attr2 )) {
+        $attr1             .= $attr2;
+        $attr2              = null;
+      }
+      $attr1               .= $this->intAttrDelimiter.'ENCODING='.$params['ENCODING'];
+    }
+    if( isset( $params['VALUE'] )    && !in_array( 'VALUE',   $ctrKeys ))
+      $attr1               .= $this->intAttrDelimiter.'VALUE='.$params['VALUE'];
+    if( isset( $params['TZID'] )     && !in_array( 'TZID',    $ctrKeys )) {
+      $attr1               .= $this->intAttrDelimiter.'TZID='.$params['TZID'];
+    }
+    if( isset( $params['RANGE'] )    && !in_array( 'RANGE',   $ctrKeys ))
+      $attr1               .= $this->intAttrDelimiter.'RANGE='.$params['RANGE'];
+    if( isset( $params['RELTYPE'] )  && !in_array( 'RELTYPE', $ctrKeys ))
+      $attr1               .= $this->intAttrDelimiter.'RELTYPE='.$params['RELTYPE'];
+    if( isset( $params['CN'] )       && $CNattrKey ) {
+      $attr1                = $this->intAttrDelimiter.'CN='.$params['CN'];
+      $CNattrExist          = TRUE;
+    }
+    if( isset( $params['DIR'] )      && in_array( 'DIR',      $ctrKeys )) {
+      $delim = ( FALSE !== strpos( $params['DIR'], '"' )) ? '' : '"';
+      $attr1               .= $this->intAttrDelimiter.'DIR='.$delim.$params['DIR'].$delim;
+    }
+    if( isset( $params['SENT-BY'] )  && in_array( 'SENT-BY',  $ctrKeys ))
+      $attr1               .= $this->intAttrDelimiter.'SENT-BY='.$params['SENT-BY'];
+    if( isset( $params['ALTREP'] )   && in_array( 'ALTREP',   $ctrKeys )) {
+      $delim = ( FALSE !== strpos( $params['ALTREP'], '"' )) ? '' : '"';
+      $attr1               .= $this->intAttrDelimiter.'ALTREP='.$delim.$params['ALTREP'].$delim;
+    }
+    if( isset( $params['LANGUAGE'] ) && $LANGattrKey ) {
+      $attrLANG            .= $this->intAttrDelimiter.'LANGUAGE='.$params['LANGUAGE'];
+      $LANGattrExist        = TRUE;
+    }
+    if( !$LANGattrExist ) {
+      $lang = $this->getConfig( 'language' );
+      if(( $CNattrExist || $LANGattrKey ) && $lang )
+        $attrLANG .= $this->intAttrDelimiter.'LANGUAGE='.$lang;
+    }
+    return $attr1.$attrLANG.$attr2;
+  }
+/**
+ * creates formatted output for calendar component property data value type recur
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.1 - 2012-10-06
+ * @param array $recurlabel
+ * @param array $recurdata
+ * @return string
+ */
+  function _format_recur( $recurlabel, $recurdata ) {
+    $output = null;
+    foreach( $recurdata as $therule ) {
+      if( empty( $therule['value'] )) {
+        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $recurlabel );
+        continue;
+      }
+      $attributes = ( isset( $therule['params'] )) ? $this->_createParams( $therule['params'] ) : null;
+      $content1  = $content2  = null;
+      foreach( $therule['value'] as $rulelabel => $rulevalue ) {
+        switch( $rulelabel ) {
+          case 'FREQ': {
+            $content1 .= "FREQ=$rulevalue";
+            break;
+          }
+          case 'UNTIL': {
+            $parno     = ( isset( $rulevalue['hour'] )) ? 7 : 3;
+            $content2 .= ';UNTIL='.iCalUtilityFunctions::_date2strdate( $rulevalue, $parno );
+            break;
+          }
+          case 'COUNT':
+          case 'INTERVAL':
+          case 'WKST': {
+            $content2 .= ";$rulelabel=$rulevalue";
+            break;
+          }
+          case 'BYSECOND':
+          case 'BYMINUTE':
+          case 'BYHOUR':
+          case 'BYMONTHDAY':
+          case 'BYYEARDAY':
+          case 'BYWEEKNO':
+          case 'BYMONTH':
+          case 'BYSETPOS': {
+            $content2 .= ";$rulelabel=";
+            if( is_array( $rulevalue )) {
+              foreach( $rulevalue as $vix => $valuePart ) {
+                $content2 .= ( $vix ) ? ',' : null;
+                $content2 .= $valuePart;
+              }
+            }
+            else
+             $content2 .= $rulevalue;
+            break;
+          }
+          case 'BYDAY': {
+            $content2 .= ";$rulelabel=";
+            $bydaycnt = 0;
+            foreach( $rulevalue as $vix => $valuePart ) {
+              $content21 = $content22 = null;
+              if( is_array( $valuePart )) {
+                $content2 .= ( $bydaycnt ) ? ',' : null;
+                foreach( $valuePart as $vix2 => $valuePart2 ) {
+                  if( 'DAY' != strtoupper( $vix2 ))
+                      $content21 .= $valuePart2;
+                  else
+                    $content22 .= $valuePart2;
+                }
+                $content2 .= $content21.$content22;
+                $bydaycnt++;
+              }
+              else {
+                $content2 .= ( $bydaycnt ) ? ',' : null;
+                if( 'DAY' != strtoupper( $vix ))
+                    $content21 .= $valuePart;
+                else {
+                  $content22 .= $valuePart;
+                  $bydaycnt++;
+                }
+                $content2 .= $content21.$content22;
+              }
+            }
+            break;
+          }
+          default: {
+            $content2 .= ";$rulelabel=$rulevalue";
+            break;
+          }
+        }
+      }
+      $output .= $this->_createElement( $recurlabel, $attributes, $content1.$content2 );
+    }
+    return $output;
+  }
+/**
+ * check if property not exists within component
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-10-15
+ * @param string $propName
+ * @return bool
+ */
+  function _notExistProp( $propName ) {
+    if( empty( $propName )) return FALSE; // when deleting x-prop, an empty propName may be used=allowed
+    $propName = strtolower( $propName );
+    if(     'last-modified'    == $propName )  { if( !isset( $this->lastmodified ))    return TRUE; }
+    elseif( 'percent-complete' == $propName )  { if( !isset( $this->percentcomplete )) return TRUE; }
+    elseif( 'recurrence-id'    == $propName )  { if( !isset( $this->recurrenceid ))    return TRUE; }
+    elseif( 'related-to'       == $propName )  { if( !isset( $this->relatedto ))       return TRUE; }
+    elseif( 'request-status'   == $propName )  { if( !isset( $this->requeststatus ))   return TRUE; }
+    elseif((       'x-' != substr($propName,0,2)) && !isset( $this->$propName ))       return TRUE;
+    return FALSE;
+  }
+/*********************************************************************************/
+/*********************************************************************************/
+/**
+ * get general component config variables or info about subcomponents
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.9.6 - 2011-05-14
+ * @param mixed $config
+ * @return value
+ */
+  function getConfig( $config = FALSE) {
+    if( !$config ) {
+      $return = array();
+      $return['ALLOWEMPTY']  = $this->getConfig( 'ALLOWEMPTY' );
+      $return['FORMAT']      = $this->getConfig( 'FORMAT' );
+      if( FALSE !== ( $lang  = $this->getConfig( 'LANGUAGE' )))
+        $return['LANGUAGE']  = $lang;
+      $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' );
+      $return['TZTD']        = $this->getConfig( 'TZID' );
+      $return['UNIQUE_ID']   = $this->getConfig( 'UNIQUE_ID' );
+      return $return;
+    }
+    switch( strtoupper( $config )) {
+      case 'ALLOWEMPTY':
+        return $this->allowEmpty;
+        break;
+      case 'COMPSINFO':
+        unset( $this->compix );
+        $info = array();
+        if( isset( $this->components )) {
+          foreach( $this->components as $cix => $component ) {
+            if( empty( $component )) continue;
+            $info[$cix]['ordno'] = $cix + 1;
+            $info[$cix]['type']  = $component->objName;
+            $info[$cix]['uid']   = $component->getProperty( 'uid' );
+            $info[$cix]['props'] = $component->getConfig( 'propinfo' );
+            $info[$cix]['sub']   = $component->getConfig( 'compsinfo' );
+          }
+        }
+        return $info;
+        break;
+      case 'FORMAT':
+        return $this->format;
+        break;
+      case 'LANGUAGE':
+         // get language for calendar component as defined in [RFC 1766]
+        return $this->language;
+        break;
+      case 'NL':
+      case 'NEWLINECHAR':
+        return $this->nl;
+        break;
+      case 'PROPINFO':
+        $output = array();
+        if( !in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) {
+          if( empty( $this->uid['value'] ))   $this->_makeuid();
+                                              $output['UID']              = 1;
+          if( empty( $this->dtstamp ))        $this->_makeDtstamp();
+                                              $output['DTSTAMP']          = 1;
+        }
+        if( !empty( $this->summary ))         $output['SUMMARY']          = 1;
+        if( !empty( $this->description ))     $output['DESCRIPTION']      = count( $this->description );
+        if( !empty( $this->dtstart ))         $output['DTSTART']          = 1;
+        if( !empty( $this->dtend ))           $output['DTEND']            = 1;
+        if( !empty( $this->due ))             $output['DUE']              = 1;
+        if( !empty( $this->duration ))        $output['DURATION']         = 1;
+        if( !empty( $this->rrule ))           $output['RRULE']            = count( $this->rrule );
+        if( !empty( $this->rdate ))           $output['RDATE']            = count( $this->rdate );
+        if( !empty( $this->exdate ))          $output['EXDATE']           = count( $this->exdate );
+        if( !empty( $this->exrule ))          $output['EXRULE']           = count( $this->exrule );
+        if( !empty( $this->action ))          $output['ACTION']           = 1;
+        if( !empty( $this->attach ))          $output['ATTACH']           = count( $this->attach );
+        if( !empty( $this->attendee ))        $output['ATTENDEE']         = count( $this->attendee );
+        if( !empty( $this->categories ))      $output['CATEGORIES']       = count( $this->categories );
+        if( !empty( $this->class ))           $output['CLASS']            = 1;
+        if( !empty( $this->comment ))         $output['COMMENT']          = count( $this->comment );
+        if( !empty( $this->completed ))       $output['COMPLETED']        = 1;
+        if( !empty( $this->contact ))         $output['CONTACT']          = count( $this->contact );
+        if( !empty( $this->created ))         $output['CREATED']          = 1;
+        if( !empty( $this->freebusy ))        $output['FREEBUSY']         = count( $this->freebusy );
+        if( !empty( $this->geo ))             $output['GEO']              = 1;
+        if( !empty( $this->lastmodified ))    $output['LAST-MODIFIED']    = 1;
+        if( !empty( $this->location ))        $output['LOCATION']         = 1;
+        if( !empty( $this->organizer ))       $output['ORGANIZER']        = 1;
+        if( !empty( $this->percentcomplete )) $output['PERCENT-COMPLETE'] = 1;
+        if( !empty( $this->priority ))        $output['PRIORITY']         = 1;
+        if( !empty( $this->recurrenceid ))    $output['RECURRENCE-ID']    = 1;
+        if( !empty( $this->relatedto ))       $output['RELATED-TO']       = count( $this->relatedto );
+        if( !empty( $this->repeat ))          $output['REPEAT']           = 1;
+        if( !empty( $this->requeststatus ))   $output['REQUEST-STATUS']   = count( $this->requeststatus );
+        if( !empty( $this->resources ))       $output['RESOURCES']        = count( $this->resources );
+        if( !empty( $this->sequence ))        $output['SEQUENCE']         = 1;
+        if( !empty( $this->sequence ))        $output['SEQUENCE']         = 1;
+        if( !empty( $this->status ))          $output['STATUS']           = 1;
+        if( !empty( $this->transp ))          $output['TRANSP']           = 1;
+        if( !empty( $this->trigger ))         $output['TRIGGER']          = 1;
+        if( !empty( $this->tzid ))            $output['TZID']             = 1;
+        if( !empty( $this->tzname ))          $output['TZNAME']           = count( $this->tzname );
+        if( !empty( $this->tzoffsetfrom ))    $output['TZOFFSETFROM']     = 1;
+        if( !empty( $this->tzoffsetto ))      $output['TZOFFSETTO']       = 1;
+        if( !empty( $this->tzurl ))           $output['TZURL']            = 1;
+        if( !empty( $this->url ))             $output['URL']              = 1;
+        if( !empty( $this->xprop ))           $output['X-PROP']           = count( $this->xprop );
+        return $output;
+        break;
+      case 'SETPROPERTYNAMES':
+        return array_keys( $this->getConfig( 'propinfo' ));
+        break;
+      case 'TZID':
+        return $this->dtzid;
+        break;
+      case 'UNIQUE_ID':
+        if( empty( $this->unique_id ))
+          $this->unique_id  = ( isset( $_SERVER['SERVER_NAME'] )) ? gethostbyname( $_SERVER['SERVER_NAME'] ) : 'localhost';
+        return $this->unique_id;
+        break;
+    }
+  }
+/**
+ * general component config setting
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.10.18 - 2011-10-28
+ * @param mixed  $config
+ * @param string $value
+ * @param bool   $softUpdate
+ * @return void
+ */
+  function setConfig( $config, $value = FALSE, $softUpdate = FALSE ) {
+    if( is_array( $config )) {
+      $ak = array_keys( $config );
+      foreach( $ak as $k ) {
+        if( 'NEWLINECHAR' == strtoupper( $k )) {
+          if( FALSE === $this->setConfig( 'NEWLINECHAR', $config[$k] ))
+            return FALSE;
+          unset( $config[$k] );
+          break;
+        }
+      }
+      foreach( $config as $cKey => $cValue ) {
+        if( FALSE === $this->setConfig( $cKey, $cValue, $softUpdate ))
+          return FALSE;
+      }
+      return TRUE;
+    }
+    $res = FALSE;
+    switch( strtoupper( $config )) {
+      case 'ALLOWEMPTY':
+        $this->allowEmpty = $value;
+        $subcfg = array( 'ALLOWEMPTY' => $value );
+        $res    = TRUE;
+        break;
+      case 'FORMAT':
+        $value  = trim( strtolower( $value ));
+        $this->format = $value;
+        $this->_createFormat();
+        $subcfg = array( 'FORMAT' => $value );
+        $res    = TRUE;
+        break;
+      case 'LANGUAGE':
+         // set language for calendar component as defined in [RFC 1766]
+        $value  = trim( $value );
+        if( empty( $this->language ) || !$softUpdate )
+          $this->language = $value;
+        $subcfg = array( 'LANGUAGE' => $value );
+        $res    = TRUE;
+        break;
+      case 'NL':
+      case 'NEWLINECHAR':
+        $this->nl = $value;
+        $this->_createFormat();
+        $subcfg = array( 'NL' => $value );
+        $res    = TRUE;
+        break;
+      case 'TZID':
+        $this->dtzid = $value;
+        $subcfg = array( 'TZID' => $value );
+        $res    = TRUE;
+        break;
+      case 'UNIQUE_ID':
+        $value  = trim( $value );
+        $this->unique_id = $value;
+        $subcfg = array( 'UNIQUE_ID' => $value );
+        $res    = TRUE;
+        break;
+      default:  // any unvalid config key.. .
+        return TRUE;
+    }
+    if( !$res ) return FALSE;
+    if( isset( $subcfg ) && !empty( $this->components )) {
+      foreach( $subcfg as $cfgkey => $cfgvalue ) {
+        foreach( $this->components as $cix => $component ) {
+          $res = $component->setConfig( $cfgkey, $cfgvalue, $softUpdate );
+          if( !$res )
+            break 2;
+          $this->components[$cix] = $component->copy(); // PHP4 compliant
+        }
+      }
+    }
+    return $res;
+  }
+/*********************************************************************************/
+/**
+ * delete component property value
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.8.8 - 2011-03-15
+ * @param mixed $propName, bool FALSE => X-property
+ * @param int   $propix, optional, if specific property is wanted in case of multiply occurences
+ * @return bool, if successfull delete TRUE
+ */
+  function deleteProperty( $propName=FALSE, $propix=FALSE ) {
+    if( $this->_notExistProp( $propName )) return FALSE;
+    $propName = strtoupper( $propName );
+    if( in_array( $propName, array( 'ATTACH',   'ATTENDEE', 'CATEGORIES', 'COMMENT',   'CONTACT', 'DESCRIPTION',    'EXDATE', 'EXRULE',
+                                    'FREEBUSY', 'RDATE',    'RELATED-TO', 'RESOURCES', 'RRULE',   'REQUEST-STATUS', 'TZNAME', 'X-PROP'  ))) {
+      if( !$propix )
+        $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1;
+      $this->propdelix[$propName] = --$propix;
+    }
+    $return = FALSE;
+    switch( $propName ) {
+      case 'ACTION':
+        if( !empty( $this->action )) {
+          $this->action = '';
+          $return = TRUE;
+        }
+        break;
+      case 'ATTACH':
+        return $this->deletePropertyM( $this->attach, $this->propdelix[$propName] );
+        break;
+      case 'ATTENDEE':
+        return $this->deletePropertyM( $this->attendee, $this->propdelix[$propName] );
+        break;
+      case 'CATEGORIES':
+        return $this->deletePropertyM( $this->categories, $this->propdelix[$propName] );
+        break;
+      case 'CLASS':
+        if( !empty( $this->class )) {
+          $this->class = '';
+          $return = TRUE;
+        }
+        break;
+      case 'COMMENT':
+        return $this->deletePropertyM( $this->comment, $this->propdelix[$propName] );
+        break;
+      case 'COMPLETED':
+        if( !empty( $this->completed )) {
+          $this->completed = '';
+          $return = TRUE;
+        }
+        break;
+      case 'CONTACT':
+        return $this->deletePropertyM( $this->contact, $this->propdelix[$propName] );
+        break;
+      case 'CREATED':
+        if( !empty( $this->created )) {
+          $this->created = '';
+          $return = TRUE;
+        }
+        break;
+      case 'DESCRIPTION':
+        return $this->deletePropertyM( $this->description, $this->propdelix[$propName] );
+        break;
+      case 'DTEND':
+        if( !empty( $this->dtend )) {
+          $this->dtend = '';
+          $return = TRUE;
+        }
+        break;
+      case 'DTSTAMP':
+        if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
+          return FALSE;
+        if( !empty( $this->dtstamp )) {
+          $this->dtstamp = '';
+          $return = TRUE;
+        }
+        break;
+      case 'DTSTART':
+        if( !empty( $this->dtstart )) {
+          $this->dtstart = '';
+          $return = TRUE;
+        }
+        break;
+      case 'DUE':
+        if( !empty( $this->due )) {
+          $this->due = '';
+          $return = TRUE;
+        }
+        break;
+      case 'DURATION':
+        if( !empty( $this->duration )) {
+          $this->duration = '';
+          $return = TRUE;
+        }
+        break;
+      case 'EXDATE':
+        return $this->deletePropertyM( $this->exdate, $this->propdelix[$propName] );
+        break;
+      case 'EXRULE':
+        return $this->deletePropertyM( $this->exrule, $this->propdelix[$propName] );
+        break;
+      case 'FREEBUSY':
+        return $this->deletePropertyM( $this->freebusy, $this->propdelix[$propName] );
+        break;
+      case 'GEO':
+        if( !empty( $this->geo )) {
+          $this->geo = '';
+          $return = TRUE;
+        }
+        break;
+      case 'LAST-MODIFIED':
+        if( !empty( $this->lastmodified )) {
+          $this->lastmodified = '';
+          $return = TRUE;
+        }
+        break;
+      case 'LOCATION':
+        if( !empty( $this->location )) {
+          $this->location = '';
+          $return = TRUE;
+        }
+        break;
+      case 'ORGANIZER':
+        if( !empty( $this->organizer )) {
+          $this->organizer = '';
+          $return = TRUE;
+        }
+        break;
+      case 'PERCENT-COMPLETE':
+        if( !empty( $this->percentcomplete )) {
+          $this->percentcomplete = '';
+          $return = TRUE;
+        }
+        break;
+      case 'PRIORITY':
+        if( !empty( $this->priority )) {
+          $this->priority = '';
+          $return = TRUE;
+        }
+        break;
+      case 'RDATE':
+        return $this->deletePropertyM( $this->rdate, $this->propdelix[$propName] );
+        break;
+      case 'RECURRENCE-ID':
+        if( !empty( $this->recurrenceid )) {
+          $this->recurrenceid = '';
+          $return = TRUE;
+        }
+        break;
+      case 'RELATED-TO':
+        return $this->deletePropertyM( $this->relatedto, $this->propdelix[$propName] );
+        break;
+      case 'REPEAT':
+        if( !empty( $this->repeat )) {
+          $this->repeat = '';
+          $return = TRUE;
+        }
+        break;
+      case 'REQUEST-STATUS':
+        return $this->deletePropertyM( $this->requeststatus, $this->propdelix[$propName] );
+        break;
+      case 'RESOURCES':
+        return $this->deletePropertyM( $this->resources, $this->propdelix[$propName] );
+        break;
+      case 'RRULE':
+        return $this->deletePropertyM( $this->rrule, $this->propdelix[$propName] );
+        break;
+      case 'SEQUENCE':
+        if( !empty( $this->sequence )) {
+          $this->sequence = '';
+          $return = TRUE;
+        }
+        break;
+      case 'STATUS':
+        if( !empty( $this->status )) {
+          $this->status = '';
+          $return = TRUE;
+        }
+        break;
+      case 'SUMMARY':
+        if( !empty( $this->summary )) {
+          $this->summary = '';
+          $return = TRUE;
+        }
+        break;
+      case 'TRANSP':
+        if( !empty( $this->transp )) {
+          $this->transp = '';
+          $return = TRUE;
+        }
+        break;
+      case 'TRIGGER':
+        if( !empty( $this->trigger )) {
+          $this->trigger = '';
+          $return = TRUE;
+        }
+        break;
+      case 'TZID':
+        if( !empty( $this->tzid )) {
+          $this->tzid = '';
+          $return = TRUE;
+        }
+        break;
+      case 'TZNAME':
+        return $this->deletePropertyM( $this->tzname, $this->propdelix[$propName] );
+        break;
+      case 'TZOFFSETFROM':
+        if( !empty( $this->tzoffsetfrom )) {
+          $this->tzoffsetfrom = '';
+          $return = TRUE;
+        }
+        break;
+      case 'TZOFFSETTO':
+        if( !empty( $this->tzoffsetto )) {
+          $this->tzoffsetto = '';
+          $return = TRUE;
+        }
+        break;
+      case 'TZURL':
+        if( !empty( $this->tzurl )) {
+          $this->tzurl = '';
+          $return = TRUE;
+        }
+        break;
+      case 'UID':
+        if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
+          return FALSE;
+        if( !empty( $this->uid )) {
+          $this->uid = '';
+          $return = TRUE;
+        }
+        break;
+      case 'URL':
+        if( !empty( $this->url )) {
+          $this->url = '';
+          $return = TRUE;
+        }
+        break;
+      default:
+        $reduced = '';
+        if( $propName != 'X-PROP' ) {
+          if( !isset( $this->xprop[$propName] )) return FALSE;
+          foreach( $this->xprop as $k => $a ) {
+            if(( $k != $propName ) && !empty( $a ))
+              $reduced[$k] = $a;
+          }
+        }
+        else {
+          if( count( $this->xprop ) <= $propix ) { unset( $this->propdelix[$propName] ); return FALSE; }
+          $xpropno = 0;
+          foreach( $this->xprop as $xpropkey => $xpropvalue ) {
+            if( $propix != $xpropno )
+              $reduced[$xpropkey] = $xpropvalue;
+            $xpropno++;
+          }
+        }
+        $this->xprop = $reduced;
+        if( empty( $this->xprop )) {
+          unset( $this->propdelix[$propName] );
+          return FALSE;
+        }
+        return TRUE;
+    }
+    return $return;
+  }
+/*********************************************************************************/
+/**
+ * delete component property value, fixing components with multiple occurencies
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.8.8 - 2011-03-15
+ * @param array $multiprop, reference to a component property
+ * @param int   $propix, reference to removal counter
+ * @return bool TRUE
+ */
+  function deletePropertyM( & $multiprop, & $propix ) {
+    if( isset( $multiprop[$propix] ))
+      unset( $multiprop[$propix] );
+    if( empty( $multiprop )) {
+      $multiprop = '';
+      unset( $propix );
+      return FALSE;
+    }
+    else
+      return TRUE;
+  }
+/**
+ * get component property value/params
+ *
+ * if property has multiply values, consequtive function calls are needed
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.12.4 - 2012-04-22
+ * @param string $propName, optional
+ * @param int @propix, optional, if specific property is wanted in case of multiply occurences
+ * @param bool $inclParam=FALSE
+ * @param bool $specform=FALSE
+ * @return mixed
+ */
+  function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE, $specform=FALSE ) {
+    if( 'GEOLOCATION' == strtoupper( $propName )) {
+      $content = $this->getProperty( 'LOCATION' );
+      $content = ( !empty( $content )) ? $content.' ' : '';
+      if(( FALSE === ( $geo     = $this->getProperty( 'GEO' ))) || empty( $geo ))
+        return FALSE;
+      if( 0.0 < $geo['latitude'] )
+        $sign   = '+';
+      else
+        $sign   = ( 0.0 > $geo['latitude'] ) ? '-' : '';
+      $content .= $sign.sprintf( "%09.6f", abs( $geo['latitude'] ));   // sprintf && lpad && float && sign !"#¤%&/(
+      $content  = rtrim( rtrim( $content, '0' ), '.' );
+      if( 0.0 < $geo['longitude'] )
+        $sign   = '+';
+      else
+       $sign   = ( 0.0 > $geo['longitude'] ) ? '-' : '';
+      return $content.$sign.sprintf( '%8.6f', abs( $geo['longitude'] )).'/';   // sprintf && lpad && float && sign !"#¤%&/(
+    }
+    if( $this->_notExistProp( $propName )) return FALSE;
+    $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
+    if( in_array( $propName, array( 'ATTACH',   'ATTENDEE', 'CATEGORIES', 'COMMENT',   'CONTACT', 'DESCRIPTION',    'EXDATE', 'EXRULE',
+                                    'FREEBUSY', 'RDATE',    'RELATED-TO', 'RESOURCES', 'RRULE',   'REQUEST-STATUS', 'TZNAME', 'X-PROP'  ))) {
+      if( !$propix )
+        $propix = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1;
+      $this->propix[$propName] = --$propix;
+    }
+    switch( $propName ) {
+      case 'ACTION':
+        if( !empty( $this->action['value'] )) return ( $inclParam ) ? $this->action : $this->action['value'];
+        break;
+      case 'ATTACH':
+        $ak = ( is_array( $this->attach )) ? array_keys( $this->attach ) : array();
+        while( is_array( $this->attach ) && !isset( $this->attach[$propix] ) && ( 0 < count( $this->attach )) && ( $propix < end( $ak )))
+          $propix++;
+        $this->propix[$propName] = $propix;
+        if( !isset( $this->attach[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
+        return ( $inclParam ) ? $this->attach[$propix] : $this->attach[$propix]['value'];
+        break;
+      case 'ATTENDEE':
+        $ak = ( is_array( $this->attendee )) ? array_keys( $this->attendee ) : array();
+        while( is_array( $this->attendee ) && !isset( $this->attendee[$propix] ) && ( 0 < count( $this->attendee )) && ( $propix < end( $ak )))
+          $propix++;
+        $this->propix[$propName] = $propix;
+        if( !isset( $this->attendee[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
+        return ( $inclParam ) ? $this->attendee[$propix] : $this->attendee[$propix]['value'];
+        break;
+      case 'CATEGORIES':
+        $ak = ( is_array( $this->categories )) ? array_keys( $this->categories ) : array();
+        while( is_array( $this->categories ) && !isset( $this->categories[$propix] ) && ( 0 < count( $this->categories )) && ( $propix < end( $ak )))
+          $propix++;
+        $this->propix[$propName] = $propix;
+        if( !isset( $this->categories[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
+        return ( $inclParam ) ? $this->categories[$propix] : $this->categories[$propix]['value'];
+        break;
+      case 'CLASS':
+        if( !empty( $this->class['value'] )) return ( $inclParam ) ? $this->class : $this->class['value'];
+        break;
+      case 'COMMENT':
+        $ak = ( is_array( $this->comment )) ? array_keys( $this->comment ) : array();
+        while( is_array( $this->comment ) && !isset( $this->comment[$propix] ) && ( 0 < count( $this->comment )) && ( $propix < end( $ak )))
+          $propix++;
+        $this->propix[$propName] = $propix;
+        if( !isset( $this->comment[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
+        return ( $inclParam ) ? $this->comment[$propix] : $this->comment[$propix]['value'];
+        break;
+      case 'COMPLETED':
+        if( !empty( $this->completed['value'] )) return ( $inclParam ) ? $this->completed : $this->completed['value'];
+        break;
+      case 'CONTACT':
+        $ak = ( is_array( $this->contact )) ? array_keys( $this->contact ) : array();
+        while( is_array( $this->contact ) && !isset( $this->contact[$propix] ) && ( 0 < count( $this->contact )) && ( $propix < end( $ak )))
+          $propix++;
+        $this->propix[$propName] = $propix;
+        if( !isset( $this->contact[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
+        return ( $inclParam ) ? $this->contact[$propix] : $this->contact[$propix]['value'];
+        break;
+      case 'CREATED':
+        if( !empty( $this->created['value'] )) return ( $inclParam ) ? $this->created : $this->created['value'];
+        break;
+      case 'DESCRIPTION':
+        $ak = ( is_array( $this->description )) ? array_keys( $this->description ) : array();
+        while( is_array( $this->description ) && !isset( $this->description[$propix] ) && ( 0 < count( $this->description )) && ( $propix < end( $ak )))
+          $propix++;
+        $this->propix[$propName] = $propix;
+        if( !isset( $this->description[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
+        return ( $inclParam ) ? $this->description[$propix] : $this->description[$propix]['value'];
+        break;
+      case 'DTEND':
+        if( !empty( $this->dtend['value'] )) return ( $inclParam ) ? $this->dtend : $this->dtend['value'];
+        break;
+      case 'DTSTAMP':
+        if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
+          return;
+        if( !isset( $this->dtstamp['value'] ))
+          $this->_makeDtstamp();
+        return ( $inclParam ) ? $this->dtstamp : $this->dtstamp['value'];
+        break;
+      case 'DTSTART':
+        if( !empty( $this->dtstart['value'] )) return ( $inclParam ) ? $this->dtstart : $this->dtstart['value'];
+        break;
+      case 'DUE':
+        if( !empty( $this->due['value'] )) return ( $inclParam ) ? $this->due : $this->due['value'];
+        break;
+      case 'DURATION':
+        if( !isset( $this->duration['value'] )) return FALSE;
+        $value = ( $specform && isset( $this->dtstart['value'] ) && isset( $this->duration['value'] )) ? iCalUtilityFunctions::_duration2date( $this->dtstart['value'], $this->duration['value'] ) : $this->duration['value'];
+        return ( $inclParam ) ? array( 'value' => $value, 'params' =>  $this->duration['params'] ) : $value;
+        break;
+      case 'EXDATE':
+        $ak = ( is_array( $this->exdate )) ? array_keys( $this->exdate ) : array();
+        while( is_array( $this->exdate ) && !isset( $this->exdate[$propix] ) && ( 0 < count( $this->exdate )) && ( $propix < end( $ak )))
+          $propix++;
+        $this->propix[$propName] = $propix;
+        if( !isset( $this->exdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
+        return ( $inclParam ) ? $this->exdate[$propix] : $this->exdate[$propix]['value'];
+        break;
+      case 'EXRULE':
+        $ak = ( is_array( $this->exrule )) ? array_keys( $this->exrule ) : array();
+        while( is_array( $this->exrule ) && !isset( $this->exrule[$propix] ) && ( 0 < count( $this->exrule )) && ( $propix < end( $ak )))
+          $propix++;
+        $this->propix[$propName] = $propix;
+        if( !isset( $this->exrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
+        return ( $inclParam ) ? $this->exrule[$propix] : $this->exrule[$propix]['value'];
+        break;
+      case 'FREEBUSY':
+        $ak = ( is_array( $this->freebusy )) ? array_keys( $this->freebusy ) : array();
+        while( is_array( $this->freebusy ) && !isset( $this->freebusy[$propix] ) && ( 0 < count( $this->freebusy )) && ( $propix < end( $ak )))
+          $propix++;
+        $this->propix[$propName] = $propix;
+        if( !isset( $this->freebusy[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
+        return ( $inclParam ) ? $this->freebusy[$propix] : $this->freebusy[$propix]['value'];
+        break;
+      case 'GEO':
+        if( !empty( $this->geo['value'] )) return ( $inclParam ) ? $this->geo : $this->geo['value'];
+        break;
+      case 'LAST-MODIFIED':
+        if( !empty( $this->lastmodified['value'] )) return ( $inclParam ) ? $this->lastmodified : $this->lastmodified['value'];
+        break;
+      case 'LOCATION':
+        if( !empty( $this->location['value'] )) return ( $inclParam ) ? $this->location : $this->location['value'];
+        break;
+      case 'ORGANIZER':
+        if( !empty( $this->organizer['value'] )) return ( $inclParam ) ? $this->organizer : $this->organizer['value'];
+        break;
+      case 'PERCENT-COMPLETE':
+        if( !empty( $this->percentcomplete['value'] ) || ( isset( $this->percentcomplete['value'] ) && ( '0' == $this->percentcomplete['value'] ))) return ( $inclParam ) ? $this->percentcomplete : $this->percentcomplete['value'];
+        break;
+      case 'PRIORITY':
+        if( !empty( $this->priority['value'] ) || ( isset( $this->priority['value'] ) && ('0' == $this->priority['value'] ))) return ( $inclParam ) ? $this->priority : $this->priority['value'];
+        break;
+      case 'RDATE':
+        $ak = ( is_array( $this->rdate )) ? array_keys( $this->rdate ) : array();
+        while( is_array( $this->rdate ) && !isset( $this->rdate[$propix] ) && ( 0 < count( $this->rdate )) && ( $propix < end( $ak )))
+          $propix++;
+        $this->propix[$propName] = $propix;
+        if( !isset( $this->rdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
+        return ( $inclParam ) ? $this->rdate[$propix] : $this->rdate[$propix]['value'];
+        break;
+      case 'RECURRENCE-ID':
+        if( !empty( $this->recurrenceid['value'] )) return ( $inclParam ) ? $this->recurrenceid : $this->recurrenceid['value'];
+        break;
+      case 'RELATED-TO':
+        $ak = ( is_array( $this->relatedto )) ? array_keys( $this->relatedto ) : array();
+        while( is_array( $this->relatedto ) && !isset( $this->relatedto[$propix] ) && ( 0 < count( $this->relatedto )) && ( $propix < end( $ak )))
+          $propix++;
+        $this->propix[$propName] = $propix;
+        if( !isset( $this->relatedto[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
+        return ( $inclParam ) ? $this->relatedto[$propix] : $this->relatedto[$propix]['value'];
+        break;
+      case 'REPEAT':
+        if( !empty( $this->repeat['value'] ) || ( isset( $this->repeat['value'] ) && ( '0' == $this->repeat['value'] ))) return ( $inclParam ) ? $this->repeat : $this->repeat['value'];
+        break;
+      case 'REQUEST-STATUS':
+        $ak = ( is_array( $this->requeststatus )) ? array_keys( $this->requeststatus ) : array();
+        while( is_array( $this->requeststatus ) && !isset( $this->requeststatus[$propix] ) && ( 0 < count( $this->requeststatus )) && ( $propix < end( $ak )))
+          $propix++;
+        $this->propix[$propName] = $propix;
+        if( !isset( $this->requeststatus[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
+        return ( $inclParam ) ? $this->requeststatus[$propix] : $this->requeststatus[$propix]['value'];
+        break;
+      case 'RESOURCES':
+        $ak = ( is_array( $this->resources )) ? array_keys( $this->resources ) : array();
+        while( is_array( $this->resources ) && !isset( $this->resources[$propix] ) && ( 0 < count( $this->resources )) && ( $propix < end( $ak )))
+          $propix++;
+        $this->propix[$propName] = $propix;
+        if( !isset( $this->resources[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
+        return ( $inclParam ) ? $this->resources[$propix] : $this->resources[$propix]['value'];
+        break;
+      case 'RRULE':
+        $ak = ( is_array( $this->rrule )) ? array_keys( $this->rrule ) : array();
+        while( is_array( $this->rrule ) && !isset( $this->rrule[$propix] ) && ( 0 < count( $this->rrule )) && ( $propix < end( $ak )))
+          $propix++;
+        $this->propix[$propName] = $propix;
+        if( !isset( $this->rrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
+        return ( $inclParam ) ? $this->rrule[$propix] : $this->rrule[$propix]['value'];
+        break;
+      case 'SEQUENCE':
+        if( isset( $this->sequence['value'] ) && ( isset( $this->sequence['value'] ) && ( '0' <= $this->sequence['value'] ))) return ( $inclParam ) ? $this->sequence : $this->sequence['value'];
+        break;
+      case 'STATUS':
+        if( !empty( $this->status['value'] )) return ( $inclParam ) ? $this->status : $this->status['value'];
+        break;
+      case 'SUMMARY':
+        if( !empty( $this->summary['value'] )) return ( $inclParam ) ? $this->summary : $this->summary['value'];
+        break;
+      case 'TRANSP':
+        if( !empty( $this->transp['value'] )) return ( $inclParam ) ? $this->transp : $this->transp['value'];
+        break;
+      case 'TRIGGER':
+        if( !empty( $this->trigger['value'] )) return ( $inclParam ) ? $this->trigger : $this->trigger['value'];
+        break;
+      case 'TZID':
+        if( !empty( $this->tzid['value'] )) return ( $inclParam ) ? $this->tzid : $this->tzid['value'];
+        break;
+      case 'TZNAME':
+        $ak = ( is_array( $this->tzname )) ? array_keys( $this->tzname ) : array();
+        while( is_array( $this->tzname ) && !isset( $this->tzname[$propix] ) && ( 0 < count( $this->tzname )) && ( $propix < end( $ak )))
+          $propix++;
+        $this->propix[$propName] = $propix;
+        if( !isset( $this->tzname[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
+        return ( $inclParam ) ? $this->tzname[$propix] : $this->tzname[$propix]['value'];
+        break;
+      case 'TZOFFSETFROM':
+        if( !empty( $this->tzoffsetfrom['value'] )) return ( $inclParam ) ? $this->tzoffsetfrom : $this->tzoffsetfrom['value'];
+        break;
+      case 'TZOFFSETTO':
+        if( !empty( $this->tzoffsetto['value'] )) return ( $inclParam ) ? $this->tzoffsetto : $this->tzoffsetto['value'];
+        break;
+      case 'TZURL':
+        if( !empty( $this->tzurl['value'] )) return ( $inclParam ) ? $this->tzurl : $this->tzurl['value'];
+        break;
+      case 'UID':
+        if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
+          return FALSE;
+        if( empty( $this->uid['value'] ))
+          $this->_makeuid();
+        return ( $inclParam ) ? $this->uid : $this->uid['value'];
+        break;
+      case 'URL':
+        if( !empty( $this->url['value'] )) return ( $inclParam ) ? $this->url : $this->url['value'];
+        break;
+      default:
+        if( $propName != 'X-PROP' ) {
+          if( !isset( $this->xprop[$propName] )) return FALSE;
+          return ( $inclParam ) ? array( $propName, $this->xprop[$propName] )
+                                : array( $propName, $this->xprop[$propName]['value'] );
+        }
+        else {
+          if( empty( $this->xprop )) return FALSE;
+          $xpropno = 0;
+          foreach( $this->xprop as $xpropkey => $xpropvalue ) {
+            if( $propix == $xpropno )
+              return ( $inclParam ) ? array( $xpropkey, $this->xprop[$xpropkey] )
+                                    : array( $xpropkey, $this->xprop[$xpropkey]['value'] );
+            else
+              $xpropno++;
+          }
+          return FALSE; // not found ??
+        }
+    }
+    return FALSE;
+  }
+/**
+ * returns calendar property unique values for 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO' or 'RESOURCES' and for each, number of occurrence
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.13.4 - 2012-08-07
+ * @param string $propName
+ * @param array  $output, incremented result array
+ */
+  function _getProperties( $propName, & $output ) {
+    if( empty( $output ))
+      $output = array();
+    if( !in_array( strtoupper( $propName ), array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' )))
+      return $output;
+    while( FALSE !== ( $content = $this->getProperty( $propName ))) {
+      if( empty( $content ))
+        continue;
+      if( is_array( $content )) {
+        foreach( $content as $part ) {
+          if( FALSE !== strpos( $part, ',' )) {
+            $part = explode( ',', $part );
+            foreach( $part as $thePart ) {
+              $thePart = trim( $thePart );
+              if( !empty( $thePart )) {
+                if( !isset( $output[$thePart] ))
+                  $output[$thePart] = 1;
+                else
+                  $output[$thePart] += 1;
+              }
+            }
+          }
+          else {
+            $part = trim( $part );
+            if( !isset( $output[$part] ))
+              $output[$part] = 1;
+            else
+              $output[$part] += 1;
+          }
+        }
+      } // end if( is_array( $content ))
+      elseif( FALSE !== strpos( $content, ',' )) {
+        $content = explode( ',', $content );
+        foreach( $content as $thePart ) {
+          $thePart = trim( $thePart );
+          if( !empty( $thePart )) {
+            if( !isset( $output[$thePart] ))
+              $output[$thePart] = 1;
+            else
+              $output[$thePart] += 1;
+          }
+        }
+      } // end elseif( FALSE !== strpos( $content, ',' ))
+      else {
+        $content = trim( $content );
+        if( !empty( $content )) {
+          if( !isset( $output[$content] ))
+            $output[$content] = 1;
+          else
+            $output[$content] += 1;
+        }
+      }
+    }
+    ksort( $output );
+  }
+/**
+ * general component property setting
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-11-05
+ * @param mixed $args variable number of function arguments,
+ *                    first argument is ALWAYS component name,
+ *                    second ALWAYS component value!
+ * @return void
+ */
+  function setProperty() {
+    $numargs    = func_num_args();
+    if( 1 > $numargs ) return FALSE;
+    $arglist    = func_get_args();
+    if( $this->_notExistProp( $arglist[0] )) return FALSE;
+    if( !$this->getConfig( 'allowEmpty' ) && ( !isset( $arglist[1] ) || empty( $arglist[1] )))
+      return FALSE;
+    $arglist[0] = strtoupper( $arglist[0] );
+    for( $argix=$numargs; $argix < 12; $argix++ ) {
+      if( !isset( $arglist[$argix] ))
+        $arglist[$argix] = null;
+    }
+    switch( $arglist[0] ) {
+      case 'ACTION':
+        return $this->setAction( $arglist[1], $arglist[2] );
+      case 'ATTACH':
+        return $this->setAttach( $arglist[1], $arglist[2], $arglist[3] );
+      case 'ATTENDEE':
+        return $this->setAttendee( $arglist[1], $arglist[2], $arglist[3] );
+      case 'CATEGORIES':
+        return $this->setCategories( $arglist[1], $arglist[2], $arglist[3] );
+      case 'CLASS':
+        return $this->setClass( $arglist[1], $arglist[2] );
+      case 'COMMENT':
+        return $this->setComment( $arglist[1], $arglist[2], $arglist[3] );
+      case 'COMPLETED':
+        return $this->setCompleted( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
+      case 'CONTACT':
+        return $this->setContact( $arglist[1], $arglist[2], $arglist[3] );
+      case 'CREATED':
+        return $this->setCreated( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
+      case 'DESCRIPTION':
+        return $this->setDescription( $arglist[1], $arglist[2], $arglist[3] );
+      case 'DTEND':
+        return $this->setDtend( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
+      case 'DTSTAMP':
+        return $this->setDtstamp( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
+      case 'DTSTART':
+        return $this->setDtstart( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
+      case 'DUE':
+        return $this->setDue( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
+      case 'DURATION':
+        return $this->setDuration( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6] );
+      case 'EXDATE':
+        return $this->setExdate( $arglist[1], $arglist[2], $arglist[3] );
+      case 'EXRULE':
+        return $this->setExrule( $arglist[1], $arglist[2], $arglist[3] );
+      case 'FREEBUSY':
+        return $this->setFreebusy( $arglist[1], $arglist[2], $arglist[3], $arglist[4] );
+      case 'GEO':
+        return $this->setGeo( $arglist[1], $arglist[2], $arglist[3] );
+      case 'LAST-MODIFIED':
+        return $this->setLastModified( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
+      case 'LOCATION':
+        return $this->setLocation( $arglist[1], $arglist[2] );
+      case 'ORGANIZER':
+        return $this->setOrganizer( $arglist[1], $arglist[2] );
+      case 'PERCENT-COMPLETE':
+        return $this->setPercentComplete( $arglist[1], $arglist[2] );
+      case 'PRIORITY':
+        return $this->setPriority( $arglist[1], $arglist[2] );
+      case 'RDATE':
+        return $this->setRdate( $arglist[1], $arglist[2], $arglist[3] );
+      case 'RECURRENCE-ID':
+       return $this->setRecurrenceid( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
+      case 'RELATED-TO':
+        return $this->setRelatedTo( $arglist[1], $arglist[2], $arglist[3] );
+      case 'REPEAT':
+        return $this->setRepeat( $arglist[1], $arglist[2] );
+      case 'REQUEST-STATUS':
+        return $this->setRequestStatus( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5] );
+      case 'RESOURCES':
+        return $this->setResources( $arglist[1], $arglist[2], $arglist[3] );
+      case 'RRULE':
+        return $this->setRrule( $arglist[1], $arglist[2], $arglist[3] );
+      case 'SEQUENCE':
+        return $this->setSequence( $arglist[1], $arglist[2] );
+      case 'STATUS':
+        return $this->setStatus( $arglist[1], $arglist[2] );
+      case 'SUMMARY':
+        return $this->setSummary( $arglist[1], $arglist[2] );
+      case 'TRANSP':
+        return $this->setTransp( $arglist[1], $arglist[2] );
+      case 'TRIGGER':
+        return $this->setTrigger( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8], $arglist[9], $arglist[10], $arglist[11] );
+      case 'TZID':
+        return $this->setTzid( $arglist[1], $arglist[2] );
+      case 'TZNAME':
+        return $this->setTzname( $arglist[1], $arglist[2], $arglist[3] );
+      case 'TZOFFSETFROM':
+        return $this->setTzoffsetfrom( $arglist[1], $arglist[2] );
+      case 'TZOFFSETTO':
+        return $this->setTzoffsetto( $arglist[1], $arglist[2] );
+      case 'TZURL':
+        return $this->setTzurl( $arglist[1], $arglist[2] );
+      case 'UID':
+        return $this->setUid( $arglist[1], $arglist[2] );
+      case 'URL':
+        return $this->setUrl( $arglist[1], $arglist[2] );
+      default:
+        return $this->setXprop( $arglist[0], $arglist[1], $arglist[2] );
+    }
+    return FALSE;
+  }
+/*********************************************************************************/
+/**
+ * parse component unparsed data into properties
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.2 - 2012-12-18
+ * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of strings
+ * @return bool FALSE if error occurs during parsing
+ *
+ */
+  function parse( $unparsedtext=null ) {
+    $nl = $this->getConfig( 'nl' );
+    if( !empty( $unparsedtext )) {
+      if( is_array( $unparsedtext ))
+        $unparsedtext = implode( '\n'.$nl, $unparsedtext );
+      $unparsedtext = explode( $nl, iCalUtilityFunctions::convEolChar( $unparsedtext, $nl ));
+    }
+    elseif( !isset( $this->unparsed ))
+      $unparsedtext = array();
+    else
+      $unparsedtext = $this->unparsed;
+            /* skip leading (empty/invalid) lines */
+    foreach( $unparsedtext as $lix => $line ) {
+      $tst = trim( $line );
+      if(( '\n' == $tst ) || empty( $tst ))
+        unset( $unparsedtext[$lix] );
+      else
+        break;
+    }
+    $this->unparsed = array();
+    $comp           = & $this;
+    $config         = $this->getConfig();
+    $compsync = $subsync = 0;
+    foreach ( $unparsedtext as $lix => $line ) {
+      if( 'END:VALARM'         == strtoupper( substr( $line, 0, 10 ))) {
+        if( 1 != $subsync ) return FALSE;
+        $this->components[]     = $comp->copy();
+        $subsync--;
+      }
+      elseif( 'END:DAYLIGHT'   == strtoupper( substr( $line, 0, 12 ))) {
+        if( 1 != $subsync ) return FALSE;
+        $this->components[]     = $comp->copy();
+        $subsync--;
+      }
+      elseif( 'END:STANDARD'   == strtoupper( substr( $line, 0, 12 ))) {
+        if( 1 != $subsync ) return FALSE;
+        array_unshift( $this->components, $comp->copy());
+        $subsync--;
+      }
+      elseif( 'END:'           == strtoupper( substr( $line, 0, 4 ))) { // end:<component>
+        if( 1 != $compsync ) return FALSE;
+        if( 0 < $subsync )
+          $this->components[]   = $comp->copy();
+        $compsync--;
+        break;                       /* skip trailing empty lines */
+      }
+      elseif( 'BEGIN:VALARM'   == strtoupper( substr( $line, 0, 12 ))) {
+        $comp = new valarm( $config);
+        $subsync++;
+      }
+      elseif( 'BEGIN:STANDARD' == strtoupper( substr( $line, 0, 14 ))) {
+        $comp = new vtimezone( 'standard', $config );
+        $subsync++;
+      }
+      elseif( 'BEGIN:DAYLIGHT' == strtoupper( substr( $line, 0, 14 ))) {
+        $comp = new vtimezone( 'daylight', $config );
+        $subsync++;
+      }
+      elseif( 'BEGIN:'         == strtoupper( substr( $line, 0, 6 )))  // begin:<component>
+        $compsync++;
+      else
+        $comp->unparsed[]       = $line;
+    }
+    if( 0 < $subsync )
+      $this->components[]   = $comp->copy();
+    unset( $config );
+            /* concatenate property values spread over several lines */
+    $lastix    = -1;
+    $propnames = array( 'action', 'attach', 'attendee', 'categories', 'comment', 'completed'
+                      , 'contact', 'class', 'created', 'description', 'dtend', 'dtstart'
+                      , 'dtstamp', 'due', 'duration', 'exdate', 'exrule', 'freebusy', 'geo'
+                      , 'last-modified', 'location', 'organizer', 'percent-complete'
+                      , 'priority', 'rdate', 'recurrence-id', 'related-to', 'repeat'
+                      , 'request-status', 'resources', 'rrule', 'sequence', 'status'
+                      , 'summary', 'transp', 'trigger', 'tzid', 'tzname', 'tzoffsetfrom'
+                      , 'tzoffsetto', 'tzurl', 'uid', 'url', 'x-' );
+    $proprows  = array();
+    for( $i = 0; $i < count( $this->unparsed ); $i++ ) { // concatenate lines
+      $line = rtrim( $this->unparsed[$i], $nl );
+      while( isset( $this->unparsed[$i+1] ) && !empty( $this->unparsed[$i+1] ) && ( ' ' == $this->unparsed[$i+1]{0} ))
+        $line .= rtrim( substr( $this->unparsed[++$i], 1 ), $nl );
+      $proprows[] = $line;
+    }
+            /* parse each property 'line' */
+    $paramMStz   = array( 'utc-', 'utc+', 'gmt-', 'gmt+' );
+    $paramProto3 = array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' );
+    $paramProto4 = array( 'crid:', 'news:', 'pres:' );
+    foreach( $proprows as $line ) {
+      if( '\n' == substr( $line, -2 ))
+        $line = substr( $line, 0, -2 );
+            /* get propname */
+      $propname = null;
+      $cix = 0;
+      while( isset( $line[$cix] )) {
+        if( in_array( $line[$cix], array( ':', ';' )))
+          break;
+        else
+          $propname .= $line[$cix];
+        $cix++;
+      }
+      if(( 'x-' == substr( $propname, 0, 2 )) || ( 'X-' == substr( $propname, 0, 2 ))) {
+        $propname2 = $propname;
+        $propname  = 'X-';
+      }
+      if( !in_array( strtolower( $propname ), $propnames )) // skip non standard property names
+        continue;
+            /* rest of the line is opt.params and value */
+      $line = substr( $line, $cix );
+            /* separate attributes from value */
+      $attr         = array();
+      $attrix       = -1;
+      $clen         = strlen( $line );
+      $WithinQuotes = FALSE;
+      $cix          = 0;
+      while( FALSE !== substr( $line, $cix, 1 )) {
+        if(                       (  ':' == $line[$cix] )                         &&
+                                  ( substr( $line,$cix,     3 )  != '://' )       &&
+           ( !in_array( strtolower( substr( $line,$cix - 6, 4 )), $paramMStz ))   &&
+           ( !in_array( strtolower( substr( $line,$cix - 3, 4 )), $paramProto3 )) &&
+           ( !in_array( strtolower( substr( $line,$cix - 4, 5 )), $paramProto4 )) &&
+                      ( strtolower( substr( $line,$cix - 6, 7 )) != 'mailto:' )   &&
+             !$WithinQuotes ) {
+          $attrEnd = TRUE;
+          if(( $cix < ( $clen - 4 )) &&
+               ctype_digit( substr( $line, $cix+1, 4 ))) { // an URI with a (4pos) portnr??
+            for( $c2ix = $cix; 3 < $c2ix; $c2ix-- ) {
+              if( '://' == substr( $line, $c2ix - 2, 3 )) {
+                $attrEnd = FALSE;
+                break; // an URI with a portnr!!
+              }
+            }
+          }
+          if( $attrEnd) {
+            $line = substr( $line, ( $cix + 1 ));
+            break;
+          }
+          $cix++;
+        }
+        if( '"' == $line[$cix] )
+          $WithinQuotes = ( FALSE === $WithinQuotes ) ? TRUE : FALSE;
+        if( ';' == $line[$cix] )
+          $attr[++$attrix] = null;
+        else
+          $attr[$attrix] .= $line[$cix];
+        $cix++;
+      }
+            /* make attributes in array format */
+      $propattr = array();
+      foreach( $attr as $attribute ) {
+        $attrsplit = explode( '=', $attribute, 2 );
+        if( 1 < count( $attrsplit ))
+          $propattr[$attrsplit[0]] = $attrsplit[1];
+        else
+          $propattr[] = $attribute;
+      }
+            /* call setProperty( $propname.. . */
+      switch( strtoupper( $propname )) {
+        case 'ATTENDEE':
+          foreach( $propattr as $pix => $attr ) {
+            if( !in_array( strtoupper( $pix ), array( 'MEMBER', 'DELEGATED-TO', 'DELEGATED-FROM' )))
+              continue;
+            $attr2 = explode( ',', $attr );
+              if( 1 < count( $attr2 ))
+                $propattr[$pix] = $attr2;
+          }
+          $this->setProperty( $propname, $line, $propattr );
+          break;
+        case 'X-':
+          $propname = ( isset( $propname2 )) ? $propname2 : $propname;
+          unset( $propname2 );
+        case 'CATEGORIES':
+        case 'RESOURCES':
+          if( FALSE !== strpos( $line, ',' )) {
+            $content  = array( 0 => '' );
+            $cix = $lix = 0;
+            while( FALSE !== substr( $line, $lix, 1 )) {
+              if(( ',' == $line[$lix] ) && ( "\\" != $line[( $lix - 1 )])) {
+                $cix++;
+                $content[$cix] = '';
+              }
+              else
+                $content[$cix] .= $line[$lix];
+              $lix++;
+            }
+            if( 1 < count( $content )) {
+              $content = array_values( $content );
+              foreach( $content as $cix => $contentPart )
+                $content[$cix] = iCalUtilityFunctions::_strunrep( $contentPart );
+              $this->setProperty( $propname, $content, $propattr );
+              break;
+            }
+            else
+              $line = reset( $content );
+          }
+        case 'COMMENT':
+        case 'CONTACT':
+        case 'DESCRIPTION':
+        case 'LOCATION':
+        case 'SUMMARY':
+          if( empty( $line ))
+            $propattr = null;
+          $this->setProperty( $propname, iCalUtilityFunctions::_strunrep( $line ), $propattr );
+          break;
+        case 'REQUEST-STATUS':
+          $values    = explode( ';', $line, 3 );
+          $values[1] = ( !isset( $values[1] )) ? null : iCalUtilityFunctions::_strunrep( $values[1] );
+          $values[2] = ( !isset( $values[2] )) ? null : iCalUtilityFunctions::_strunrep( $values[2] );
+          $this->setProperty( $propname
+                            , $values[0]  // statcode
+                            , $values[1]  // statdesc
+                            , $values[2]  // extdata
+                            , $propattr );
+          break;
+        case 'FREEBUSY':
+          $fbtype = ( isset( $propattr['FBTYPE'] )) ? $propattr['FBTYPE'] : ''; // force setting default, if missing
+          unset( $propattr['FBTYPE'] );
+          $values = explode( ',', $line );
+          foreach( $values as $vix => $value ) {
+            $value2 = explode( '/', $value );
+            if( 1 < count( $value2 ))
+              $values[$vix] = $value2;
+          }
+          $this->setProperty( $propname, $fbtype, $values, $propattr );
+          break;
+        case 'GEO':
+          $value = explode( ';', $line, 2 );
+          if( 2 > count( $value ))
+            $value[1] = null;
+          $this->setProperty( $propname, $value[0], $value[1], $propattr );
+          break;
+        case 'EXDATE':
+          $values = ( !empty( $line )) ? explode( ',', $line ) : null;
+          $this->setProperty( $propname, $values, $propattr );
+          break;
+        case 'RDATE':
+          if( empty( $line )) {
+            $this->setProperty( $propname, $line, $propattr );
+            break;
+          }
+          $values = explode( ',', $line );
+          foreach( $values as $vix => $value ) {
+            $value2 = explode( '/', $value );
+            if( 1 < count( $value2 ))
+              $values[$vix] = $value2;
+          }
+          $this->setProperty( $propname, $values, $propattr );
+          break;
+        case 'EXRULE':
+        case 'RRULE':
+          $values = explode( ';', $line );
+          $recur = array();
+          foreach( $values as $value2 ) {
+            if( empty( $value2 ))
+              continue; // ;-char in ending position ???
+            $value3 = explode( '=', $value2, 2 );
+            $rulelabel = strtoupper( $value3[0] );
+            switch( $rulelabel ) {
+              case 'BYDAY': {
+                $value4 = explode( ',', $value3[1] );
+                if( 1 < count( $value4 )) {
+                  foreach( $value4 as $v5ix => $value5 ) {
+                    $value6 = array();
+                    $dayno = $dayname = null;
+                    $value5 = trim( (string) $value5 );
+                    if(( ctype_alpha( substr( $value5, -1 ))) &&
+                       ( ctype_alpha( substr( $value5, -2, 1 )))) {
+                      $dayname = substr( $value5, -2, 2 );
+                      if( 2 < strlen( $value5 ))
+                        $dayno = substr( $value5, 0, ( strlen( $value5 ) - 2 ));
+                    }
+                    if( $dayno )
+                      $value6[] = $dayno;
+                    if( $dayname )
+                      $value6['DAY'] = $dayname;
+                    $value4[$v5ix] = $value6;
+                  }
+                }
+                else {
+                  $value4 = array();
+                  $dayno  = $dayname = null;
+                  $value5 = trim( (string) $value3[1] );
+                  if(( ctype_alpha( substr( $value5, -1 ))) &&
+                     ( ctype_alpha( substr( $value5, -2, 1 )))) {
+                      $dayname = substr( $value5, -2, 2 );
+                    if( 2 < strlen( $value5 ))
+                      $dayno = substr( $value5, 0, ( strlen( $value5 ) - 2 ));
+                  }
+                  if( $dayno )
+                    $value4[] = $dayno;
+                  if( $dayname )
+                    $value4['DAY'] = $dayname;
+                }
+                $recur[$rulelabel] = $value4;
+                break;
+              }
+              default: {
+                $value4 = explode( ',', $value3[1] );
+                if( 1 < count( $value4 ))
+                  $value3[1] = $value4;
+                $recur[$rulelabel] = $value3[1];
+                break;
+              }
+            } // end - switch $rulelabel
+          } // end - foreach( $values.. .
+          $this->setProperty( $propname, $recur, $propattr );
+          break;
+        case 'ACTION':
+        case 'CLASSIFICATION':
+        case 'STATUS':
+        case 'TRANSP':
+        case 'UID':
+        case 'TZID':
+        case 'RELATED-TO':
+        case 'TZNAME':
+          $line = iCalUtilityFunctions::_strunrep( $line );
+        default:
+          $this->setProperty( $propname, $line, $propattr );
+          break;
+      } // end  switch( $propname.. .
+    } // end - foreach( $proprows.. .
+    unset( $unparsedtext, $this->unparsed, $proprows );
+    if( isset( $this->components ) && is_array( $this->components ) && ( 0 < count( $this->components ))) {
+      $ckeys = array_keys( $this->components );
+      foreach( $ckeys as $ckey ) {
+        if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) {
+          $this->components[$ckey]->parse();
+        }
+      }
+    }
+    return TRUE;
+  }
+/*********************************************************************************/
+/*********************************************************************************/
+/**
+ * return a copy of this component
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.15.4 - 2012-10-18
+ * @return object
+ */
+  function copy() {
+    return unserialize( serialize( $this ));
+ }
+/*********************************************************************************/
+/*********************************************************************************/
+/**
+ * delete calendar subcomponent from component container
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.8.8 - 2011-03-15
+ * @param mixed $arg1 ordno / component type / component uid
+ * @param mixed $arg2 optional, ordno if arg1 = component type
+ * @return void
+ */
+  function deleteComponent( $arg1, $arg2=FALSE  ) {
+    if( !isset( $this->components )) return FALSE;
+    $argType = $index = null;
+    if ( ctype_digit( (string) $arg1 )) {
+      $argType = 'INDEX';
+      $index   = (int) $arg1 - 1;
+    }
+    elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
+      $argType = strtolower( $arg1 );
+      $index   = ( !empty( $arg2 ) && ctype_digit( (string) $arg2 )) ? (( int ) $arg2 - 1 ) : 0;
+    }
+    $cix2dC = 0;
+    foreach ( $this->components as $cix => $component) {
+      if( empty( $component )) continue;
+      if(( 'INDEX' == $argType ) && ( $index == $cix )) {
+        unset( $this->components[$cix] );
+        return TRUE;
+      }
+      elseif( $argType == $component->objName ) {
+        if( $index == $cix2dC ) {
+          unset( $this->components[$cix] );
+          return TRUE;
+        }
+        $cix2dC++;
+      }
+      elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) {
+        unset( $this->components[$cix] );
+        return TRUE;
+      }
+    }
+    return FALSE;
+  }
+/**
+ * get calendar component subcomponent from component container
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.8.8 - 2011-03-15
+ * @param mixed $arg1 optional, ordno/component type/ component uid
+ * @param mixed $arg2 optional, ordno if arg1 = component type
+ * @return object
+ */
+  function getComponent ( $arg1=FALSE, $arg2=FALSE ) {
+    if( !isset( $this->components )) return FALSE;
+    $index = $argType = null;
+    if ( !$arg1 ) {
+      $argType = 'INDEX';
+      $index   = $this->compix['INDEX'] =
+        ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1;
+    }
+    elseif ( ctype_digit( (string) $arg1 )) {
+      $argType = 'INDEX';
+      $index   = (int) $arg1;
+      unset( $this->compix );
+    }
+    elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
+      unset( $this->compix['INDEX'] );
+      $argType = strtolower( $arg1 );
+      if( !$arg2 )
+        $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1;
+      else
+        $index = (int) $arg2;
+    }
+    $index  -= 1;
+    $ckeys = array_keys( $this->components );
+    if( !empty( $index) && ( $index > end( $ckeys )))
+      return FALSE;
+    $cix2gC = 0;
+    foreach( $this->components as $cix => $component ) {
+      if( empty( $component )) continue;
+      if(( 'INDEX' == $argType ) && ( $index == $cix ))
+        return $component->copy();
+      elseif( $argType == $component->objName ) {
+         if( $index == $cix2gC )
+           return $component->copy();
+         $cix2gC++;
+      }
+      elseif( !$argType && ( $arg1 == $component->getProperty( 'uid' )))
+        return $component->copy();
+    }
+            /* not found.. . */
+    unset( $this->compix );
+    return false;
+  }
+/**
+ * add calendar component as subcomponent to container for subcomponents
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 1.x.x - 2007-04-24
+ * @param object $component calendar component
+ * @return void
+ */
+  function addSubComponent ( $component ) {
+    $this->setComponent( $component );
+  }
+/**
+ * create new calendar component subcomponent, already included within component
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.6.33 - 2011-01-03
+ * @param string $compType subcomponent type
+ * @return object (reference)
+ */
+  function & newComponent( $compType ) {
+    $config = $this->getConfig();
+    $keys   = array_keys( $this->components );
+    $ix     = end( $keys) + 1;
+    switch( strtoupper( $compType )) {
+      case 'ALARM':
+      case 'VALARM':
+        $this->components[$ix] = new valarm( $config );
+        break;
+      case 'STANDARD':
+        array_unshift( $this->components, new vtimezone( 'STANDARD', $config ));
+        $ix = 0;
+        break;
+      case 'DAYLIGHT':
+        $this->components[$ix] = new vtimezone( 'DAYLIGHT', $config );
+        break;
+      default:
+        return FALSE;
+    }
+    return $this->components[$ix];
+  }
+/**
+ * add calendar component as subcomponent to container for subcomponents
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.8.8 - 2011-03-15
+ * @param object $component calendar component
+ * @param mixed $arg1 optional, ordno/component type/ component uid
+ * @param mixed $arg2 optional, ordno if arg1 = component type
+ * @return bool
+ */
+  function setComponent( $component, $arg1=FALSE, $arg2=FALSE  ) {
+    if( !isset( $this->components )) return FALSE;
+    $component->setConfig( $this->getConfig(), FALSE, TRUE );
+    if( !in_array( $component->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) {
+            /* make sure dtstamp and uid is set */
+      $dummy = $component->getProperty( 'dtstamp' );
+      $dummy = $component->getProperty( 'uid' );
+    }
+    if( !$arg1 ) { // plain insert, last in chain
+      $this->components[] = $component->copy();
+      return TRUE;
+    }
+    $argType = $index = null;
+    if ( ctype_digit( (string) $arg1 )) { // index insert/replace
+      $argType = 'INDEX';
+      $index   = (int) $arg1 - 1;
+    }
+    elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) {
+      $argType = strtolower( $arg1 );
+      $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0;
+    }
+    // else if arg1 is set, arg1 must be an UID
+    $cix2sC = 0;
+    foreach ( $this->components as $cix => $component2 ) {
+      if( empty( $component2 )) continue;
+      if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace
+        $this->components[$cix] = $component->copy();
+        return TRUE;
+      }
+      elseif( $argType == $component2->objName ) { // component Type index insert/replace
+        if( $index == $cix2sC ) {
+          $this->components[$cix] = $component->copy();
+          return TRUE;
+        }
+        $cix2sC++;
+      }
+      elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace
+        $this->components[$cix] = $component->copy();
+        return TRUE;
+      }
+    }
+            /* arg1=index and not found.. . insert at index .. .*/
+    if( 'INDEX' == $argType ) {
+      $this->components[$index] = $component->copy();
+      ksort( $this->components, SORT_NUMERIC );
+    }
+    else    /* not found.. . insert last in chain anyway .. .*/
+    $this->components[] = $component->copy();
+    return TRUE;
+  }
+/**
+ * creates formatted output for subcomponents
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.11.20 - 2012-02-06
+ * @param array $xcaldecl
+ * @return string
+ */
+  function createSubComponent() {
+    $output = null;
+    if( 'vtimezone' == $this->objName ) { // sort subComponents, first standard, then daylight, in dtstart order
+      $stdarr = $dlarr = array();
+      foreach( $this->components as $component ) {
+        if( empty( $component ))
+          continue;
+        $dt  = $component->getProperty( 'dtstart' );
+        $key = sprintf( '%04d%02d%02d%02d%02d%02d000', $dt['year'], $dt['month'], $dt['day'], $dt['hour'], $dt['min'], $dt['sec'] );
+        if( 'standard' == $component->objName ) {
+          while( isset( $stdarr[$key] ))
+            $key += 1;
+          $stdarr[$key] = $component->copy();
+        }
+        elseif( 'daylight' == $component->objName ) {
+          while( isset( $dlarr[$key] ))
+            $key += 1;
+          $dlarr[$key] = $component->copy();
+        }
+      } // end foreach( $this->components as $component )
+      $this->components = array();
+      ksort( $stdarr, SORT_NUMERIC );
+      foreach( $stdarr as $std )
+        $this->components[] = $std->copy();
+      unset( $stdarr );
+      ksort( $dlarr,  SORT_NUMERIC );
+      foreach( $dlarr as $dl )
+        $this->components[] = $dl->copy();
+      unset( $dlarr );
+    } // end if( 'vtimezone' == $this->objName )
+    foreach( $this->components as $component ) {
+      $component->setConfig( $this->getConfig(), FALSE, TRUE );
+      $output .= $component->createComponent( $this->xcaldecl );
+    }
+    return $output;
+  }
+}
+/*********************************************************************************/
+/*********************************************************************************/
+/**
+ * class for calendar component VEVENT
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-10-12
+ */
+class vevent extends calendarComponent {
+  var $attach;
+  var $attendee;
+  var $categories;
+  var $comment;
+  var $contact;
+  var $class;
+  var $created;
+  var $description;
+  var $dtend;
+  var $dtstart;
+  var $duration;
+  var $exdate;
+  var $exrule;
+  var $geo;
+  var $lastmodified;
+  var $location;
+  var $organizer;
+  var $priority;
+  var $rdate;
+  var $recurrenceid;
+  var $relatedto;
+  var $requeststatus;
+  var $resources;
+  var $rrule;
+  var $sequence;
+  var $status;
+  var $summary;
+  var $transp;
+  var $url;
+  var $xprop;
+            //  component subcomponents container
+  var $components;
+/**
+ * constructor for calendar component VEVENT object
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.8.2 - 2011-05-01
+ * @param  array $config
+ * @return void
+ */
+  function vevent( $config = array()) {
+    $this->calendarComponent();
+
+    $this->attach          = '';
+    $this->attendee        = '';
+    $this->categories      = '';
+    $this->class           = '';
+    $this->comment         = '';
+    $this->contact         = '';
+    $this->created         = '';
+    $this->description     = '';
+    $this->dtstart         = '';
+    $this->dtend           = '';
+    $this->duration        = '';
+    $this->exdate          = '';
+    $this->exrule          = '';
+    $this->geo             = '';
+    $this->lastmodified    = '';
+    $this->location        = '';
+    $this->organizer       = '';
+    $this->priority        = '';
+    $this->rdate           = '';
+    $this->recurrenceid    = '';
+    $this->relatedto       = '';
+    $this->requeststatus   = '';
+    $this->resources       = '';
+    $this->rrule           = '';
+    $this->sequence        = '';
+    $this->status          = '';
+    $this->summary         = '';
+    $this->transp          = '';
+    $this->url             = '';
+    $this->xprop           = '';
+
+    $this->components      = array();
+
+    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
+                                          $config['language']   = ICAL_LANG;
+    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
+    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
+    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
+    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
+    $this->setConfig( $config );
+
+  }
+/**
+ * create formatted output for calendar component VEVENT object instance
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.10.16 - 2011-10-28
+ * @param array $xcaldecl
+ * @return string
+ */
+  function createComponent( &$xcaldecl ) {
+    $objectname    = $this->_createFormat();
+    $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
+    $component    .= $this->createUid();
+    $component    .= $this->createDtstamp();
+    $component    .= $this->createAttach();
+    $component    .= $this->createAttendee();
+    $component    .= $this->createCategories();
+    $component    .= $this->createComment();
+    $component    .= $this->createContact();
+    $component    .= $this->createClass();
+    $component    .= $this->createCreated();
+    $component    .= $this->createDescription();
+    $component    .= $this->createDtstart();
+    $component    .= $this->createDtend();
+    $component    .= $this->createDuration();
+    $component    .= $this->createExdate();
+    $component    .= $this->createExrule();
+    $component    .= $this->createGeo();
+    $component    .= $this->createLastModified();
+    $component    .= $this->createLocation();
+    $component    .= $this->createOrganizer();
+    $component    .= $this->createPriority();
+    $component    .= $this->createRdate();
+    $component    .= $this->createRrule();
+    $component    .= $this->createRelatedTo();
+    $component    .= $this->createRequestStatus();
+    $component    .= $this->createRecurrenceid();
+    $component    .= $this->createResources();
+    $component    .= $this->createSequence();
+    $component    .= $this->createStatus();
+    $component    .= $this->createSummary();
+    $component    .= $this->createTransp();
+    $component    .= $this->createUrl();
+    $component    .= $this->createXprop();
+    $component    .= $this->createSubComponent();
+    $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
+    if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
+      foreach( $this->xcaldecl as $localxcaldecl )
+        $xcaldecl[] = $localxcaldecl;
+    }
+    return $component;
+  }
+}
+/*********************************************************************************/
+/*********************************************************************************/
+/**
+ * class for calendar component VTODO
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-10-12
+ */
+class vtodo extends calendarComponent {
+  var $attach;
+  var $attendee;
+  var $categories;
+  var $comment;
+  var $completed;
+  var $contact;
+  var $class;
+  var $created;
+  var $description;
+  var $dtstart;
+  var $due;
+  var $duration;
+  var $exdate;
+  var $exrule;
+  var $geo;
+  var $lastmodified;
+  var $location;
+  var $organizer;
+  var $percentcomplete;
+  var $priority;
+  var $rdate;
+  var $recurrenceid;
+  var $relatedto;
+  var $requeststatus;
+  var $resources;
+  var $rrule;
+  var $sequence;
+  var $status;
+  var $summary;
+  var $url;
+  var $xprop;
+            //  component subcomponents container
+  var $components;
+/**
+ * constructor for calendar component VTODO object
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.8.2 - 2011-05-01
+ * @param array $config
+ * @return void
+ */
+  function vtodo( $config = array()) {
+    $this->calendarComponent();
+
+    $this->attach          = '';
+    $this->attendee        = '';
+    $this->categories      = '';
+    $this->class           = '';
+    $this->comment         = '';
+    $this->completed       = '';
+    $this->contact         = '';
+    $this->created         = '';
+    $this->description     = '';
+    $this->dtstart         = '';
+    $this->due             = '';
+    $this->duration        = '';
+    $this->exdate          = '';
+    $this->exrule          = '';
+    $this->geo             = '';
+    $this->lastmodified    = '';
+    $this->location        = '';
+    $this->organizer       = '';
+    $this->percentcomplete = '';
+    $this->priority        = '';
+    $this->rdate           = '';
+    $this->recurrenceid    = '';
+    $this->relatedto       = '';
+    $this->requeststatus   = '';
+    $this->resources       = '';
+    $this->rrule           = '';
+    $this->sequence        = '';
+    $this->status          = '';
+    $this->summary         = '';
+    $this->url             = '';
+    $this->xprop           = '';
+
+    $this->components      = array();
+
+    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
+                                          $config['language']   = ICAL_LANG;
+    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
+    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
+    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
+    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
+    $this->setConfig( $config );
+
+  }
+/**
+ * create formatted output for calendar component VTODO object instance
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-11-07
+ * @param array $xcaldecl
+ * @return string
+ */
+  function createComponent( &$xcaldecl ) {
+    $objectname    = $this->_createFormat();
+    $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
+    $component    .= $this->createUid();
+    $component    .= $this->createDtstamp();
+    $component    .= $this->createAttach();
+    $component    .= $this->createAttendee();
+    $component    .= $this->createCategories();
+    $component    .= $this->createClass();
+    $component    .= $this->createComment();
+    $component    .= $this->createCompleted();
+    $component    .= $this->createContact();
+    $component    .= $this->createCreated();
+    $component    .= $this->createDescription();
+    $component    .= $this->createDtstart();
+    $component    .= $this->createDue();
+    $component    .= $this->createDuration();
+    $component    .= $this->createExdate();
+    $component    .= $this->createExrule();
+    $component    .= $this->createGeo();
+    $component    .= $this->createLastModified();
+    $component    .= $this->createLocation();
+    $component    .= $this->createOrganizer();
+    $component    .= $this->createPercentComplete();
+    $component    .= $this->createPriority();
+    $component    .= $this->createRdate();
+    $component    .= $this->createRelatedTo();
+    $component    .= $this->createRequestStatus();
+    $component    .= $this->createRecurrenceid();
+    $component    .= $this->createResources();
+    $component    .= $this->createRrule();
+    $component    .= $this->createSequence();
+    $component    .= $this->createStatus();
+    $component    .= $this->createSummary();
+    $component    .= $this->createUrl();
+    $component    .= $this->createXprop();
+    $component    .= $this->createSubComponent();
+    $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
+    if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
+      foreach( $this->xcaldecl as $localxcaldecl )
+        $xcaldecl[] = $localxcaldecl;
+    }
+    return $component;
+  }
+}
+/*********************************************************************************/
+/*********************************************************************************/
+/**
+ * class for calendar component VJOURNAL
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-10-12
+ */
+class vjournal extends calendarComponent {
+  var $attach;
+  var $attendee;
+  var $categories;
+  var $comment;
+  var $contact;
+  var $class;
+  var $created;
+  var $description;
+  var $dtstart;
+  var $exdate;
+  var $exrule;
+  var $lastmodified;
+  var $organizer;
+  var $rdate;
+  var $recurrenceid;
+  var $relatedto;
+  var $requeststatus;
+  var $rrule;
+  var $sequence;
+  var $status;
+  var $summary;
+  var $url;
+  var $xprop;
+/**
+ * constructor for calendar component VJOURNAL object
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.8.2 - 2011-05-01
+ * @param array $config
+ * @return void
+ */
+  function vjournal( $config = array()) {
+    $this->calendarComponent();
+
+    $this->attach          = '';
+    $this->attendee        = '';
+    $this->categories      = '';
+    $this->class           = '';
+    $this->comment         = '';
+    $this->contact         = '';
+    $this->created         = '';
+    $this->description     = '';
+    $this->dtstart         = '';
+    $this->exdate          = '';
+    $this->exrule          = '';
+    $this->lastmodified    = '';
+    $this->organizer       = '';
+    $this->rdate           = '';
+    $this->recurrenceid    = '';
+    $this->relatedto       = '';
+    $this->requeststatus   = '';
+    $this->rrule           = '';
+    $this->sequence        = '';
+    $this->status          = '';
+    $this->summary         = '';
+    $this->url             = '';
+    $this->xprop           = '';
+
+    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
+                                          $config['language']   = ICAL_LANG;
+    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
+    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
+    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
+    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
+    $this->setConfig( $config );
+
+  }
+/**
+ * create formatted output for calendar component VJOURNAL object instance
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-10-12
+ * @param array $xcaldecl
+ * @return string
+ */
+  function createComponent( &$xcaldecl ) {
+    $objectname = $this->_createFormat();
+    $component  = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
+    $component .= $this->createUid();
+    $component .= $this->createDtstamp();
+    $component .= $this->createAttach();
+    $component .= $this->createAttendee();
+    $component .= $this->createCategories();
+    $component .= $this->createClass();
+    $component .= $this->createComment();
+    $component .= $this->createContact();
+    $component .= $this->createCreated();
+    $component .= $this->createDescription();
+    $component .= $this->createDtstart();
+    $component .= $this->createExdate();
+    $component .= $this->createExrule();
+    $component .= $this->createLastModified();
+    $component .= $this->createOrganizer();
+    $component .= $this->createRdate();
+    $component .= $this->createRequestStatus();
+    $component .= $this->createRecurrenceid();
+    $component .= $this->createRelatedTo();
+    $component .= $this->createRrule();
+    $component .= $this->createSequence();
+    $component .= $this->createStatus();
+    $component .= $this->createSummary();
+    $component .= $this->createUrl();
+    $component .= $this->createXprop();
+    $component .= $this->componentEnd1.$objectname.$this->componentEnd2;
+    if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
+      foreach( $this->xcaldecl as $localxcaldecl )
+        $xcaldecl[] = $localxcaldecl;
+    }
+    return $component;
+  }
+}
+/*********************************************************************************/
+/*********************************************************************************/
+/**
+ * class for calendar component VFREEBUSY
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-10-12
+ */
+class vfreebusy extends calendarComponent {
+  var $attendee;
+  var $comment;
+  var $contact;
+  var $dtend;
+  var $dtstart;
+  var $duration;
+  var $freebusy;
+  var $organizer;
+  var $requeststatus;
+  var $url;
+  var $xprop;
+            //  component subcomponents container
+  var $components;
+/**
+ * constructor for calendar component VFREEBUSY object
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.8.2 - 2011-05-01
+ * @param array $config
+ * @return void
+ */
+  function vfreebusy( $config = array()) {
+    $this->calendarComponent();
+
+    $this->attendee        = '';
+    $this->comment         = '';
+    $this->contact         = '';
+    $this->dtend           = '';
+    $this->dtstart         = '';
+    $this->duration        = '';
+    $this->freebusy        = '';
+    $this->organizer       = '';
+    $this->requeststatus   = '';
+    $this->url             = '';
+    $this->xprop           = '';
+
+    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
+                                          $config['language']   = ICAL_LANG;
+    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
+    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
+    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
+    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
+    $this->setConfig( $config );
+
+  }
+/**
+ * create formatted output for calendar component VFREEBUSY object instance
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.3.1 - 2007-11-19
+ * @param array $xcaldecl
+ * @return string
+ */
+  function createComponent( &$xcaldecl ) {
+    $objectname = $this->_createFormat();
+    $component  = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
+    $component .= $this->createUid();
+    $component .= $this->createDtstamp();
+    $component .= $this->createAttendee();
+    $component .= $this->createComment();
+    $component .= $this->createContact();
+    $component .= $this->createDtstart();
+    $component .= $this->createDtend();
+    $component .= $this->createDuration();
+    $component .= $this->createFreebusy();
+    $component .= $this->createOrganizer();
+    $component .= $this->createRequestStatus();
+    $component .= $this->createUrl();
+    $component .= $this->createXprop();
+    $component .= $this->componentEnd1.$objectname.$this->componentEnd2;
+    if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
+      foreach( $this->xcaldecl as $localxcaldecl )
+        $xcaldecl[] = $localxcaldecl;
+    }
+    return $component;
+  }
+}
+/*********************************************************************************/
+/*********************************************************************************/
+/**
+ * class for calendar component VALARM
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-10-12
+ */
+class valarm extends calendarComponent {
+  var $action;
+  var $attach;
+  var $attendee;
+  var $description;
+  var $duration;
+  var $repeat;
+  var $summary;
+  var $trigger;
+  var $xprop;
+/**
+ * constructor for calendar component VALARM object
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.8.2 - 2011-05-01
+ * @param array $config
+ * @return void
+ */
+  function valarm( $config = array()) {
+    $this->calendarComponent();
+
+    $this->action          = '';
+    $this->attach          = '';
+    $this->attendee        = '';
+    $this->description     = '';
+    $this->duration        = '';
+    $this->repeat          = '';
+    $this->summary         = '';
+    $this->trigger         = '';
+    $this->xprop           = '';
+
+    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
+                                          $config['language']   = ICAL_LANG;
+    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
+    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
+    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
+    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
+    $this->setConfig( $config );
+
+  }
+/**
+ * create formatted output for calendar component VALARM object instance
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-10-22
+ * @param array $xcaldecl
+ * @return string
+ */
+  function createComponent( &$xcaldecl ) {
+    $objectname    = $this->_createFormat();
+    $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
+    $component    .= $this->createAction();
+    $component    .= $this->createAttach();
+    $component    .= $this->createAttendee();
+    $component    .= $this->createDescription();
+    $component    .= $this->createDuration();
+    $component    .= $this->createRepeat();
+    $component    .= $this->createSummary();
+    $component    .= $this->createTrigger();
+    $component    .= $this->createXprop();
+    $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
+    if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
+      foreach( $this->xcaldecl as $localxcaldecl )
+        $xcaldecl[] = $localxcaldecl;
+    }
+    return $component;
+  }
+}
+/**********************************************************************************
+/*********************************************************************************/
+/**
+ * class for calendar component VTIMEZONE
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-10-12
+ */
+class vtimezone extends calendarComponent {
+  var $timezonetype;
+
+  var $comment;
+  var $dtstart;
+  var $lastmodified;
+  var $rdate;
+  var $rrule;
+  var $tzid;
+  var $tzname;
+  var $tzoffsetfrom;
+  var $tzoffsetto;
+  var $tzurl;
+  var $xprop;
+            //  component subcomponents container
+  var $components;
+/**
+ * constructor for calendar component VTIMEZONE object
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.8.2 - 2011-05-01
+ * @param mixed $timezonetype optional, default FALSE ( STANDARD / DAYLIGHT )
+ * @param array $config
+ * @return void
+ */
+  function vtimezone( $timezonetype=FALSE, $config = array()) {
+    if( is_array( $timezonetype )) {
+      $config       = $timezonetype;
+      $timezonetype = FALSE;
+    }
+    if( !$timezonetype )
+      $this->timezonetype = 'VTIMEZONE';
+    else
+      $this->timezonetype = strtoupper( $timezonetype );
+    $this->calendarComponent();
+
+    $this->comment         = '';
+    $this->dtstart         = '';
+    $this->lastmodified    = '';
+    $this->rdate           = '';
+    $this->rrule           = '';
+    $this->tzid            = '';
+    $this->tzname          = '';
+    $this->tzoffsetfrom    = '';
+    $this->tzoffsetto      = '';
+    $this->tzurl           = '';
+    $this->xprop           = '';
+
+    $this->components      = array();
+
+    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
+                                          $config['language']   = ICAL_LANG;
+    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
+    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
+    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
+    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
+    $this->setConfig( $config );
+
+  }
+/**
+ * create formatted output for calendar component VTIMEZONE object instance
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.5.1 - 2008-10-25
+ * @param array $xcaldecl
+ * @return string
+ */
+  function createComponent( &$xcaldecl ) {
+    $objectname    = $this->_createFormat();
+    $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
+    $component    .= $this->createTzid();
+    $component    .= $this->createLastModified();
+    $component    .= $this->createTzurl();
+    $component    .= $this->createDtstart();
+    $component    .= $this->createTzoffsetfrom();
+    $component    .= $this->createTzoffsetto();
+    $component    .= $this->createComment();
+    $component    .= $this->createRdate();
+    $component    .= $this->createRrule();
+    $component    .= $this->createTzname();
+    $component    .= $this->createXprop();
+    $component    .= $this->createSubComponent();
+    $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
+    if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
+      foreach( $this->xcaldecl as $localxcaldecl )
+        $xcaldecl[] = $localxcaldecl;
+    }
+    return $component;
+  }
+}
+/*********************************************************************************/
+/*********************************************************************************/
+/**
+ * moving all utility (static) functions to a utility class
+ * 20111223 - move iCalUtilityFunctions class to the end of the iCalcreator class file
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.10.1 - 2011-07-16
+ *
+ */
+class iCalUtilityFunctions {
+  // Store the single instance of iCalUtilityFunctions
+  private static $m_pInstance;
+
+  // Private constructor to limit object instantiation to within the class
+  private function __construct() {
+    $m_pInstance = FALSE;
+  }
+
+  // Getter method for creating/returning the single instance of this class
+  public static function getInstance() {
+    if (!self::$m_pInstance)
+      self::$m_pInstance = new iCalUtilityFunctions();
+
+    return self::$m_pInstance;
+  }
+/**
+ * ensures internal date-time/date format (keyed array) for an input date-time/date array (keyed or unkeyed)
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.1 - 2012-09-27
+ * @param array $datetime
+ * @param int $parno optional, default FALSE
+ * @return array
+ */
+  public static function _date_time_array( $datetime, $parno=FALSE ) {
+    return iCalUtilityFunctions::_chkDateArr( $datetime, $parno );
+  }
+  public static function _chkDateArr( $datetime, $parno=FALSE ) {
+    $output = array();
+    foreach( $datetime as $dateKey => $datePart ) {
+      switch ( $dateKey ) {
+        case '0': case 'year':   $output['year']  = $datePart; break;
+        case '1': case 'month':  $output['month'] = $datePart; break;
+        case '2': case 'day':    $output['day']   = $datePart; break;
+      }
+      if( 3 != $parno ) {
+        switch ( $dateKey ) {
+          case '0':
+          case '1':
+          case '2': break;
+          case '3': case 'hour': $output['hour']  = $datePart; break;
+          case '4': case 'min' : $output['min']   = $datePart; break;
+          case '5': case 'sec' : $output['sec']   = $datePart; break;
+          case '6': case 'tz'  : $output['tz']    = $datePart; break;
+        }
+      }
+    }
+    if( 3 != $parno ) {
+      if( !isset( $output['hour'] ))         $output['hour'] = 0;
+      if( !isset( $output['min']  ))         $output['min']  = 0;
+      if( !isset( $output['sec']  ))         $output['sec']  = 0;
+      if( isset( $output['tz'] ) &&
+        (( '+0000' == $output['tz'] ) || ( '-0000' == $output['tz'] ) || ( '+000000' == $output['tz'] ) || ( '-000000' == $output['tz'] )))
+                                             $output['tz']   = 'Z';
+    }
+    return $output;
+  }
+/**
+ * check date(-time) and params arrays for an opt. timezone and if it is a DATE-TIME or DATE (updates $parno and params)
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.10.30 - 2012-01-16
+ * @param array $date, date to check
+ * @param int $parno, no of date parts (i.e. year, month.. .)
+ * @param array $params, property parameters
+ * @return void
+ */
+  public static function _chkdatecfg( $theDate, & $parno, & $params ) {
+    if( isset( $params['TZID'] ))
+      $parno = 6;
+    elseif( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] ))
+      $parno = 3;
+    else {
+      if( isset( $params['VALUE'] ) && ( 'PERIOD' == $params['VALUE'] ))
+        $parno = 7;
+      if( is_array( $theDate )) {
+        if( isset( $theDate['timestamp'] ))
+          $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : null;
+        else
+          $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : ( 7 == count( $theDate )) ? end( $theDate ) : null;
+        if( !empty( $tzid )) {
+          $parno = 7;
+          if( !iCalUtilityFunctions::_isOffset( $tzid ))
+            $params['TZID'] = $tzid; // save only timezone
+        }
+        elseif( !$parno && ( 3 == count( $theDate )) &&
+          ( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] )))
+          $parno = 3;
+        else
+          $parno = 6;
+      }
+      else { // string
+        $date = trim( $theDate );
+        if( 'Z' == substr( $date, -1 ))
+          $parno = 7; // UTC DATE-TIME
+        elseif((( 8 == strlen( $date ) && ctype_digit( $date )) || ( 11 >= strlen( $date ))) &&
+          ( !isset( $params['VALUE'] ) || !in_array( $params['VALUE'], array( 'DATE-TIME', 'PERIOD' ))))
+          $parno = 3; // DATE
+        $date = iCalUtilityFunctions::_strdate2date( $date, $parno );
+        unset( $date['unparsedtext'] );
+        if( !empty( $date['tz'] )) {
+          $parno = 7;
+          if( !iCalUtilityFunctions::_isOffset( $date['tz'] ))
+            $params['TZID'] = $date['tz']; // save only timezone
+        }
+        elseif( empty( $parno ))
+          $parno = 6;
+      }
+      if( isset( $params['TZID'] ))
+        $parno = 6;
+    }
+  }
+/**
+ * vcalendar sort callback function
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.2 - 2012-12-17
+ * @param array $a
+ * @param array $b
+ * @return int
+ */
+  public static function _cmpfcn( $a, $b ) {
+    if(        empty( $a ))                       return -1;
+    if(        empty( $b ))                       return  1;
+    if( 'vtimezone' == $a->objName ) {
+      if( 'vtimezone' != $b->objName )            return -1;
+      elseif( $a->srtk[0] <= $b->srtk[0] )        return -1;
+      else                                        return  1;
+    }
+    elseif( 'vtimezone' == $b->objName )          return  1;
+    $sortkeys = array( 'year', 'month', 'day', 'hour', 'min', 'sec' );
+    for( $k = 0; $k < 4 ; $k++ ) {
+      if(        empty( $a->srtk[$k] ))           return -1;
+      elseif(    empty( $b->srtk[$k] ))           return  1;
+      if( is_array( $a->srtk[$k] )) {
+        if( is_array( $b->srtk[$k] )) {
+          foreach( $sortkeys as $key ) {
+            if    ( !isset( $a->srtk[$k][$key] )) return -1;
+            elseif( !isset( $b->srtk[$k][$key] )) return  1;
+            if    (  empty( $a->srtk[$k][$key] )) return -1;
+            elseif(  empty( $b->srtk[$k][$key] )) return  1;
+            if    (         $a->srtk[$k][$key] == $b->srtk[$k][$key])
+                                                  continue;
+            if    ((  (int) $a->srtk[$k][$key] ) < ((int) $b->srtk[$k][$key] ))
+                                                  return -1;
+            elseif((  (int) $a->srtk[$k][$key] ) > ((int) $b->srtk[$k][$key] ))
+                                                  return  1;
+          }
+        }
+        else                                      return -1;
+      }
+      elseif( is_array( $b->srtk[$k] ))           return  1;
+      elseif( $a->srtk[$k] < $b->srtk[$k] )       return -1;
+      elseif( $a->srtk[$k] > $b->srtk[$k] )       return  1;
+    }
+    return 0;
+  }
+/**
+ * byte oriented line folding fix
+ *
+ * remove any line-endings that may include spaces or tabs
+ * and convert all line endings (iCal default '\r\n'),
+ * takes care of '\r\n', '\r' and '\n' and mixed '\r\n'+'\r', '\r\n'+'\n'
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.12.17 - 2012-07-12
+ * @param string $text
+ * @param string $nl
+ * @return string
+ */
+  public static function convEolChar( & $text, $nl ) {
+    $outp = '';
+    $cix  = 0;
+    while(    isset(   $text[$cix] )) {
+      if(     isset(   $text[$cix + 2] ) &&  ( "\r" == $text[$cix] ) && ( "\n" == $text[$cix + 1] ) &&
+        ((    " " ==   $text[$cix + 2] ) ||  ( "\t" == $text[$cix + 2] )))                    // 2 pos eolchar + ' ' or '\t'
+        $cix  += 2;                                                                           // skip 3
+      elseif( isset(   $text[$cix + 1] ) &&  ( "\r" == $text[$cix] ) && ( "\n" == $text[$cix + 1] )) {
+        $outp .= $nl;                                                                         // 2 pos eolchar
+        $cix  += 1;                                                                           // replace with $nl
+      }
+      elseif( isset(   $text[$cix + 1] ) && (( "\r" == $text[$cix] ) || ( "\n" == $text[$cix] )) &&
+           (( " " ==   $text[$cix + 1] ) ||  ( "\t" == $text[$cix + 1] )))                     // 1 pos eolchar + ' ' or '\t'
+        $cix  += 1;                                                                            // skip 2
+      elseif(( "\r" == $text[$cix] )     ||  ( "\n" == $text[$cix] ))                          // 1 pos eolchar
+        $outp .= $nl;                                                                          // replace with $nl
+      else
+        $outp .= $text[$cix];                                                                  // add any other byte
+      $cix    += 1;
+    }
+    return $outp;
+  }
+/**
+ * create a calendar timezone and standard/daylight components
+ *
+ * Result when 'Europe/Stockholm' and no from/to arguments is used as timezone:
+ *
+ * BEGIN:VTIMEZONE
+ * TZID:Europe/Stockholm
+ * BEGIN:STANDARD
+ * DTSTART:20101031T020000
+ * TZOFFSETFROM:+0200
+ * TZOFFSETTO:+0100
+ * TZNAME:CET
+ * END:STANDARD
+ * BEGIN:DAYLIGHT
+ * DTSTART:20100328T030000
+ * TZOFFSETFROM:+0100
+ * TZOFFSETTO:+0200
+ * TZNAME:CEST
+ * END:DAYLIGHT
+ * END:VTIMEZONE
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.1 - 2012-11-26
+ * Generates components for all transitions in a date range, based on contribution by Yitzchok Lavi <icalcreator@onebigsystem.com>
+ * Additional changes jpirkey
+ * @param object $calendar, reference to an iCalcreator calendar instance
+ * @param string $timezone, a PHP5 (DateTimeZone) valid timezone
+ * @param array  $xProp,    *[x-propName => x-propValue], optional
+ * @param int    $from      a unix timestamp
+ * @param int    $to        a unix timestamp
+ * @return bool
+ */
+  public static function createTimezone( & $calendar, $timezone, $xProp=array(), $from=null, $to=null ) {
+    if( empty( $timezone ))
+      return FALSE;
+    if( !empty( $from ) && !is_int( $from ))
+      return FALSE;
+    if( !empty( $to )   && !is_int( $to ))
+      return FALSE;
+    try {
+      $dtz               = new DateTimeZone( $timezone );
+      $transitions       = $dtz->getTransitions();
+      $utcTz             = new DateTimeZone( 'UTC' );
+    }
+    catch( Exception $e ) { return FALSE; }
+    if( empty( $to )) {
+      $dates             = array_keys( $calendar->getProperty( 'dtstart' ));
+      if( empty( $dates ))
+        $dates           = array( date( 'Ymd' ));
+    }
+    if( !empty( $from ))
+      $dateFrom          = new DateTime( "@$from" );             // set lowest date (UTC)
+    else {
+      $from              = reset( $dates );                      // set lowest date to the lowest dtstart date
+      $dateFrom          = new DateTime( $from.'T000000', $dtz );
+      $dateFrom->modify( '-1 month' );                           // set $dateFrom to one month before the lowest date
+      $dateFrom->setTimezone( $utcTz );                          // convert local date to UTC
+    }
+    $dateFromYmd         = $dateFrom->format('Y-m-d' );
+    if( !empty( $to ))
+      $dateTo            = new DateTime( "@$to" );               // set end date (UTC)
+    else {
+      $to                = end( $dates );                        // set highest date to the highest dtstart date
+      $dateTo            = new DateTime( $to.'T235959', $dtz );
+      $dateTo->modify( '+1 year' );                              // set $dateTo to one year after the highest date
+      $dateTo->setTimezone( $utcTz );                            // convert local date to UTC
+    }
+    $dateToYmd           = $dateTo->format('Y-m-d' );
+    unset( $dtz );
+    $transTemp           = array();
+    $prevOffsetfrom      = 0;
+    $stdIx  = $dlghtIx   = null;
+    $prevTrans           = FALSE;
+    foreach( $transitions as $tix => $trans ) {                  // all transitions in date-time order!!
+      $date              = new DateTime( "@{$trans['ts']}" );    // set transition date (UTC)
+      $transDateYmd      = $date->format('Y-m-d' );
+      if ( $transDateYmd < $dateFromYmd ) {
+        $prevOffsetfrom  = $trans['offset'];                     // previous trans offset will be 'next' trans offsetFrom
+        $prevTrans       = $trans;                               // save it in case we don't find any that match
+        $prevTrans['offsetfrom'] = ( 0 < $tix ) ? $transitions[$tix-1]['offset'] : 0;
+        continue;
+      }
+      if( $transDateYmd > $dateToYmd )
+        break;                                                   // loop always (?) breaks here
+      if( !empty( $prevOffsetfrom ) || ( 0 == $prevOffsetfrom )) {
+        $trans['offsetfrom'] = $prevOffsetfrom;                  // i.e. set previous offsetto as offsetFrom
+        $date->modify( $trans['offsetfrom'].'seconds' );         // convert utc date to local date
+        $d = $date->format( 'Y-n-j-G-i-s' );                     // set date to array to ease up dtstart and (opt) rdate setting
+        $d = explode( '-', $d );
+        $trans['time']   = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
+      }
+      $prevOffsetfrom    = $trans['offset'];
+      if( TRUE !== $trans['isdst'] ) {                           // standard timezone
+        if( !empty( $stdIx ) && isset( $transTemp[$stdIx]['offsetfrom'] )  && // check for any repeating rdate's (in order)
+           ( $transTemp[$stdIx]['abbr']       ==   $trans['abbr'] )        &&
+           ( $transTemp[$stdIx]['offsetfrom'] ==   $trans['offsetfrom'] )  &&
+           ( $transTemp[$stdIx]['offset']     ==   $trans['offset'] )) {
+          $transTemp[$stdIx]['rdate'][]        =   $trans['time'];
+          continue;
+        }
+        $stdIx           = $tix;
+      } // end standard timezone
+      else {                                                     // daylight timezone
+        if( !empty( $dlghtIx ) && isset( $transTemp[$dlghtIx]['offsetfrom'] ) && // check for any repeating rdate's (in order)
+           ( $transTemp[$dlghtIx]['abbr']       ==   $trans['abbr'] )         &&
+           ( $transTemp[$dlghtIx]['offsetfrom'] ==   $trans['offsetfrom'] )   &&
+           ( $transTemp[$dlghtIx]['offset']     ==   $trans['offset'] )) {
+          $transTemp[$dlghtIx]['rdate'][]        =   $trans['time'];
+          continue;
+        }
+        $dlghtIx         = $tix;
+      } // end daylight timezone
+      $transTemp[$tix]   = $trans;
+    } // end foreach( $transitions as $tix => $trans )
+    $tz  = & $calendar->newComponent( 'vtimezone' );
+    $tz->setproperty( 'tzid', $timezone );
+    if( !empty( $xProp )) {
+      foreach( $xProp as $xPropName => $xPropValue )
+        if( 'x-' == strtolower( substr( $xPropName, 0, 2 )))
+          $tz->setproperty( $xPropName, $xPropValue );
+    }
+    if( empty( $transTemp )) {      // if no match found
+      if( $prevTrans ) {            // then we use the last transition (before startdate) for the tz info
+        $date = new DateTime( "@{$prevTrans['ts']}" );           // set transition date (UTC)
+        $date->modify( $prevTrans['offsetfrom'].'seconds' );     // convert utc date to local date
+        $d = $date->format( 'Y-n-j-G-i-s' );                     // set date to array to ease up dtstart setting
+        $d = explode( '-', $d );
+        $prevTrans['time'] = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
+        $transTemp[0] = $prevTrans;
+      }
+      else {                        // or we use the timezone identifier to BUILD the standard tz info (?)
+        $date = new DateTime( 'now', new DateTimeZone( $timezone ));
+        $transTemp[0] = array( 'time'       => $date->format( 'Y-m-d\TH:i:s O' )
+                             , 'offset'     => $date->format( 'Z' )
+                             , 'offsetfrom' => $date->format( 'Z' )
+                             , 'isdst'      => FALSE );
+      }
+    }
+    unset( $transitions, $date, $prevTrans );
+    foreach( $transTemp as $tix => $trans ) {
+      $type  = ( TRUE !== $trans['isdst'] ) ? 'standard' : 'daylight';
+      $scomp = & $tz->newComponent( $type );
+      $scomp->setProperty( 'dtstart',         $trans['time'] );
+//      $scomp->setProperty( 'x-utc-timestamp', $tix.' : '.$trans['ts'] );   // test ###
+      if( !empty( $trans['abbr'] ))
+        $scomp->setProperty( 'tzname',        $trans['abbr'] );
+      if( isset( $trans['offsetfrom'] ))
+        $scomp->setProperty( 'tzoffsetfrom',  iCalUtilityFunctions::offsetSec2His( $trans['offsetfrom'] ));
+      $scomp->setProperty( 'tzoffsetto',      iCalUtilityFunctions::offsetSec2His( $trans['offset'] ));
+      if( isset( $trans['rdate'] ))
+        $scomp->setProperty( 'RDATE',         $trans['rdate'] );
+    }
+    return TRUE;
+  }
+/**
+ * creates formatted output for calendar component property data value type date/date-time
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.1 - 2012-09-17
+ * @param array   $datetime
+ * @param int     $parno, optional, default 6
+ * @return string
+ */
+  public static function _format_date_time( $datetime, $parno=6 ) {
+    return iCalUtilityFunctions::_date2strdate( $datetime, $parno );
+  }
+  public static function _date2strdate( $datetime, $parno=6 ) {
+    if( !isset( $datetime['year'] )  &&
+        !isset( $datetime['month'] ) &&
+        !isset( $datetime['day'] )   &&
+        !isset( $datetime['hour'] )  &&
+        !isset( $datetime['min'] )   &&
+        !isset( $datetime['sec'] ))
+      return;
+    $output     = null;
+    foreach( $datetime as $dkey => & $dvalue )
+      if( 'tz' != $dkey ) $dvalue = (integer) $dvalue;
+    $output = sprintf( '%04d%02d%02d', $datetime['year'], $datetime['month'], $datetime['day'] );
+    if( 3 == $parno )
+      return $output;
+    if( !isset( $datetime['hour'] )) $datetime['hour'] = 0;
+    if( !isset( $datetime['min'] ))  $datetime['min']  = 0;
+    if( !isset( $datetime['sec'] ))  $datetime['sec']  = 0;
+    $output    .= sprintf( 'T%02d%02d%02d', $datetime['hour'], $datetime['min'], $datetime['sec'] );
+    if( isset( $datetime['tz'] ) && ( '' < trim( $datetime['tz'] ))) {
+      $datetime['tz'] = trim( $datetime['tz'] );
+      if( 'Z'  == $datetime['tz'] )
+        $parno  = 7;
+      elseif( iCalUtilityFunctions::_isOffset( $datetime['tz'] )) {
+        $parno  = 7;
+        $offset = iCalUtilityFunctions::_tz2offset( $datetime['tz'] );
+        try {
+          $d    = new DateTime( $output, new DateTimeZone( 'UTC' ));
+          if( 0 != $offset ) // adjust för offset
+            $d->modify( "$offset seconds" );
+          $output = $d->format( 'Ymd\THis' );
+        }
+        catch( Exception $e ) {
+          $output = date( 'Ymd\THis', mktime( $datetime['hour'], $datetime['min'], ($datetime['sec'] - $offset), $datetime['month'], $datetime['day'], $datetime['year'] ));
+        }
+      }
+      if( 7 == $parno )
+        $output .= 'Z';
+    }
+    return $output;
+  }
+/**
+ * convert a date/datetime (array) to timestamp
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.1 - 2012-09-29
+ * @param array  $datetime  datetime(/date)
+ * @param string $wtz       timezone
+ * @return int
+ */
+  public static function _date2timestamp( $datetime, $wtz=null ) {
+    if( !isset( $datetime['hour'] )) $datetime['hour'] = 0;
+    if( !isset( $datetime['min'] ))  $datetime['min']  = 0;
+    if( !isset( $datetime['sec'] ))  $datetime['sec']  = 0;
+    if( empty( $wtz ) && ( !isset( $datetime['tz'] ) || empty(  $datetime['tz'] )))
+      return mktime( $datetime['hour'], $datetime['min'], $datetime['sec'], $datetime['month'], $datetime['day'], $datetime['year'] );
+    $output = $offset = 0;
+    if( empty( $wtz )) {
+      if( iCalUtilityFunctions::_isOffset( $datetime['tz'] )) {
+        $offset = iCalUtilityFunctions::_tz2offset( $datetime['tz'] ) * -1;
+        $wtz    = 'UTC';
+      }
+      else
+        $wtz    = $datetime['tz'];
+    }
+    if(( 'Z' == $wtz ) || ( 'GMT' == strtoupper( $wtz )))
+      $wtz      = 'UTC';
+    try {
+      $strdate  = sprintf( '%04d-%02d-%02d %02d:%02d:%02d', $datetime['year'], $datetime['month'], $datetime['day'], $datetime['hour'], $datetime['min'], $datetime['sec'] );
+      $d        = new DateTime( $strdate, new DateTimeZone( $wtz ));
+      if( 0    != $offset )  // adjust for offset
+        $d->modify( $offset.' seconds' );
+      $output   = $d->format( 'U' );
+      unset( $d );
+    }
+    catch( Exception $e ) {
+      $output = mktime( $datetime['hour'], $datetime['min'], $datetime['sec'], $datetime['month'], $datetime['day'], $datetime['year'] );
+    }
+    return $output;
+  }
+/**
+ * ensures internal duration format for input in array format
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.1 - 2012-09-25
+ * @param array $duration
+ * @return array
+ */
+  public static function _duration_array( $duration ) {
+    return iCalUtilityFunctions::_duration2arr( $duration );
+  }
+  public static function _duration2arr( $duration ) {
+    $output = array();
+    if(    is_array( $duration )        &&
+       ( 1 == count( $duration ))       &&
+              isset( $duration['sec'] ) &&
+              ( 60 < $duration['sec'] )) {
+      $durseconds  = $duration['sec'];
+      $output['week'] = (int) floor( $durseconds / ( 60 * 60 * 24 * 7 ));
+      $durseconds     =              $durseconds % ( 60 * 60 * 24 * 7 );
+      $output['day']  = (int) floor( $durseconds / ( 60 * 60 * 24 ));
+      $durseconds     =              $durseconds % ( 60 * 60 * 24 );
+      $output['hour'] = (int) floor( $durseconds / ( 60 * 60 ));
+      $durseconds     =              $durseconds % ( 60 * 60 );
+      $output['min']  = (int) floor( $durseconds / ( 60 ));
+      $output['sec']  =            ( $durseconds % ( 60 ));
+    }
+    else {
+      foreach( $duration as $durKey => $durValue ) {
+        if( empty( $durValue )) continue;
+        switch ( $durKey ) {
+          case '0': case 'week': $output['week']  = $durValue; break;
+          case '1': case 'day':  $output['day']   = $durValue; break;
+          case '2': case 'hour': $output['hour']  = $durValue; break;
+          case '3': case 'min':  $output['min']   = $durValue; break;
+          case '4': case 'sec':  $output['sec']   = $durValue; break;
+        }
+      }
+    }
+    if( isset( $output['week'] ) && ( 0 < $output['week'] )) {
+      unset( $output['day'], $output['hour'], $output['min'], $output['sec'] );
+      return $output;
+    }
+    unset( $output['week'] );
+    if( empty( $output['day'] ))
+      unset( $output['day'] );
+    if ( isset( $output['hour'] ) || isset( $output['min'] ) || isset( $output['sec'] )) {
+      if( !isset( $output['hour'] )) $output['hour'] = 0;
+      if( !isset( $output['min']  )) $output['min']  = 0;
+      if( !isset( $output['sec']  )) $output['sec']  = 0;
+      if(( 0 == $output['hour'] ) && ( 0 == $output['min'] ) && ( 0 == $output['sec'] ))
+        unset( $output['hour'], $output['min'], $output['sec'] );
+    }
+    return $output;
+  }
+/**
+ * convert startdate+duration to a array format datetime
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.15.12 - 2012-10-31
+ * @param array   $startdate
+ * @param array   $duration
+ * @return array, date format
+ */
+  public static function _duration2date( $startdate, $duration ) {
+    $dateOnly          = ( isset( $startdate['hour'] ) || isset( $startdate['min'] ) || isset( $startdate['sec'] )) ? FALSE : TRUE;
+    $startdate['hour'] = ( isset( $startdate['hour'] )) ? $startdate['hour'] : 0;
+    $startdate['min']  = ( isset( $startdate['min'] ))  ? $startdate['min']  : 0;
+    $startdate['sec']  = ( isset( $startdate['sec'] ))  ? $startdate['sec']  : 0;
+    $dtend = 0;
+    if(    isset( $duration['week'] )) $dtend += ( $duration['week'] * 7 * 24 * 60 * 60 );
+    if(    isset( $duration['day'] ))  $dtend += ( $duration['day'] * 24 * 60 * 60 );
+    if(    isset( $duration['hour'] )) $dtend += ( $duration['hour'] * 60 *60 );
+    if(    isset( $duration['min'] ))  $dtend += ( $duration['min'] * 60 );
+    if(    isset( $duration['sec'] ))  $dtend +=   $duration['sec'];
+    $date     = date( 'Y-m-d-H-i-s', mktime((int) $startdate['hour'], (int) $startdate['min'], (int) ( $startdate['sec'] + $dtend ), (int) $startdate['month'], (int) $startdate['day'], (int) $startdate['year'] ));
+    $d        = explode( '-', $date );
+    $dtend2   = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
+    if( isset( $startdate['tz'] ))
+      $dtend2['tz']   = $startdate['tz'];
+    if( $dateOnly && (( 0 == $dtend2['hour'] ) && ( 0 == $dtend2['min'] ) && ( 0 == $dtend2['sec'] )))
+      unset( $dtend2['hour'], $dtend2['min'], $dtend2['sec'] );
+    return $dtend2;
+  }
+/**
+ * ensures internal duration format for an input string (iCal) formatted duration
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.1 - 2012-09-25
+ * @param string $duration
+ * @return array
+ */
+  public static function _duration_string( $duration ) {
+    return iCalUtilityFunctions::_durationStr2arr( $duration );
+  }
+  public static function _durationStr2arr( $duration ) {
+    $duration = (string) trim( $duration );
+    while( 'P' != strtoupper( substr( $duration, 0, 1 ))) {
+      if( 0 < strlen( $duration ))
+        $duration = substr( $duration, 1 );
+      else
+        return false; // no leading P !?!?
+    }
+    $duration = substr( $duration, 1 ); // skip P
+    $duration = str_replace ( 't', 'T', $duration );
+    $duration = str_replace ( 'T', '', $duration );
+    $output = array();
+    $val    = null;
+    for( $ix=0; $ix < strlen( $duration ); $ix++ ) {
+      switch( strtoupper( substr( $duration, $ix, 1 ))) {
+       case 'W':
+         $output['week'] = $val;
+         $val            = null;
+         break;
+       case 'D':
+         $output['day']  = $val;
+         $val            = null;
+         break;
+       case 'H':
+         $output['hour'] = $val;
+         $val            = null;
+         break;
+       case 'M':
+         $output['min']  = $val;
+         $val            = null;
+         break;
+       case 'S':
+         $output['sec']  = $val;
+         $val            = null;
+         break;
+       default:
+         if( !ctype_digit( substr( $duration, $ix, 1 )))
+           return false; // unknown duration control character  !?!?
+         else
+           $val .= substr( $duration, $ix, 1 );
+      }
+    }
+    return iCalUtilityFunctions::_duration2arr( $output );
+  }
+/**
+ * creates formatted output for calendar component property data value type duration
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.15.8 - 2012-10-30
+ * @param array $duration, array( week, day, hour, min, sec )
+ * @return string
+ */
+  public static function _format_duration( $duration ) {
+    return iCalUtilityFunctions::_duration2str( $duration );
+  }
+  public static function _duration2str( $duration ) {
+    if( isset( $duration['week'] ) ||
+        isset( $duration['day'] )  ||
+        isset( $duration['hour'] ) ||
+        isset( $duration['min'] )  ||
+        isset( $duration['sec'] ))
+       $ok = TRUE;
+    else
+      return;
+    if( isset( $duration['week'] ) && ( 0 < $duration['week'] ))
+      return 'P'.$duration['week'].'W';
+    $output = 'P';
+    if( isset($duration['day'] ) && ( 0 < $duration['day'] ))
+      $output .= $duration['day'].'D';
+    if(( isset( $duration['hour']) && ( 0 < $duration['hour'] )) ||
+       ( isset( $duration['min'])  && ( 0 < $duration['min'] ))  ||
+       ( isset( $duration['sec'])  && ( 0 < $duration['sec'] ))) {
+      $output .= 'T';
+      $output .= ( isset( $duration['hour']) && ( 0 < $duration['hour'] )) ? $duration['hour'].'H' : '0H';
+      $output .= ( isset( $duration['min'])  && ( 0 < $duration['min'] ))  ? $duration['min']. 'M' : '0M';
+      $output .= ( isset( $duration['sec'])  && ( 0 < $duration['sec'] ))  ? $duration['sec']. 'S' : '0S';
+    }
+    if( 'P' == $output )
+      $output = 'PT0H0M0S';
+    return $output;
+  }
+/**
+ * removes expkey+expvalue from array and returns hitval (if found) else returns elseval
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.16 - 2008-11-08
+ * @param array $array
+ * @param string $expkey, expected key
+ * @param string $expval, expected value
+ * @param int $hitVal optional, return value if found
+ * @param int $elseVal optional, return value if not found
+ * @param int $preSet optional, return value if already preset
+ * @return int
+ */
+  public static function _existRem( &$array, $expkey, $expval=FALSE, $hitVal=null, $elseVal=null, $preSet=null ) {
+    if( $preSet )
+      return $preSet;
+    if( !is_array( $array ) || ( 0 == count( $array )))
+      return $elseVal;
+    foreach( $array as $key => $value ) {
+      if( strtoupper( $expkey ) == strtoupper( $key )) {
+        if( !$expval || ( strtoupper( $expval ) == strtoupper( $array[$key] ))) {
+          unset( $array[$key] );
+          return $hitVal;
+        }
+      }
+    }
+    return $elseVal;
+  }
+/**
+ * checks if input contains a (array formatted) date/time
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.11.8 - 2012-01-20
+ * @param array $input
+ * @return bool
+ */
+  public static function _isArrayDate( $input ) {
+    if( !is_array( $input ))
+      return FALSE;
+    if( isset( $input['week'] ) || ( !in_array( count( $input ), array( 3, 6, 7 ))))
+      return FALSE;
+    if( 7 == count( $input ))
+      return TRUE;
+    if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] ))
+      return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] );
+    if( isset( $input['day'] ) || isset( $input['hour'] ) || isset( $input['min'] ) || isset( $input['sec'] ))
+      return FALSE;
+    if( in_array( 0, $input ))
+      return FALSE;
+    if(( 1970 > $input[0] ) || ( 12 < $input[1] ) || ( 31 < $input[2] ))
+      return FALSE;
+    if(( isset( $input[0] ) && isset( $input[1] ) && isset( $input[2] )) &&
+         checkdate( (int) $input[1], (int) $input[2], (int) $input[0] ))
+      return TRUE;
+    $input = iCalUtilityFunctions::_strdate2date( $input[1].'/'.$input[2].'/'.$input[0], 3 ); //  m - d - Y
+    if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] ))
+      return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] );
+    return FALSE;
+  }
+/**
+ * checks if input array contains a timestamp date
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.4.16 - 2008-10-18
+ * @param array $input
+ * @return bool
+ */
+  public static function _isArrayTimestampDate( $input ) {
+    return ( is_array( $input ) && isset( $input['timestamp'] )) ? TRUE : FALSE ;
+  }
+/**
+ * controls if input string contains (trailing) UTC/iCal offset
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.1 - 2012-09-21
+ * @param string $input
+ * @return bool
+ */
+  public static function _isOffset( $input ) {
+    $input         = trim( (string) $input );
+    if( 'Z' == substr( $input, -1 ))
+      return TRUE;
+    elseif((   5 <= strlen( $input )) &&
+       ( in_array( substr( $input, -5, 1 ), array( '+', '-' ))) &&
+       (   '0000' <= substr( $input, -4 )) && (   '9999' >= substr( $input, -4 )))
+      return TRUE;
+    elseif((    7 <= strlen( $input )) &&
+       ( in_array( substr( $input, -7, 1 ), array( '+', '-' ))) &&
+       ( '000000' <= substr( $input, -6 )) && ( '999999' >= substr( $input, -6 )))
+      return TRUE;
+    return FALSE;
+  }
+/**
+ * (very simple) conversion of a MS timezone to a PHP5 valid (Date-)timezone
+ * matching (MS) UCT offset and time zone descriptors
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.1 - 2012-09-16
+ * @param string $timezone, input/output variable reference
+ * @return bool
+ */
+  public static function ms2phpTZ( & $timezone ) {
+    if( empty( $timezone ))
+      return FALSE;
+    $search = str_replace( '"', '', $timezone );
+    $search = str_replace( array('GMT', 'gmt', 'utc' ), 'UTC', $search );
+    if( '(UTC' != substr( $search, 0, 4 ))
+      return FALSE;
+    if( FALSE === ( $pos = strpos( $search, ')' )))
+      return FALSE;
+    $pos    = strpos( $search, ')' );
+    $searchOffset = substr( $search, 4, ( $pos - 4 ));
+    $searchOffset = iCalUtilityFunctions::_tz2offset( str_replace( ':', '', $searchOffset ));
+    while( ' ' ==substr( $search, ( $pos + 1 )))
+      $pos += 1;
+    $searchText   = trim( str_replace( array( '(', ')', '&', ',', '  ' ), ' ', substr( $search, ( $pos + 1 )) ));
+    $searchWords  = explode( ' ', $searchText );
+    $timezone_abbreviations = DateTimeZone::listAbbreviations();
+    $hits = array();
+    foreach( $timezone_abbreviations as $name => $transitions ) {
+      foreach( $transitions as $cnt => $transition ) {
+        if( empty( $transition['offset'] )      ||
+            empty( $transition['timezone_id'] ) ||
+          ( $transition['offset'] != $searchOffset ))
+        continue;
+        $cWords = explode( '/', $transition['timezone_id'] );
+        $cPrio   = $hitCnt = $rank = 0;
+        foreach( $cWords as $cWord ) {
+          if( empty( $cWord ))
+            continue;
+          $cPrio += 1;
+          $sPrio  = 0;
+          foreach( $searchWords as $sWord ) {
+            if( empty( $sWord ) || ( 'time' == strtolower( $sWord )))
+              continue;
+            $sPrio += 1;
+            if( strtolower( $cWord ) == strtolower( $sWord )) {
+              $hitCnt += 1;
+              $rank   += ( $cPrio + $sPrio );
+            }
+            else
+              $rank += 10;
+          }
+        }
+        if( 0 < $hitCnt ) {
+          $hits[$rank][] = $transition['timezone_id'];
+        }
+      }
+    }
+    unset( $timezone_abbreviations );
+    if( empty( $hits ))
+      return FALSE;
+    ksort( $hits );
+    foreach( $hits as $rank => $tzs ) {
+      if( !empty( $tzs )) {
+        $timezone = reset( $tzs );
+        return TRUE;
+      }
+    }
+    return FALSE;
+  }
+/**
+ * transforms offset in seconds to [-/+]hhmm[ss]
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2011-05-02
+ * @param string $seconds
+ * @return string
+ */
+  public static function offsetSec2His( $seconds ) {
+    if( '-' == substr( $seconds, 0, 1 )) {
+      $prefix  = '-';
+      $seconds = substr( $seconds, 1 );
+    }
+    elseif( '+' == substr( $seconds, 0, 1 )) {
+      $prefix  = '+';
+      $seconds = substr( $seconds, 1 );
+    }
+    else
+      $prefix  = '+';
+    $output  = '';
+    $hour    = (int) floor( $seconds / 3600 );
+    if( 10 > $hour )
+      $hour  = '0'.$hour;
+    $seconds = $seconds % 3600;
+    $min     = (int) floor( $seconds / 60 );
+    if( 10 > $min )
+      $min   = '0'.$min;
+    $output  = $hour.$min;
+    $seconds = $seconds % 60;
+    if( 0 < $seconds) {
+      if( 9 < $seconds)
+        $output .= $seconds;
+      else
+        $output .= '0'.$seconds;
+    }
+    return $prefix.$output;
+  }
+/**
+ * updates an array with dates based on a recur pattern
+ *
+ * if missing, UNTIL is set 1 year from startdate (emergency break)
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.10.19 - 2011-10-31
+ * @param array $result, array to update, array([timestamp] => timestamp)
+ * @param array $recur, pattern for recurrency (only value part, params ignored)
+ * @param array $wdate, component start date
+ * @param array $startdate, start date
+ * @param array $enddate, optional
+ * @return void
+ * @todo BYHOUR, BYMINUTE, BYSECOND, WEEKLY at year end/start
+ */
+  public static function _recur2date( & $result, $recur, $wdate, $startdate, $enddate=FALSE ) {
+    foreach( $wdate as $k => $v ) if( ctype_digit( $v )) $wdate[$k] = (int) $v;
+    $wdateStart  = $wdate;
+    $wdatets     = iCalUtilityFunctions::_date2timestamp( $wdate );
+    $startdatets = iCalUtilityFunctions::_date2timestamp( $startdate );
+    if( !$enddate ) {
+      $enddate = $startdate;
+      $enddate['year'] += 1;
+    }
+// echo "recur __in_ comp start ".implode('-',$wdate)." period start ".implode('-',$startdate)." period end ".implode('-',$enddate)."<br />\n";print_r($recur);echo "<br />\n";//test###
+    $endDatets = iCalUtilityFunctions::_date2timestamp( $enddate ); // fix break
+    if( !isset( $recur['COUNT'] ) && !isset( $recur['UNTIL'] ))
+      $recur['UNTIL'] = $enddate; // create break
+    if( isset( $recur['UNTIL'] )) {
+      $tdatets = iCalUtilityFunctions::_date2timestamp( $recur['UNTIL'] );
+      if( $endDatets > $tdatets ) {
+        $endDatets = $tdatets; // emergency break
+        $enddate   = iCalUtilityFunctions::_timestamp2date( $endDatets, 6 );
+      }
+      else
+        $recur['UNTIL'] = iCalUtilityFunctions::_timestamp2date( $endDatets, 6 );
+    }
+    if( $wdatets > $endDatets ) {
+// echo "recur out of date ".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test
+      return array(); // nothing to do.. .
+    }
+    if( !isset( $recur['FREQ'] )) // "MUST be specified.. ."
+      $recur['FREQ'] = 'DAILY'; // ??
+    $wkst = ( isset( $recur['WKST'] ) && ( 'SU' == $recur['WKST'] )) ? 24*60*60 : 0; // ??
+    $weekStart = (int) date( 'W', ( $wdatets + $wkst ));
+    if( !isset( $recur['INTERVAL'] ))
+      $recur['INTERVAL'] = 1;
+    $countcnt = ( !isset( $recur['BYSETPOS'] )) ? 1 : 0; // DTSTART counts as the first occurrence
+            /* find out how to step up dates and set index for interval count */
+    $step = array();
+    if( 'YEARLY' == $recur['FREQ'] )
+      $step['year']  = 1;
+    elseif( 'MONTHLY' == $recur['FREQ'] )
+      $step['month'] = 1;
+    elseif( 'WEEKLY' == $recur['FREQ'] )
+      $step['day']   = 7;
+    else
+      $step['day']   = 1;
+    if( isset( $step['year'] ) && isset( $recur['BYMONTH'] ))
+      $step = array( 'month' => 1 );
+    if( empty( $step ) && isset( $recur['BYWEEKNO'] )) // ??
+      $step = array( 'day' => 7 );
+    if( isset( $recur['BYYEARDAY'] ) || isset( $recur['BYMONTHDAY'] ) || isset( $recur['BYDAY'] ))
+      $step = array( 'day' => 1 );
+    $intervalarr = array();
+    if( 1 < $recur['INTERVAL'] ) {
+      $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst );
+      $intervalarr = array( $intervalix => 0 );
+    }
+    if( isset( $recur['BYSETPOS'] )) { // save start date + weekno
+      $bysetposymd1 = $bysetposymd2 = $bysetposw1 = $bysetposw2 = array();
+// echo "bysetposXold_start=$bysetposYold $bysetposMold $bysetposDold<br />\n"; // test ###
+      if( is_array( $recur['BYSETPOS'] )) {
+        foreach( $recur['BYSETPOS'] as $bix => $bval )
+          $recur['BYSETPOS'][$bix] = (int) $bval;
+      }
+      else
+        $recur['BYSETPOS'] = array( (int) $recur['BYSETPOS'] );
+      if( 'YEARLY' == $recur['FREQ'] ) {
+        $wdate['month'] = $wdate['day'] = 1; // start from beginning of year
+        $wdatets        = iCalUtilityFunctions::_date2timestamp( $wdate );
+        iCalUtilityFunctions::_stepdate( $enddate, $endDatets, array( 'year' => 1 )); // make sure to count whole last year
+      }
+      elseif( 'MONTHLY' == $recur['FREQ'] ) {
+        $wdate['day']   = 1; // start from beginning of month
+        $wdatets        = iCalUtilityFunctions::_date2timestamp( $wdate );
+        iCalUtilityFunctions::_stepdate( $enddate, $endDatets, array( 'month' => 1 )); // make sure to count whole last month
+      }
+      else
+        iCalUtilityFunctions::_stepdate( $enddate, $endDatets, $step); // make sure to count whole last period
+// echo "BYSETPOS endDat++ =".implode('-',$enddate).' step='.var_export($step,TRUE)."<br />\n";//test###
+      $bysetposWold = (int) date( 'W', ( $wdatets + $wkst ));
+      $bysetposYold = $wdate['year'];
+      $bysetposMold = $wdate['month'];
+      $bysetposDold = $wdate['day'];
+    }
+    else
+      iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
+    $year_old     = null;
+    $daynames     = array( 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA' );
+             /* MAIN LOOP */
+// echo "recur start ".implode('-',$wdate)." end ".implode('-',$enddate)."<br />\n";//test
+    while( TRUE ) {
+      if( isset( $endDatets ) && ( $wdatets > $endDatets ))
+        break;
+      if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] ))
+        break;
+      if( $year_old != $wdate['year'] ) {
+        $year_old   = $wdate['year'];
+        $daycnts    = array();
+        $yeardays   = $weekno = 0;
+        $yeardaycnt = array();
+        foreach( $daynames as $dn )
+          $yeardaycnt[$dn] = 0;
+        for( $m = 1; $m <= 12; $m++ ) { // count up and update up-counters
+          $daycnts[$m] = array();
+          $weekdaycnt = array();
+          foreach( $daynames as $dn )
+            $weekdaycnt[$dn] = 0;
+          $mcnt     = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] ));
+          for( $d   = 1; $d <= $mcnt; $d++ ) {
+            $daycnts[$m][$d] = array();
+            if( isset( $recur['BYYEARDAY'] )) {
+              $yeardays++;
+              $daycnts[$m][$d]['yearcnt_up'] = $yeardays;
+            }
+            if( isset( $recur['BYDAY'] )) {
+              $day    = date( 'w', mktime( 0, 0, 0, $m, $d, $wdate['year'] ));
+              $day    = $daynames[$day];
+              $daycnts[$m][$d]['DAY'] = $day;
+              $weekdaycnt[$day]++;
+              $daycnts[$m][$d]['monthdayno_up'] = $weekdaycnt[$day];
+              $yeardaycnt[$day]++;
+              $daycnts[$m][$d]['yeardayno_up'] = $yeardaycnt[$day];
+            }
+            if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' ))
+              $daycnts[$m][$d]['weekno_up'] =(int)date('W',mktime(0,0,$wkst,$m,$d,$wdate['year']));
+          }
+        }
+        $daycnt = 0;
+        $yeardaycnt = array();
+        if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' )) {
+          $weekno = null;
+          for( $d=31; $d > 25; $d-- ) { // get last weekno for year
+            if( !$weekno )
+              $weekno = $daycnts[12][$d]['weekno_up'];
+            elseif( $weekno < $daycnts[12][$d]['weekno_up'] ) {
+              $weekno = $daycnts[12][$d]['weekno_up'];
+              break;
+            }
+          }
+        }
+        for( $m = 12; $m > 0; $m-- ) { // count down and update down-counters
+          $weekdaycnt = array();
+          foreach( $daynames as $dn )
+            $yeardaycnt[$dn] = $weekdaycnt[$dn] = 0;
+          $monthcnt = 0;
+          $mcnt     = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] ));
+          for( $d   = $mcnt; $d > 0; $d-- ) {
+            if( isset( $recur['BYYEARDAY'] )) {
+              $daycnt -= 1;
+              $daycnts[$m][$d]['yearcnt_down'] = $daycnt;
+            }
+            if( isset( $recur['BYMONTHDAY'] )) {
+              $monthcnt -= 1;
+              $daycnts[$m][$d]['monthcnt_down'] = $monthcnt;
+            }
+            if( isset( $recur['BYDAY'] )) {
+              $day  = $daycnts[$m][$d]['DAY'];
+              $weekdaycnt[$day] -= 1;
+              $daycnts[$m][$d]['monthdayno_down'] = $weekdaycnt[$day];
+              $yeardaycnt[$day] -= 1;
+              $daycnts[$m][$d]['yeardayno_down'] = $yeardaycnt[$day];
+            }
+            if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' ))
+              $daycnts[$m][$d]['weekno_down'] = ($daycnts[$m][$d]['weekno_up'] - $weekno - 1);
+          }
+        }
+      }
+            /* check interval */
+      if( 1 < $recur['INTERVAL'] ) {
+            /* create interval index */
+        $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst );
+            /* check interval */
+        $currentKey = array_keys( $intervalarr );
+        $currentKey = end( $currentKey ); // get last index
+        if( $currentKey != $intervalix )
+          $intervalarr = array( $intervalix => ( $intervalarr[$currentKey] + 1 ));
+        if(( $recur['INTERVAL'] != $intervalarr[$intervalix] ) &&
+           ( 0 != $intervalarr[$intervalix] )) {
+            /* step up date */
+// echo "skip: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."<br />\n";//test
+          iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
+          continue;
+        }
+        else // continue within the selected interval
+          $intervalarr[$intervalix] = 0;
+// echo "cont: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."<br />\n";//test
+      }
+      $updateOK = TRUE;
+      if( $updateOK && isset( $recur['BYMONTH'] ))
+        $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTH']
+                                           , $wdate['month']
+                                           ,($wdate['month'] - 13));
+      if( $updateOK && isset( $recur['BYWEEKNO'] ))
+        $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYWEEKNO']
+                                           , $daycnts[$wdate['month']][$wdate['day']]['weekno_up']
+                                           , $daycnts[$wdate['month']][$wdate['day']]['weekno_down'] );
+      if( $updateOK && isset( $recur['BYYEARDAY'] ))
+        $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYYEARDAY']
+                                           , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_up']
+                                           , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_down'] );
+      if( $updateOK && isset( $recur['BYMONTHDAY'] ))
+        $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTHDAY']
+                                           , $wdate['day']
+                                           , $daycnts[$wdate['month']][$wdate['day']]['monthcnt_down'] );
+// echo "efter BYMONTHDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "<br />\n";//test###
+      if( $updateOK && isset( $recur['BYDAY'] )) {
+        $updateOK = FALSE;
+        $m = $wdate['month'];
+        $d = $wdate['day'];
+        if( isset( $recur['BYDAY']['DAY'] )) { // single day, opt with year/month day order no
+          $daynoexists = $daynosw = $daynamesw =  FALSE;
+          if( $recur['BYDAY']['DAY'] == $daycnts[$m][$d]['DAY'] )
+            $daynamesw = TRUE;
+          if( isset( $recur['BYDAY'][0] )) {
+            $daynoexists = TRUE;
+            if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) || isset( $recur['BYMONTH'] ))
+              $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0]
+                                                , $daycnts[$m][$d]['monthdayno_up']
+                                                , $daycnts[$m][$d]['monthdayno_down'] );
+            elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' ))
+              $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0]
+                                                , $daycnts[$m][$d]['yeardayno_up']
+                                                , $daycnts[$m][$d]['yeardayno_down'] );
+          }
+          if((  $daynoexists &&  $daynosw && $daynamesw ) ||
+             ( !$daynoexists && !$daynosw && $daynamesw )) {
+            $updateOK = TRUE;
+// echo "m=$m d=$d day=".$daycnts[$m][$d]['DAY']." yeardayno_up=".$daycnts[$m][$d]['yeardayno_up']." daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw updateOK:$updateOK<br />\n"; // test ###
+          }
+// echo "m=$m d=$d day=".$daycnts[$m][$d]['DAY']." yeardayno_up=".$daycnts[$m][$d]['yeardayno_up']." daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw updateOK:$updateOK<br />\n"; // test ###
+        }
+        else {
+          foreach( $recur['BYDAY'] as $bydayvalue ) {
+            $daynoexists = $daynosw = $daynamesw = FALSE;
+            if( isset( $bydayvalue['DAY'] ) &&
+                     ( $bydayvalue['DAY'] == $daycnts[$m][$d]['DAY'] ))
+              $daynamesw = TRUE;
+            if( isset( $bydayvalue[0] )) {
+              $daynoexists = TRUE;
+              if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) ||
+                   isset( $recur['BYMONTH'] ))
+                $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0']
+                                                  , $daycnts[$m][$d]['monthdayno_up']
+                                                  , $daycnts[$m][$d]['monthdayno_down'] );
+              elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' ))
+                $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0']
+                                                  , $daycnts[$m][$d]['yeardayno_up']
+                                                  , $daycnts[$m][$d]['yeardayno_down'] );
+            }
+// echo "daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw<br />\n"; // test ###
+            if((  $daynoexists &&  $daynosw && $daynamesw ) ||
+               ( !$daynoexists && !$daynosw && $daynamesw )) {
+              $updateOK = TRUE;
+              break;
+            }
+          }
+        }
+      }
+// echo "efter BYDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "<br />\n"; // test ###
+            /* check BYSETPOS */
+      if( $updateOK ) {
+        if( isset( $recur['BYSETPOS'] ) &&
+          ( in_array( $recur['FREQ'], array( 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY' )))) {
+          if( isset( $recur['WEEKLY'] )) {
+            if( $bysetposWold == $daycnts[$wdate['month']][$wdate['day']]['weekno_up'] )
+              $bysetposw1[] = $wdatets;
+            else
+              $bysetposw2[] = $wdatets;
+          }
+          else {
+            if(( isset( $recur['FREQ'] ) && ( 'YEARLY'      == $recur['FREQ'] )  &&
+                                            ( $bysetposYold == $wdate['year'] ))   ||
+               ( isset( $recur['FREQ'] ) && ( 'MONTHLY'     == $recur['FREQ'] )  &&
+                                           (( $bysetposYold == $wdate['year'] )  &&
+                                            ( $bysetposMold == $wdate['month'] ))) ||
+               ( isset( $recur['FREQ'] ) && ( 'DAILY'       == $recur['FREQ'] )  &&
+                                           (( $bysetposYold == $wdate['year'] )  &&
+                                            ( $bysetposMold == $wdate['month'])  &&
+                                            ( $bysetposDold == $wdate['day'] )))) {
+// echo "bysetposymd1[]=".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test
+              $bysetposymd1[] = $wdatets;
+            }
+            else {
+// echo "bysetposymd2[]=".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test
+              $bysetposymd2[] = $wdatets;
+            }
+          }
+        }
+        else {
+            /* update result array if BYSETPOS is set */
+          $countcnt++;
+          if( $startdatets <= $wdatets ) { // only output within period
+            $result[$wdatets] = TRUE;
+// echo "recur ".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test
+          }
+// echo "recur undate ".date('Y-m-d H:i:s',$wdatets)." okdatstart ".date('Y-m-d H:i:s',$startdatets)."<br />\n";//test
+          $updateOK = FALSE;
+        }
+      }
+            /* step up date */
+      iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
+            /* check if BYSETPOS is set for updating result array */
+      if( $updateOK && isset( $recur['BYSETPOS'] )) {
+        $bysetpos       = FALSE;
+        if( isset( $recur['FREQ'] ) && ( 'YEARLY'  == $recur['FREQ'] ) &&
+          ( $bysetposYold != $wdate['year'] )) {
+          $bysetpos     = TRUE;
+          $bysetposYold = $wdate['year'];
+        }
+        elseif( isset( $recur['FREQ'] ) && ( 'MONTHLY' == $recur['FREQ'] &&
+         (( $bysetposYold != $wdate['year'] ) || ( $bysetposMold != $wdate['month'] )))) {
+          $bysetpos     = TRUE;
+          $bysetposYold = $wdate['year'];
+          $bysetposMold = $wdate['month'];
+        }
+        elseif( isset( $recur['FREQ'] ) && ( 'WEEKLY'  == $recur['FREQ'] )) {
+          $weekno = (int) date( 'W', mktime( 0, 0, $wkst, $wdate['month'], $wdate['day'], $wdate['year']));
+          if( $bysetposWold != $weekno ) {
+            $bysetposWold = $weekno;
+            $bysetpos     = TRUE;
+          }
+        }
+        elseif( isset( $recur['FREQ'] ) && ( 'DAILY'   == $recur['FREQ'] ) &&
+         (( $bysetposYold != $wdate['year'] )  ||
+          ( $bysetposMold != $wdate['month'] ) ||
+          ( $bysetposDold != $wdate['day'] ))) {
+          $bysetpos     = TRUE;
+          $bysetposYold = $wdate['year'];
+          $bysetposMold = $wdate['month'];
+          $bysetposDold = $wdate['day'];
+        }
+        if( $bysetpos ) {
+          if( isset( $recur['BYWEEKNO'] )) {
+            $bysetposarr1 = & $bysetposw1;
+            $bysetposarr2 = & $bysetposw2;
+          }
+          else {
+            $bysetposarr1 = & $bysetposymd1;
+            $bysetposarr2 = & $bysetposymd2;
+          }
+// echo 'test före out startYMD (weekno)='.$wdateStart['year'].':'.$wdateStart['month'].':'.$wdateStart['day']." ($weekStart) "; // test ###
+          foreach( $recur['BYSETPOS'] as $ix ) {
+            if( 0 > $ix ) // both positive and negative BYSETPOS allowed
+              $ix = ( count( $bysetposarr1 ) + $ix + 1);
+            $ix--;
+            if( isset( $bysetposarr1[$ix] )) {
+              if( $startdatets <= $bysetposarr1[$ix] ) { // only output within period
+//                $testdate   = iCalUtilityFunctions::_timestamp2date( $bysetposarr1[$ix], 6 );                // test ###
+//                $testweekno = (int) date( 'W', mktime( 0, 0, $wkst, $testdate['month'], $testdate['day'], $testdate['year'] )); // test ###
+// echo " testYMD (weekno)=".$testdate['year'].':'.$testdate['month'].':'.$testdate['day']." ($testweekno)";   // test ###
+                $result[$bysetposarr1[$ix]] = TRUE;
+// echo " recur ".date('Y-m-d H:i:s',$bysetposarr1[$ix]); // test ###
+              }
+              $countcnt++;
+            }
+            if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] ))
+              break;
+          }
+// echo "<br />\n"; // test ###
+          $bysetposarr1 = $bysetposarr2;
+          $bysetposarr2 = array();
+        }
+      }
+    }
+  }
+  public static function _recurBYcntcheck( $BYvalue, $upValue, $downValue ) {
+    if( is_array( $BYvalue ) &&
+      ( in_array( $upValue, $BYvalue ) || in_array( $downValue, $BYvalue )))
+      return TRUE;
+    elseif(( $BYvalue == $upValue ) || ( $BYvalue == $downValue ))
+      return TRUE;
+    else
+      return FALSE;
+  }
+  public static function _recurIntervalIx( $freq, $date, $wkst ) {
+            /* create interval index */
+    switch( $freq ) {
+      case 'YEARLY':
+        $intervalix = $date['year'];
+        break;
+      case 'MONTHLY':
+        $intervalix = $date['year'].'-'.$date['month'];
+        break;
+      case 'WEEKLY':
+        $wdatets    = iCalUtilityFunctions::_date2timestamp( $date );
+        $intervalix = (int) date( 'W', ( $wdatets + $wkst ));
+       break;
+      case 'DAILY':
+           default:
+        $intervalix = $date['year'].'-'.$date['month'].'-'.$date['day'];
+        break;
+    }
+    return $intervalix;
+  }
+/**
+ * convert input format for exrule and rrule to internal format
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.1 - 2012-09-24
+ * @param array $rexrule
+ * @return array
+ */
+  public static function _setRexrule( $rexrule ) {
+    $input          = array();
+    if( empty( $rexrule ))
+      return $input;
+    foreach( $rexrule as $rexrulelabel => $rexrulevalue ) {
+      $rexrulelabel = strtoupper( $rexrulelabel );
+      if( 'UNTIL'  != $rexrulelabel )
+        $input[$rexrulelabel]   = $rexrulevalue;
+      else {
+        iCalUtilityFunctions::_strDate2arr( $rexrulevalue );
+        if( iCalUtilityFunctions::_isArrayTimestampDate( $rexrulevalue )) // timestamp, always date-time UTC
+          $input[$rexrulelabel] = iCalUtilityFunctions::_timestamp2date( $rexrulevalue, 7, 'UTC' );
+        elseif( iCalUtilityFunctions::_isArrayDate( $rexrulevalue )) { // date or UTC date-time
+          $parno = ( isset( $rexrulevalue['hour'] ) || isset( $rexrulevalue[4] )) ? 7 : 3;
+          $d = iCalUtilityFunctions::_chkDateArr( $rexrulevalue, $parno );
+          if(( 3 < $parno ) && isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) {
+            $strdate              = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
+            $input[$rexrulelabel] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
+            unset( $input[$rexrulelabel]['unparsedtext'] );
+          }
+          else
+           $input[$rexrulelabel] = $d;
+        }
+        elseif( 8 <= strlen( trim( $rexrulevalue ))) { // ex. textual date-time 2006-08-03 10:12:18 => UTC
+          $input[$rexrulelabel] = iCalUtilityFunctions::_strdate2date( $rexrulevalue );
+          unset( $input['$rexrulelabel']['unparsedtext'] );
+        }
+        if(( 3 < count( $input[$rexrulelabel] )) && !isset( $input[$rexrulelabel]['tz'] ))
+          $input[$rexrulelabel]['tz'] = 'Z';
+      }
+    }
+            /* set recurrence rule specification in rfc2445 order */
+    $input2 = array();
+    if( isset( $input['FREQ'] ))
+      $input2['FREQ']       = $input['FREQ'];
+    if( isset( $input['UNTIL'] ))
+      $input2['UNTIL']      = $input['UNTIL'];
+    elseif( isset( $input['COUNT'] ))
+      $input2['COUNT']      = $input['COUNT'];
+    if( isset( $input['INTERVAL'] ))
+      $input2['INTERVAL']   = $input['INTERVAL'];
+    if( isset( $input['BYSECOND'] ))
+      $input2['BYSECOND']   = $input['BYSECOND'];
+    if( isset( $input['BYMINUTE'] ))
+      $input2['BYMINUTE']   = $input['BYMINUTE'];
+    if( isset( $input['BYHOUR'] ))
+      $input2['BYHOUR']     = $input['BYHOUR'];
+    if( isset( $input['BYDAY'] )) {
+      if( !is_array( $input['BYDAY'] )) // ensure upper case.. .
+        $input2['BYDAY']    = strtoupper( $input['BYDAY'] );
+      else {
+        foreach( $input['BYDAY'] as $BYDAYx => $BYDAYv ) {
+          if( 'DAY'        == strtoupper( $BYDAYx ))
+             $input2['BYDAY']['DAY'] = strtoupper( $BYDAYv );
+          elseif( !is_array( $BYDAYv )) {
+             $input2['BYDAY'][$BYDAYx]  = $BYDAYv;
+          }
+          else {
+            foreach( $BYDAYv as $BYDAYx2 => $BYDAYv2 ) {
+              if( 'DAY'    == strtoupper( $BYDAYx2 ))
+                 $input2['BYDAY'][$BYDAYx]['DAY'] = strtoupper( $BYDAYv2 );
+              else
+                 $input2['BYDAY'][$BYDAYx][$BYDAYx2] = $BYDAYv2;
+            }
+          }
+        }
+      }
+    }
+    if( isset( $input['BYMONTHDAY'] ))
+      $input2['BYMONTHDAY'] = $input['BYMONTHDAY'];
+    if( isset( $input['BYYEARDAY'] ))
+      $input2['BYYEARDAY']  = $input['BYYEARDAY'];
+    if( isset( $input['BYWEEKNO'] ))
+      $input2['BYWEEKNO']   = $input['BYWEEKNO'];
+    if( isset( $input['BYMONTH'] ))
+      $input2['BYMONTH']    = $input['BYMONTH'];
+    if( isset( $input['BYSETPOS'] ))
+      $input2['BYSETPOS']   = $input['BYSETPOS'];
+    if( isset( $input['WKST'] ))
+      $input2['WKST']       = $input['WKST'];
+    return $input2;
+  }
+/**
+ * convert format for input date to internal date with parameters
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.1 - 2012-10-15
+ * @param mixed  $year
+ * @param mixed  $month   optional
+ * @param int    $day     optional
+ * @param int    $hour    optional
+ * @param int    $min     optional
+ * @param int    $sec     optional
+ * @param string $tz      optional
+ * @param array  $params  optional
+ * @param string $caller  optional
+ * @param string $objName optional
+ * @param string $tzid    optional
+ * @return array
+ */
+  public static function _setDate( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE, $caller=null, $objName=null, $tzid=FALSE ) {
+    $input = $parno = null;
+    $localtime = (( 'dtstart' == $caller ) && in_array( $objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE;
+    iCalUtilityFunctions::_strDate2arr( $year );
+    if( iCalUtilityFunctions::_isArrayDate( $year )) {
+      $input['value']  = iCalUtilityFunctions::_chkDateArr( $year, $parno );
+      if( 100 > $input['value']['year'] )
+        $input['value']['year'] += 2000;
+      if( $localtime )
+        unset( $month['VALUE'], $month['TZID'] );
+      elseif( !isset( $month['TZID'] ) && isset( $tzid ))
+        $month['TZID'] = $tzid;
+      if( isset( $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] ))
+        unset( $month['TZID'] );
+      elseif( isset( $month['TZID'] ) && iCalUtilityFunctions::_isOffset( $month['TZID'] )) {
+        $input['value']['tz'] = $month['TZID'];
+        unset( $month['TZID'] );
+      }
+      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
+      $hitval          = ( isset( $input['value']['tz'] )) ? 7 : 6;
+      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval );
+      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, count( $input['value'] ), $parno );
+      if(( 3 != $parno ) && isset( $input['value']['tz'] ) && ( 'Z' != $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
+        $d             = $input['value'];
+        $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
+        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, $parno );
+        unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
+      }
+      if( isset( $input['value']['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
+        $input['params']['TZID'] = $input['value']['tz'];
+        unset( $input['value']['tz'] );
+      }
+    } // end if( iCalUtilityFunctions::_isArrayDate( $year ))
+    elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) {
+      if( $localtime ) unset ( $month['VALUE'], $month['TZID'] );
+      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
+      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 );
+      $hitval          = 7;
+      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno );
+      if( !isset( $input['params']['TZID'] ) && !empty( $tzid ))
+        $input['params']['TZID'] = $tzid;
+      if( isset( $year['tz'] )) {
+        $parno         = 6;
+        if( !iCalUtilityFunctions::_isOffset( $year['tz'] ))
+          $input['params']['TZID'] = $year['tz'];
+      }
+      elseif( isset( $input['params']['TZID'] )) {
+        $year['tz']    = $input['params']['TZID'];
+        $parno         = 6;
+        if( iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
+          unset( $input['params']['TZID'] );
+          $parno       = 7;
+        }
+      }
+      $input['value']  = iCalUtilityFunctions::_timestamp2date( $year, $parno );
+    } // end elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year ))
+    elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18 [[[+/-]1234[56]] / timezone]
+      if( $localtime )
+        unset( $month['VALUE'], $month['TZID'] );
+      elseif( !isset( $month['TZID'] ) && !empty( $tzid ))
+        $month['TZID'] = $tzid;
+      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
+      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7, $parno );
+      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, $parno, $parno );
+      $input['value']  = iCalUtilityFunctions::_strdate2date( $year, $parno );
+      unset( $input['value']['unparsedtext'] );
+      if( isset( $input['value']['tz'] )) {
+        if( iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
+          $d           = $input['value'];
+          $strdate     = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
+          $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
+          unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
+        }
+        else {
+          $input['params']['TZID'] = $input['value']['tz'];
+          unset( $input['value']['tz'] );
+        }
+      }
+      elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
+        $d             = $input['value'];
+        $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $input['params']['TZID'] );
+        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
+        unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
+      }
+    } // end elseif( 8 <= strlen( trim( $year )))
+    else {
+      if( is_array( $params ))
+        $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ));
+      elseif( is_array( $tz )) {
+        $input['params'] = iCalUtilityFunctions::_setParams( $tz,     array( 'VALUE' => 'DATE-TIME' ));
+        $tz = FALSE;
+      }
+      elseif( is_array( $hour )) {
+        $input['params'] = iCalUtilityFunctions::_setParams( $hour,   array( 'VALUE' => 'DATE-TIME' ));
+        $hour = $min = $sec = $tz = FALSE;
+      }
+      if( $localtime )
+        unset ( $input['params']['VALUE'], $input['params']['TZID'] );
+      elseif( !isset( $tz ) && !isset( $input['params']['TZID'] ) && !empty( $tzid ))
+        $input['params']['TZID'] = $tzid;
+      elseif( isset( $tz ) && iCalUtilityFunctions::_isOffset( $tz ))
+        unset( $input['params']['TZID'] );
+      elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
+        $tz            = $input['params']['TZID'];
+        unset( $input['params']['TZID'] );
+      }
+      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 );
+      $hitval          = ( iCalUtilityFunctions::_isOffset( $tz )) ? 7 : 6;
+      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno, $parno );
+      $input['value']  = array( 'year'  => $year, 'month' => $month, 'day'   => $day );
+      if( 3 != $parno ) {
+        $input['value']['hour'] = ( $hour ) ? $hour : '0';
+        $input['value']['min']  = ( $min )  ? $min  : '0';
+        $input['value']['sec']  = ( $sec )  ? $sec  : '0';
+        if( !empty( $tz ))
+          $input['value']['tz'] = $tz;
+        $strdate       = iCalUtilityFunctions::_date2strdate( $input['value'], $parno );
+        if( !empty( $tz ) && !iCalUtilityFunctions::_isOffset( $tz ))
+          $strdate    .= ( 'Z' == $tz ) ? $tz : ' '.$tz;
+        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, $parno );
+        unset( $input['value']['unparsedtext'] );
+        if( isset( $input['value']['tz'] )) {
+          if( iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
+            $d           = $input['value'];
+            $strdate     = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
+            $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
+            unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
+          }
+          else {
+            $input['params']['TZID'] = $input['value']['tz'];
+            unset( $input['value']['tz'] );
+          }
+        }
+        elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
+          $d             = $input['value'];
+          $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $input['params']['TZID'] );
+          $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
+          unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
+        }
+      }
+    } // end else (i.e. using all arguments)
+    if(( 3 == $parno ) || ( isset( $input['params']['VALUE'] ) && ( 'DATE' == $input['params']['VALUE'] ))) {
+      $input['params']['VALUE'] = 'DATE';
+      unset( $input['value']['hour'], $input['value']['min'], $input['value']['sec'], $input['value']['tz'], $input['params']['TZID'] );
+    }
+    elseif( isset( $input['params']['TZID'] )) {
+      if(( 'UTC' == strtoupper( $input['params']['TZID'] )) || ( 'GMT' == strtoupper( $input['params']['TZID'] ))) {
+        $input['value']['tz'] = 'Z';
+        unset( $input['params']['TZID'] );
+      }
+      else
+        unset( $input['value']['tz'] );
+    }
+    elseif( isset( $input['value']['tz'] )) {
+      if(( 'UTC' == strtoupper( $input['value']['tz'] )) || ( 'GMT' == strtoupper( $input['value']['tz'] )))
+        $input['value']['tz'] = 'Z';
+      if( 'Z' != $input['value']['tz'] ) {
+        $input['params']['TZID'] = $input['value']['tz'];
+        unset( $input['value']['tz'] );
+      }
+      else
+        unset( $input['params']['TZID'] );
+    }
+    if( $localtime )
+      unset( $input['value']['tz'], $input['params']['TZID'] );
+    return $input;
+  }
+/**
+ * convert format for input date (UTC) to internal date with parameters
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.4 - 2012-10-06
+ * @param mixed $year
+ * @param mixed $month  optional
+ * @param int   $day    optional
+ * @param int   $hour   optional
+ * @param int   $min    optional
+ * @param int   $sec    optional
+ * @param array $params optional
+ * @return array
+ */
+  public static function _setDate2( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
+    $input = null;
+    iCalUtilityFunctions::_strDate2arr( $year );
+    if( iCalUtilityFunctions::_isArrayDate( $year )) {
+      $input['value']  = iCalUtilityFunctions::_chkDateArr( $year, 7 );
+      if( isset( $input['value']['year'] ) && ( 100 > $input['value']['year'] ))
+        $input['value']['year'] += 2000;
+      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
+      if( isset( $input['value']['tz'] ) && ( 'Z' != $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
+        $d             = $input['value'];
+        $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
+        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
+        unset( $input['value']['unparsedtext'] );
+      }
+    }
+    elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) {
+      $year['tz']      = 'UTC';
+      $input['value']  = iCalUtilityFunctions::_timestamp2date( $year, 7 );
+      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
+    }
+    elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18
+      $input['value']  = iCalUtilityFunctions::_strdate2date( $year, 7 );
+      unset( $input['value']['unparsedtext'] );
+      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
+    }
+    else {
+      $input['value']  = array( 'year'  => $year
+                              , 'month' => $month
+                              , 'day'   => $day
+                              , 'hour'  => $hour
+                              , 'min'   => $min
+                              , 'sec'   => $sec );
+      if(  isset( $tz )) $input['value']['tz'] = $tz;
+      if(( isset( $tz ) && iCalUtilityFunctions::_isOffset( $tz )) ||
+         ( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))) {
+          if( !isset( $tz ) && isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))
+            $input['value']['tz'] = $input['params']['TZID'];
+          unset( $input['params']['TZID'] );
+        $strdate        = iCalUtilityFunctions::_date2strdate( $input['value'], 7 );
+        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
+        unset( $input['value']['unparsedtext'] );
+      }
+      $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ));
+    }
+    $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7 ); // remove default
+    if( !isset( $input['value']['hour'] )) $input['value']['hour'] = 0;
+    if( !isset( $input['value']['min'] ))  $input['value']['min']  = 0;
+    if( !isset( $input['value']['sec'] ))  $input['value']['sec']  = 0;
+    $input['value']['tz'] = 'Z';
+    return $input;
+  }
+/**
+ * check index and set (an indexed) content in multiple value array
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.6.12 - 2011-01-03
+ * @param array $valArr
+ * @param mixed $value
+ * @param array $params
+ * @param array $defaults
+ * @param int $index
+ * @return void
+ */
+  public static function _setMval( & $valArr, $value, $params=FALSE, $defaults=FALSE, $index=FALSE ) {
+    if( !is_array( $valArr )) $valArr = array();
+    if( $index )
+      $index = $index - 1;
+    elseif( 0 < count( $valArr )) {
+      $keys  = array_keys( $valArr );
+      $index = end( $keys ) + 1;
+    }
+    else
+      $index = 0;
+    $valArr[$index] = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params, $defaults ));
+    ksort( $valArr );
+  }
+/**
+ * set input (formatted) parameters- component property attributes
+ *
+ * default parameters can be set, if missing
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 1.x.x - 2007-05-01
+ * @param array $params
+ * @param array $defaults
+ * @return array
+ */
+  public static function _setParams( $params, $defaults=FALSE ) {
+    if( !is_array( $params))
+      $params = array();
+    $input = array();
+    foreach( $params as $paramKey => $paramValue ) {
+      if( is_array( $paramValue )) {
+        foreach( $paramValue as $pkey => $pValue ) {
+          if(( '"' == substr( $pValue, 0, 1 )) && ( '"' == substr( $pValue, -1 )))
+            $paramValue[$pkey] = substr( $pValue, 1, ( strlen( $pValue ) - 2 ));
+        }
+      }
+      elseif(( '"' == substr( $paramValue, 0, 1 )) && ( '"' == substr( $paramValue, -1 )))
+        $paramValue = substr( $paramValue, 1, ( strlen( $paramValue ) - 2 ));
+      if( 'VALUE' == strtoupper( $paramKey ))
+        $input['VALUE']                 = strtoupper( $paramValue );
+      else
+        $input[strtoupper( $paramKey )] = $paramValue;
+    }
+    if( is_array( $defaults )) {
+      foreach( $defaults as $paramKey => $paramValue ) {
+        if( !isset( $input[$paramKey] ))
+          $input[$paramKey] = $paramValue;
+      }
+    }
+    return (0 < count( $input )) ? $input : null;
+  }
+/**
+ * break lines at pos 75
+ *
+ * Lines of text SHOULD NOT be longer than 75 octets, excluding the line
+ * break. Long content lines SHOULD be split into a multiple line
+ * representations using a line "folding" technique. That is, a long
+ * line can be split between any two characters by inserting a CRLF
+ * immediately followed by a single linear white space character (i.e.,
+ * SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). Any sequence
+ * of CRLF followed immediately by a single linear white space character
+ * is ignored (i.e., removed) when processing the content type.
+ *
+ * Edited 2007-08-26 by Anders Litzell, anders@litzell.se to fix bug where
+ * the reserved expression "\n" in the arg $string could be broken up by the
+ * folding of lines, causing ambiguity in the return string.
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.2 - 2012-12-18
+ * @param string $value
+ * @return string
+ */
+  public static function _size75( $string, $nl ) {
+    $tmp             = $string;
+    $string          = '';
+    $cCnt = $x       = 0;
+    while( TRUE ) {
+      if( !isset( $tmp[$x] )) {
+        $string     .= $nl;                           // loop breakes here
+        break;
+      }
+      elseif(( 74   <= $cCnt ) && ( '\\'  == $tmp[$x] ) && ( 'n' == $tmp[$x+1] )) {
+        $string     .= $nl.' \n';                     // don't break lines inside '\n'
+        $x          += 2;
+        if( !isset( $tmp[$x] )) {
+          $string   .= $nl;
+          break;
+        }
+        $cCnt        = 3;
+      }
+      elseif( 75    <= $cCnt ) {
+        $string     .= $nl.' ';
+        $cCnt        = 1;
+      }
+      $byte          = ord( $tmp[$x] );
+      $string       .= $tmp[$x];
+      switch( TRUE ) { // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+        case(( $byte >= 0x20 ) && ( $byte <= 0x7F )): // characters U-00000000 - U-0000007F (same as ASCII)
+          $cCnt     += 1;
+          break;                                      // add a one byte character
+        case(( $byte & 0xE0) == 0xC0 ):               // characters U-00000080 - U-000007FF, mask 110XXXXX
+          if( isset( $tmp[$x+1] )) {
+            $cCnt   += 1;
+            $string  .= $tmp[$x+1];
+            $x       += 1;                            // add a two bytes character
+          }
+          break;
+        case(( $byte & 0xF0 ) == 0xE0 ):              // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+          if( isset( $tmp[$x+2] )) {
+            $cCnt   += 1;
+            $string .= $tmp[$x+1].$tmp[$x+2];
+            $x      += 2;                             // add a three bytes character
+          }
+          break;
+        case(( $byte & 0xF8 ) == 0xF0 ):              // characters U-00010000 - U-001FFFFF, mask 11110XXX
+          if( isset( $tmp[$x+3] )) {
+            $cCnt   += 1;
+            $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3];
+            $x      += 3;                             // add a four bytes character
+          }
+          break;
+        case(( $byte & 0xFC ) == 0xF8 ):              // characters U-00200000 - U-03FFFFFF, mask 111110XX
+          if( isset( $tmp[$x+4] )) {
+            $cCnt   += 1;
+            $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3].$tmp[$x+4];
+            $x      += 4;                             // add a five bytes character
+          }
+          break;
+        case(( $byte & 0xFE ) == 0xFC ):              // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+          if( isset( $tmp[$x+5] )) {
+            $cCnt   += 1;
+            $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3].$tmp[$x+4].$tmp[$x+5];
+            $x      += 5;                             // add a six bytes character
+          }
+        default:                                      // add any other byte without counting up $cCnt
+          break;
+      } // end switch( TRUE )
+      $x         += 1;                                // next 'byte' to test
+    } // end while( TRUE ) {
+    return $string;
+  }
+/**
+ * sort callback functions for exdate
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.11 - 2013-01-12
+ * @param array $a
+ * @param array $b
+ * @return int
+ */
+  public static function _sortExdate1( $a, $b ) {
+    $as  = sprintf( '%04d%02d%02d', $a['year'], $a['month'], $a['day'] );
+    $as .= ( isset( $a['hour'] )) ? sprintf( '%02d%02d%02d', $a['hour'], $a['min'], $a['sec'] ) : '';
+    $bs  = sprintf( '%04d%02d%02d', $b['year'], $b['month'], $b['day'] );
+    $bs .= ( isset( $b['hour'] )) ? sprintf( '%02d%02d%02d', $b['hour'], $b['min'], $b['sec'] ) : '';
+    return strcmp( $as, $bs );
+  }
+  public static function _sortExdate2( $a, $b ) {
+    $val = reset( $a['value'] );
+    $as  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
+    $as .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
+    $val = reset( $b['value'] );
+    $bs  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
+    $bs .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
+    return strcmp( $as, $bs );
+  }
+/**
+ * sort callback functions for rdate
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.9 - 2013-01-12
+ * @param array $a
+ * @param array $b
+ * @return int
+ */
+  public static function _sortRdate1( $a, $b ) {
+    $val = isset( $a['year'] ) ? $a : $a[0];
+    $as  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
+    $as .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
+    $val = isset( $b['year'] ) ? $b : $b[0];
+    $bs  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
+    $bs .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
+    return strcmp( $as, $bs );
+  }
+  public static function _sortRdate2( $a, $b ) {
+    $val = isset( $a['value'][0]['year'] ) ? $a['value'][0] : $a['value'][0][0];
+    $as  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
+    $as .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
+    $val = isset( $b['value'][0]['year'] ) ? $b['value'][0] : $b['value'][0][0];
+    $bs  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
+    $bs .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
+    return strcmp( $as, $bs );
+  }
+/**
+ * step date, return updated date, array and timpstamp
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.1 - 2012-09-24
+ * @param array $date, date to step
+ * @param int   $timestamp
+ * @param array $step, default array( 'day' => 1 )
+ * @return void
+ */
+  public static function _stepdate( &$date, &$timestamp, $step=array( 'day' => 1 )) {
+    if( !isset( $date['hour'] )) $date['hour'] = 0;
+    if( !isset( $date['min'] ))  $date['min']  = 0;
+    if( !isset( $date['sec'] ))  $date['sec']  = 0;
+    foreach( $step as $stepix => $stepvalue )
+      $date[$stepix] += $stepvalue;
+    $timestamp  = mktime( $date['hour'], $date['min'], $date['sec'], $date['month'], $date['day'], $date['year'] );
+    $d          = date( 'Y-m-d-H-i-s', $timestamp);
+    $d          = explode( '-', $d );
+    $date       = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
+    foreach( $date as $k => $v )
+      $date[$k] = (int) $v;
+  }
+/**
+ * convert a date from specific string to array format
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.11.8 - 2012-01-27
+ * @param mixed $input
+ * @return bool, TRUE on success
+ */
+  public static function _strDate2arr( & $input ) {
+    if( is_array( $input ))
+      return FALSE;
+    if( 5 > strlen( (string) $input ))
+      return FALSE;
+    $work = $input;
+    if( 2 == substr_count( $work, '-' ))
+      $work = str_replace( '-', '', $work );
+    if( 2 == substr_count( $work, '/' ))
+      $work = str_replace( '/', '', $work );
+    if( !ctype_digit( substr( $work, 0, 8 )))
+      return FALSE;
+    $temp = array( 'year'  => (int) substr( $work,  0, 4 )
+                 , 'month' => (int) substr( $work,  4, 2 )
+                 , 'day'   => (int) substr( $work,  6, 2 ));
+    if( !checkdate( $temp['month'], $temp['day'], $temp['year'] ))
+      return FALSE;
+    if( 8 == strlen( $work )) {
+      $input = $temp;
+      return TRUE;
+    }
+    if(( ' ' == substr( $work, 8, 1 )) || ( 'T' == substr( $work, 8, 1 )) || ( 't' == substr( $work, 8, 1 )))
+      $work =  substr( $work, 9 );
+    elseif( ctype_digit( substr( $work, 8, 1 )))
+      $work = substr( $work, 8 );
+    else
+     return FALSE;
+    if( 2 == substr_count( $work, ':' ))
+      $work = str_replace( ':', '', $work );
+    if( !ctype_digit( substr( $work, 0, 4 )))
+      return FALSE;
+    $temp['hour']  = substr( $work, 0, 2 );
+    $temp['min']   = substr( $work, 2, 2 );
+    if((( 0 > $temp['hour'] ) || ( $temp['hour'] > 23 )) ||
+       (( 0 > $temp['min'] )  || ( $temp['min']  > 59 )))
+      return FALSE;
+    if( ctype_digit( substr( $work, 4, 2 ))) {
+      $temp['sec'] = substr( $work, 4, 2 );
+      if((  0 > $temp['sec'] ) || ( $temp['sec']  > 59 ))
+        return FALSE;
+      $len = 6;
+    }
+    else {
+      $temp['sec'] = 0;
+      $len = 4;
+    }
+    if( $len < strlen( $work))
+      $temp['tz'] = trim( substr( $work, 6 ));
+    $input = $temp;
+    return TRUE;
+  }
+/**
+ * ensures internal date-time/date format for input date-time/date in string fromat
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.14.1 - 2012-10-07
+ * Modified to also return original string value by Yitzchok Lavi <icalcreator@onebigsystem.com>
+ * @param array $datetime
+ * @param int   $parno optional, default FALSE
+ * @param moxed $wtz optional, default null
+ * @return array
+ */
+  public static function _date_time_string( $datetime, $parno=FALSE ) {
+    return iCalUtilityFunctions::_strdate2date( $datetime, $parno, null );
+  }
+  public static function _strdate2date( $datetime, $parno=FALSE, $wtz=null ) {
+    // save original input string to return it later
+    $unparseddatetime = $datetime;
+    $datetime   = (string) trim( $datetime );
+    $tz         = null;
+    $offset     = 0;
+    $tzSts      = FALSE;
+    $len        = strlen( $datetime );
+    if( 'Z' == substr( $datetime, -1 )) {
+      $tz       = 'Z';
+      $datetime = trim( substr( $datetime, 0, ( $len - 1 )));
+      $tzSts    = TRUE;
+      $len      = 88;
+    }
+    if( iCalUtilityFunctions::_isOffset( substr( $datetime, -5, 5 ))) { // [+/-]NNNN offset
+      $tz       = substr( $datetime, -5, 5 );
+      $datetime = trim( substr( $datetime, 0, ($len - 5)));
+      $len      = strlen( $datetime );
+    }
+    elseif( iCalUtilityFunctions::_isOffset( substr( $datetime, -7, 7 ))) { // [+/-]NNNNNN offset
+      $tz       = substr( $datetime, -7, 7 );
+      $datetime = trim( substr( $datetime, 0, ($len - 7)));
+      $len      = strlen( $datetime );
+    }
+    elseif( empty( $wtz ) && ctype_digit( substr( $datetime, 0, 4 )) && ctype_digit( substr( $datetime, -2, 2 )) && iCalUtilityFunctions::_strDate2arr( $datetime )) {
+      $output = $datetime;
+      if( !empty( $tz ))
+        $output['tz'] = 'Z';
+      $output['unparsedtext'] = $unparseddatetime;
+      return $output;
+    }
+    else {
+      $cx  = $tx = 0;    //  find any trailing timezone or offset
+      for( $cx = -1; $cx > ( 9 - $len ); $cx-- ) {
+        $char = substr( $datetime, $cx, 1 );
+        if(( ' ' == $char) || ctype_digit( $char ))
+          break; // if exists, tz ends here.. . ?
+        else
+           $tx--; // tz length counter
+      }
+      if( 0 > $tx ) { // if any
+        $tz     = substr( $datetime, $tx );
+        $datetime = trim( substr( $datetime, 0, $len + $tx ));
+        $len    = strlen( $datetime );
+      }
+      if(( 17 <= $len ) ||  // long textual datetime
+         ( ctype_digit( substr( $datetime, 0, 8 )) && ( 'T' ==  substr( $datetime, 8, 1 )) && ctype_digit( substr( $datetime, -6, 6 ))) ||
+         ( ctype_digit( substr( $datetime, 0, 14 )))) {
+        $len    = 88;
+        $tzSts  = TRUE;
+      }
+      else
+        $tz     = null; // no tz for Y-m-d dates
+    }
+    if( empty( $tz ) && !empty( $wtz ))
+      $tz       = $wtz;
+    if( 17 >= $len ) // any Y-m-d textual date
+      $tz       = null;
+    if( !empty( $tz ) && ( 17 < $len )) { // tz set AND long textual datetime
+      if(( 'Z' != $tz ) && ( iCalUtilityFunctions::_isOffset( $tz ))) {
+        $offset = (string) iCalUtilityFunctions::_tz2offset( $tz ) * -1;
+        $tz     = 'UTC';
+        $tzSts  = TRUE;
+      }
+      elseif( !empty( $wtz ))
+        $tzSts  = TRUE;
+      $tz       = trim( $tz );
+      if(( 'Z' == $tz ) || ( 'GMT' == strtoupper( $tz )))
+        $tz     = 'UTC';
+      if( 0 < substr_count( $datetime, '-' ))
+        $datetime = str_replace( '-', '/', $datetime );
+      try {
+        $d        = new DateTime( $datetime, new DateTimeZone( $tz ));
+        if( 0  != $offset )  // adjust for offset
+          $d->modify( $offset.' seconds' );
+        $datestring = $d->format( 'Y-m-d-H-i-s' );
+        unset( $d );
+      }
+      catch( Exception $e ) {
+        $datestring = date( 'Y-m-d-H-i-s', strtotime( $datetime ));
+      }
+    } // end if( !empty( $tz ) && ( 17 < $len ))
+    else
+      $datestring = date( 'Y-m-d-H-i-s', strtotime( $datetime ));
+// echo "<tr><td>&nbsp;<td colspan='3'>_strdate2date input=$datetime, tz=$tz, offset=$offset, wtz=$wtz, len=$len, prepDate=$datestring\n";
+    if( 'UTC' == $tz )
+      $tz         = 'Z';
+    $d            = explode( '-', $datestring );
+    $output       = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2] );
+    if((( FALSE !== $parno ) && ( 3 != $parno )) || // parno is set to 6 or 7
+       (( FALSE === $parno ) && ( 'Z' == $tz ))  || // parno is not set and UTC
+       (( FALSE === $parno ) && ( 'Z' != $tz ) && ( 0 != $d[3] + $d[4] + $d[5] ) && ( 17 < $len ))) { // !parno and !UTC and 0 != hour+min+sec and long input text
+      $output['hour'] = $d[3];
+      $output['min']  = $d[4];
+      $output['sec']  = $d[5];
+      if(( $tzSts || ( 7 == $parno )) && !empty( $tz ))
+        $output['tz'] = $tz;
+    }
+    // return original string in the array in case strtotime failed to make sense of it
+    $output['unparsedtext'] = $unparseddatetime;
+    return $output;
+  }
+/********************************************************************************/
+/**
+ * special characters management output
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.2 - 2012-12-18
+ * @param string $string
+ * @param string $format
+ * @param string $nl
+ * @return string
+ */
+  public static function _strrep( $string, $format, $nl ) {
+    switch( $format ) {
+      case 'xcal':
+        $string = str_replace( '\n',  $nl, $string);
+        $string = htmlspecialchars( strip_tags( stripslashes( urldecode ( $string ))));
+        break;
+      default:
+        $pos = 0;
+        $specChars = array( 'n', 'N', 'r', ',', ';' );
+        while( isset( $string[$pos] )) {
+          if( FALSE === ( $pos = strpos( $string, "\\", $pos )))
+            break;
+          if( !in_array( substr( $string, $pos, 1 ), $specChars )) {
+            $string = substr( $string, 0, $pos )."\\".substr( $string, ( $pos + 1 ));
+            $pos += 1;
+          }
+          $pos += 1;
+        }
+        if( FALSE !== strpos( $string, '"' ))
+          $string = str_replace('"',   "'",       $string);
+        if( FALSE !== strpos( $string, ',' ))
+          $string = str_replace(',',   '\,',      $string);
+        if( FALSE !== strpos( $string, ';' ))
+          $string = str_replace(';',   '\;',      $string);
+        if( FALSE !== strpos( $string, "\r\n" ))
+          $string = str_replace( "\r\n", '\n',    $string);
+        elseif( FALSE !== strpos( $string, "\r" ))
+          $string = str_replace( "\r", '\n',      $string);
+        elseif( FALSE !== strpos( $string, "\n" ))
+          $string = str_replace( "\n", '\n',      $string);
+        if( FALSE !== strpos( $string, '\N' ))
+          $string = str_replace( '\N', '\n',      $string);
+//        if( FALSE !== strpos( $string, $nl ))
+          $string = str_replace( $nl, '\n', $string);
+        break;
+    }
+    return $string;
+  }
+/**
+ * special characters management input (from iCal file)
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.16.2 - 2012-12-18
+ * @param string $string
+ * @return string
+ */
+  public static function _strunrep( $string ) {
+    $string = str_replace( '\\\\', '\\',     $string);
+    $string = str_replace( '\,',   ',',      $string);
+    $string = str_replace( '\;',   ';',      $string);
+//    $string = str_replace( '\n',  $nl, $string); // ??
+    return $string;
+  }
+/**
+ * convert timestamp to date array, default UTC or adjusted for offset/timezone
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.15.1 - 2012-10-17
+ * @param mixed   $timestamp
+ * @param int     $parno
+ * @param string  $wtz
+ * @return array
+ */
+  public static function _timestamp2date( $timestamp, $parno=6, $wtz=null ) {
+    if( is_array( $timestamp )) {
+      $tz        = ( isset( $timestamp['tz'] )) ? $timestamp['tz'] : $wtz;
+      $timestamp = $timestamp['timestamp'];
+    }
+    $tz          = ( isset( $tz )) ? $tz : $wtz;
+    if( empty( $tz ) || ( 'Z' == $tz ) || ( 'GMT' == strtoupper( $tz )))
+      $tz        = 'UTC';
+    elseif( iCalUtilityFunctions::_isOffset( $tz )) {
+      $offset    = iCalUtilityFunctions::_tz2offset( $tz );
+      $tz        = 'UTC';
+    }
+    try {
+      $d         = new DateTime( "@$timestamp" );  // set UTC date
+      if( isset( $offset ) && ( 0 != $offset ))    // adjust for offset
+        $d->modify( $offset.' seconds' );
+      elseif( 'UTC' != $tz )
+        $d->setTimezone( new DateTimeZone( $tz )); // convert to local date
+      $date      = $d->format( 'Y-m-d-H-i-s' );
+      unset( $d );
+    }
+    catch( Exception $e ) {
+      $date      = date( 'Y-m-d-H-i-s', $timestamp );
+    }
+    $date        = explode( '-', $date );
+    $output      = array( 'year' => $date[0], 'month' => $date[1], 'day' => $date[2] );
+    if( 3 != $parno ) {
+      $output['hour'] = $date[3];
+      $output['min']  = $date[4];
+      $output['sec']  = $date[5];
+      if( 'UTC' == $tz && ( !isset( $offset ) || ( 0 == $offset )))
+        $output['tz'] = 'Z';
+    }
+    return $output;
+  }
+/**
+ * convert timestamp (seconds) to duration in array format
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.6.23 - 2010-10-23
+ * @param int $timestamp
+ * @return array, duration format
+ */
+  public static function _timestamp2duration( $timestamp ) {
+    $dur         = array();
+    $dur['week'] = (int) floor( $timestamp / ( 7 * 24 * 60 * 60 ));
+    $timestamp   =              $timestamp % ( 7 * 24 * 60 * 60 );
+    $dur['day']  = (int) floor( $timestamp / ( 24 * 60 * 60 ));
+    $timestamp   =              $timestamp % ( 24 * 60 * 60 );
+    $dur['hour'] = (int) floor( $timestamp / ( 60 * 60 ));
+    $timestamp   =              $timestamp % ( 60 * 60 );
+    $dur['min']  = (int) floor( $timestamp / ( 60 ));
+    $dur['sec']  = (int)        $timestamp % ( 60 );
+    return $dur;
+  }
+/**
+ * transforms a dateTime from a timezone to another using PHP DateTime and DateTimeZone class (PHP >= PHP 5.2.0)
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.15.1 - 2012-10-17
+ * @param mixed  $date,   date to alter
+ * @param string $tzFrom, PHP valid 'from' timezone
+ * @param string $tzTo,   PHP valid 'to' timezone, default 'UTC'
+ * @param string $format, date output format, default 'Ymd\THis'
+ * @return bool
+ */
+  public static function transformDateTime( & $date, $tzFrom, $tzTo='UTC', $format = 'Ymd\THis' ) {
+    if( is_array( $date ) && isset( $date['timestamp'] )) {
+      try {
+        $d = new DateTime( "@{$date['timestamp']}" ); // set UTC date
+        $d->setTimezone(new DateTimeZone( $tzFrom )); // convert to 'from' date
+      }
+      catch( Exception $e ) { return FALSE; }
+    }
+    else {
+      if( iCalUtilityFunctions::_isArrayDate( $date )) {
+        if( isset( $date['tz'] ))
+          unset( $date['tz'] );
+        $date  = iCalUtilityFunctions::_date2strdate( iCalUtilityFunctions::_chkDateArr( $date ));
+      }
+      if( 'Z' == substr( $date, -1 ))
+        $date = substr( $date, 0, ( strlen( $date ) - 2 ));
+      try { $d = new DateTime( $date, new DateTimeZone( $tzFrom )); }
+      catch( Exception $e ) { return FALSE; }
+    }
+    try { $d->setTimezone( new DateTimeZone( $tzTo )); }
+    catch( Exception $e ) { return FALSE; }
+    $date = $d->format( $format );
+    return TRUE;
+  }
+/**
+ * convert offset, [+/-]HHmm[ss], to seconds, used when correcting UTC to localtime or v.v.
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.11.4 - 2012-01-11
+ * @param string $offset
+ * @return integer
+ */
+  public static function _tz2offset( $tz ) {
+    $tz           = trim( (string) $tz );
+    $offset       = 0;
+    if(((     5  != strlen( $tz ))       && ( 7  != strlen( $tz ))) ||
+       ((    '+' != substr( $tz, 0, 1 )) && ( '-' != substr( $tz, 0, 1 ))) ||
+       (( '0000' >= substr( $tz, 1, 4 )) && ( '9999' < substr( $tz, 1, 4 ))) ||
+           (( 7  == strlen( $tz ))       && ( '00' > substr( $tz, 5, 2 )) && ( '99' < substr( $tz, 5, 2 ))))
+      return $offset;
+    $hours2sec    = (int) substr( $tz, 1, 2 ) * 3600;
+    $min2sec      = (int) substr( $tz, 3, 2 ) *   60;
+    $sec          = ( 7  == strlen( $tz )) ? (int) substr( $tz, -2 ) : '00';
+    $offset       = $hours2sec + $min2sec + $sec;
+    $offset       = ('-' == substr( $tz, 0, 1 )) ? $offset * -1 : $offset;
+    return $offset;
+  }
+}
+/*********************************************************************************/
+/*          iCalcreator vCard helper functions                                   */
+/*********************************************************************************/
+/**
+ * convert single ATTENDEE, CONTACT or ORGANIZER (in email format) to vCard
+ * returns vCard/TRUE or if directory (if set) or file write is unvalid, FALSE
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.12.2 - 2012-07-11
+ * @param object $email
+ * $param string $version, vCard version (default 2.1)
+ * $param string $directory, where to save vCards (default FALSE)
+ * $param string $ext, vCard file extension (default 'vcf')
+ * @return mixed
+ */
+function iCal2vCard( $email, $version='2.1', $directory=FALSE, $ext='vcf' ) {
+  if( FALSE === ( $pos = strpos( $email, '@' )))
+    return FALSE;
+  if( $directory ) {
+    if( DIRECTORY_SEPARATOR != substr( $directory, ( 0 - strlen( DIRECTORY_SEPARATOR ))))
+      $directory .= DIRECTORY_SEPARATOR;
+    if( !is_dir( $directory ) || !is_writable( $directory ))
+      return FALSE;
+  }
+            /* prepare vCard */
+  $email  = str_replace( 'MAILTO:', '', $email );
+  $name   = $person = substr( $email, 0, $pos );
+  if( ctype_upper( $name ) || ctype_lower( $name ))
+    $name = array( $name );
+  else {
+    if( FALSE !== ( $pos = strpos( $name, '.' ))) {
+      $name = explode( '.', $name );
+      foreach( $name as $k => $part )
+        $name[$k] = ucfirst( $part );
+    }
+    else { // split camelCase
+      $chars = $name;
+      $name  = array( $chars[0] );
+      $k     = 0;
+      $x     = 1;
+      while( FALSE !== ( $char = substr( $chars, $x, 1 ))) {
+        if( ctype_upper( $char )) {
+          $k += 1;
+          $name[$k] = '';
+        }
+        $name[$k]  .= $char;
+        $x++;
+      }
+    }
+  }
+  $nl     = "\r\n";
+  $FN     = 'FN:'.implode( ' ', $name ).$nl;
+  $name   = array_reverse( $name );
+  $N      = 'N:'.array_shift( $name );
+  $scCnt  = 0;
+  while( NULL != ( $part = array_shift( $name ))) {
+    if(( '4.0' != $version ) || ( 4 > $scCnt ))
+      $scCnt += 1;
+    $N   .= ';'.$part;
+  }
+  while(( '4.0' == $version ) && ( 4 > $scCnt )) {
+    $N   .= ';';
+    $scCnt += 1;
+  }
+  $N     .= $nl;
+  $EMAIL  = 'EMAIL:'.$email.$nl;
+           /* create vCard */
+  $vCard  = 'BEGIN:VCARD'.$nl;
+  $vCard .= "VERSION:$version$nl";
+  $vCard .= 'PRODID:-//kigkonsult.se '.ICALCREATOR_VERSION."//$nl";
+  $vCard .= $N;
+  $vCard .= $FN;
+  $vCard .= $EMAIL;
+  $vCard .= 'REV:'.gmdate( 'Ymd\THis\Z' ).$nl;
+  $vCard .= 'END:VCARD'.$nl;
+            /* save each vCard as (unique) single file */
+  if( $directory ) {
+    $fname = $directory.preg_replace( '/[^a-z0-9.]/i', '', $email );
+    $cnt   = 1;
+    $dbl   = '';
+    while( is_file ( $fname.$dbl.'.'.$ext )) {
+      $cnt += 1;
+      $dbl = "_$cnt";
+    }
+    if( FALSE === file_put_contents( $fname, $fname.$dbl.'.'.$ext ))
+      return FALSE;
+    return TRUE;
+  }
+            /* return vCard */
+  else
+    return $vCard;
+}
+/**
+ * convert ATTENDEEs, CONTACTs and ORGANIZERs (in email format) to vCards
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.12.2 - 2012-05-07
+ * @param object $calendar, iCalcreator vcalendar instance reference
+ * $param string $version, vCard version (default 2.1)
+ * $param string $directory, where to save vCards (default FALSE)
+ * $param string $ext, vCard file extension (default 'vcf')
+ * @return mixed
+ */
+function iCal2vCards( & $calendar, $version='2.1', $directory=FALSE, $ext='vcf' ) {
+  $hits   = array();
+  $vCardP = array( 'ATTENDEE', 'CONTACT', 'ORGANIZER' );
+  foreach( $vCardP as $prop ) {
+    $hits2 = $calendar->getProperty( $prop );
+    foreach( $hits2 as $propValue => $occCnt ) {
+      if( FALSE === ( $pos = strpos( $propValue, '@' )))
+        continue;
+      $propValue = str_replace( 'MAILTO:', '', $propValue );
+      if( isset( $hits[$propValue] ))
+        $hits[$propValue] += $occCnt;
+      else
+        $hits[$propValue]  = $occCnt;
+    }
+  }
+  if( empty( $hits ))
+    return FALSE;
+  ksort( $hits );
+  $output   = '';
+  foreach( $hits as $email => $skip ) {
+    $res = iCal2vCard( $email, $version, $directory, $ext );
+    if( $directory && !$res )
+      return FALSE;
+    elseif( !$res )
+      return $res;
+    else
+      $output .= $res;
+  }
+  if( $directory )
+    return TRUE;
+  if( !empty( $output ))
+    return $output;
+  return FALSE;
+}
+/*********************************************************************************/
+/*          iCalcreator XML (rfc6321) helper functions                           */
+/*********************************************************************************/
+/**
+ * format iCal XML output, rfc6321, using PHP SimpleXMLElement
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.15.6 - 2012-10-19
+ * @param object $calendar, iCalcreator vcalendar instance reference
+ * @return string
+ */
+function iCal2XML( & $calendar ) {
+            /** fix an SimpleXMLElement instance and create root element */
+  $xmlstr     = '<?xml version="1.0" encoding="utf-8"?><icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0">';
+  $xmlstr    .= '<!-- created utilizing kigkonsult.se '.ICALCREATOR_VERSION.' iCal2XMl (rfc6321) -->';
+  $xmlstr    .= '</icalendar>';
+  $xml        = new SimpleXMLElement( $xmlstr );
+  $vcalendar  = $xml->addChild( 'vcalendar' );
+            /** fix calendar properties */
+  $properties = $vcalendar->addChild( 'properties' );
+  $calProps = array( 'prodid', 'version', 'calscale', 'method' );
+  foreach( $calProps as $calProp ) {
+    if( FALSE !== ( $content = $calendar->getProperty( $calProp )))
+      _addXMLchild( $properties, $calProp, 'text', $content );
+  }
+  while( FALSE !== ( $content = $calendar->getProperty( FALSE, FALSE, TRUE )))
+    _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
+  $langCal = $calendar->getConfig( 'language' );
+            /** prepare to fix components with properties */
+  $components    = $vcalendar->addChild( 'components' );
+  $comps         = array( 'vtimezone', 'vevent', 'vtodo', 'vjournal', 'vfreebusy' );
+  foreach( $comps as $compName ) {
+    switch( $compName ) {
+      case 'vevent':
+      case 'vtodo':
+        $subComps     = array( 'valarm' );
+        break;
+      case 'vjournal':
+      case 'vfreebusy':
+        $subComps     = array();
+        break;
+      case 'vtimezone':
+        $subComps     = array( 'standard', 'daylight' );
+        break;
+    } // end switch( $compName )
+            /** fix component properties */
+    while( FALSE !== ( $component = $calendar->getComponent( $compName ))) {
+      $child      = $components->addChild( $compName );
+      $properties = $child->addChild( 'properties' );
+      $langComp   = $component->getConfig( 'language' );
+      $props      = $component->getConfig( 'setPropertyNames' );
+      foreach( $props as $prop ) {
+        switch( strtolower( $prop )) {
+          case 'attach':          // may occur multiple times, below
+            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
+              $type = ( isset( $content['params']['VALUE'] ) && ( 'BINARY' == $content['params']['VALUE'] )) ? 'binary' : 'uri';
+              unset( $content['params']['VALUE'] );
+              _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
+            }
+            break;
+          case 'attendee':
+            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
+              if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
+                if( $langComp )
+                  $content['params']['LANGUAGE'] = $langComp;
+                elseif( $langCal )
+                  $content['params']['LANGUAGE'] = $langCal;
+              }
+              _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
+            }
+            break;
+          case 'exdate':
+            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
+              $type = ( isset( $content['params']['VALUE'] ) && ( 'DATE' == $content['params']['VALUE'] )) ? 'date' : 'date-time';
+              unset( $content['params']['VALUE'] );
+              _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
+            }
+            break;
+          case 'freebusy':
+            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
+              if( is_array( $content ) && isset( $content['value']['fbtype'] )) {
+                $content['params']['FBTYPE'] = $content['value']['fbtype'];
+                unset( $content['value']['fbtype'] );
+              }
+              _addXMLchild( $properties, $prop, 'period', $content['value'], $content['params'] );
+            }
+            break;
+          case 'request-status':
+            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
+              if( !isset( $content['params']['LANGUAGE'] )) {
+                if( $langComp )
+                  $content['params']['LANGUAGE'] = $langComp;
+                elseif( $langCal )
+                  $content['params']['LANGUAGE'] = $langCal;
+              }
+              _addXMLchild( $properties, $prop, 'rstatus', $content['value'], $content['params'] );
+            }
+            break;
+          case 'rdate':
+            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
+              $type = 'date-time';
+              if( isset( $content['params']['VALUE'] )) {
+                if( 'DATE' == $content['params']['VALUE'] )
+                  $type = 'date';
+                elseif( 'PERIOD' == $content['params']['VALUE'] )
+                  $type = 'period';
+              }
+              unset( $content['params']['VALUE'] );
+              _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
+            }
+            break;
+          case 'categories':
+          case 'comment':
+          case 'contact':
+          case 'description':
+          case 'related-to':
+          case 'resources':
+            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
+              if(( 'related-to' != $prop ) && !isset( $content['params']['LANGUAGE'] )) {
+                if( $langComp )
+                  $content['params']['LANGUAGE'] = $langComp;
+                elseif( $langCal )
+                  $content['params']['LANGUAGE'] = $langCal;
+              }
+              _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
+            }
+            break;
+          case 'x-prop':
+            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
+              _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
+            break;
+          case 'created':         // single occurence below, if set
+          case 'completed':
+          case 'dtstamp':
+          case 'last-modified':
+            $utcDate = TRUE;
+          case 'dtstart':
+          case 'dtend':
+          case 'due':
+          case 'recurrence-id':
+            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
+              $type = ( isset( $content['params']['VALUE'] ) && ( 'DATE' == $content['params']['VALUE'] )) ? 'date' : 'date-time';
+              unset( $content['params']['VALUE'] );
+              if(( isset( $content['params']['TZID'] ) && empty( $content['params']['TZID'] )) || @is_null( $content['params']['TZID'] ))
+                unset( $content['params']['TZID'] );
+              _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
+            }
+            unset( $utcDate );
+            break;
+          case 'duration':
+            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
+              if( !isset( $content['value']['relatedStart'] ) || ( TRUE !== $content['value']['relatedStart'] ))
+                $content['params']['RELATED'] = 'END';
+              _addXMLchild( $properties, $prop, 'duration', $content['value'], $content['params'] );
+            }
+            break;
+          case 'rrule':
+            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
+              _addXMLchild( $properties, $prop, 'recur', $content['value'], $content['params'] );
+            break;
+          case 'class':
+          case 'location':
+          case 'status':
+          case 'summary':
+          case 'transp':
+          case 'tzid':
+          case 'uid':
+            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
+              if((( 'location' == $prop ) || ( 'summary' == $prop )) && !isset( $content['params']['LANGUAGE'] )) {
+                if( $langComp )
+                  $content['params']['LANGUAGE'] = $langComp;
+                elseif( $langCal )
+                  $content['params']['LANGUAGE'] = $langCal;
+              }
+              _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
+            }
+            break;
+          case 'geo':
+            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
+              _addXMLchild( $properties, $prop, 'geo', $content['value'], $content['params'] );
+            break;
+          case 'organizer':
+            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
+              if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
+                if( $langComp )
+                  $content['params']['LANGUAGE'] = $langComp;
+                elseif( $langCal )
+                  $content['params']['LANGUAGE'] = $langCal;
+              }
+              _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
+            }
+            break;
+          case 'percent-complete':
+          case 'priority':
+          case 'sequence':
+            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
+              _addXMLchild( $properties, $prop, 'integer', $content['value'], $content['params'] );
+            break;
+          case 'tzurl':
+          case 'url':
+            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
+              _addXMLchild( $properties, $prop, 'uri', $content['value'], $content['params'] );
+            break;
+        } // end switch( $prop )
+      } // end foreach( $props as $prop )
+            /** fix subComponent properties, if any */
+      foreach( $subComps as $subCompName ) {
+        while( FALSE !== ( $subcomp = $component->getComponent( $subCompName ))) {
+          $child2       = $child->addChild( $subCompName );
+          $properties   = $child2->addChild( 'properties' );
+          $langComp     = $subcomp->getConfig( 'language' );
+          $subCompProps = $subcomp->getConfig( 'setPropertyNames' );
+          foreach( $subCompProps as $prop ) {
+            switch( strtolower( $prop )) {
+              case 'attach':          // may occur multiple times, below
+                while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
+                  $type = ( isset( $content['params']['VALUE'] ) && ( 'BINARY' == $content['params']['VALUE'] )) ? 'binary' : 'uri';
+                  unset( $content['params']['VALUE'] );
+                  _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
+                }
+                break;
+              case 'attendee':
+                while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
+                  if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
+                    if( $langComp )
+                      $content['params']['LANGUAGE'] = $langComp;
+                    elseif( $langCal )
+                      $content['params']['LANGUAGE'] = $langCal;
+                  }
+                  _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
+                }
+                break;
+              case 'comment':
+              case 'tzname':
+                while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
+                  if( !isset( $content['params']['LANGUAGE'] )) {
+                    if( $langComp )
+                      $content['params']['LANGUAGE'] = $langComp;
+                    elseif( $langCal )
+                      $content['params']['LANGUAGE'] = $langCal;
+                  }
+                  _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
+                }
+                break;
+              case 'rdate':
+                while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
+                  $type = 'date-time';
+                  if( isset( $content['params']['VALUE'] )) {
+                    if( 'DATE' == $content['params']['VALUE'] )
+                      $type = 'date';
+                    elseif( 'PERIOD' == $content['params']['VALUE'] )
+                      $type = 'period';
+                  }
+                  unset( $content['params']['VALUE'] );
+                  _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
+                }
+                break;
+              case 'x-prop':
+                while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
+                  _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
+                break;
+              case 'action':      // single occurence below, if set
+              case 'description':
+              case 'summary':
+                if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
+                  if(( 'action' != $prop ) && !isset( $content['params']['LANGUAGE'] )) {
+                    if( $langComp )
+                      $content['params']['LANGUAGE'] = $langComp;
+                    elseif( $langCal )
+                      $content['params']['LANGUAGE'] = $langCal;
+                  }
+                  _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
+                }
+                break;
+              case 'dtstart':
+                if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
+                  unset( $content['value']['tz'], $content['params']['VALUE'] ); // always local time
+                  _addXMLchild( $properties, $prop, 'date-time', $content['value'], $content['params'] );
+                }
+                break;
+              case 'duration':
+                if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
+                  _addXMLchild( $properties, $prop, 'duration', $content['value'], $content['params'] );
+                break;
+              case 'repeat':
+                if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
+                  _addXMLchild( $properties, $prop, 'integer', $content['value'], $content['params'] );
+                break;
+              case 'trigger':
+                if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
+                  if( isset( $content['value']['year'] )   &&
+                      isset( $content['value']['month'] )  &&
+                      isset( $content['value']['day'] ))
+                    $type = 'date-time';
+                  else {
+                    $type = 'duration';
+                    if( !isset( $content['value']['relatedStart'] ) || ( TRUE !== $content['value']['relatedStart'] ))
+                      $content['params']['RELATED'] = 'END';
+                  }
+                  _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
+                }
+                break;
+              case 'tzoffsetto':
+              case 'tzoffsetfrom':
+                if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
+                  _addXMLchild( $properties, $prop, 'utc-offset', $content['value'], $content['params'] );
+                break;
+              case 'rrule':
+                while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
+                  _addXMLchild( $properties, $prop, 'recur', $content['value'], $content['params'] );
+                break;
+            } // switch( $prop )
+          } // end foreach( $subCompProps as $prop )
+        } // end while( FALSE !== ( $subcomp = $component->getComponent( subCompName )))
+      } // end foreach( $subCombs as $subCompName )
+    } // end while( FALSE !== ( $component = $calendar->getComponent( $compName )))
+  } // end foreach( $comps as $compName)
+  return $xml->asXML();
+}
+/**
+ * Add children to a SimpleXMLelement
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.15.5 - 2012-10-19
+ * @param object $parent,  reference to a SimpleXMLelement node
+ * @param string $name,    new element node name
+ * @param string $type,    content type, subelement(-s) name
+ * @param string $content, new subelement content
+ * @param array  $params,  new element 'attributes'
+ * @return void
+ */
+function _addXMLchild( & $parent, $name, $type, $content, $params=array()) {
+            /** create new child node */
+  $name  = strtolower( $name );
+  $child = $parent->addChild( $name );
+  if( isset( $params['VALUE'] ))
+    unset( $params['VALUE'] );
+  if( !empty( $params )) {
+    $parameters = $child->addChild( 'parameters' );
+    foreach( $params as $param => $parVal ) {
+      $param = strtolower( $param );
+      if( 'x-' == substr( $param, 0, 2  )) {
+        $p1 = $parameters->addChild( $param );
+        $p2 = $p1->addChild( 'unknown', htmlspecialchars( $parVal ));
+      }
+      else {
+        $p1 = $parameters->addChild( $param );
+        switch( $param ) {
+          case 'altrep':
+          case 'dir':            $ptype = 'uri';            break;
+          case 'delegated-from':
+          case 'delegated-to':
+          case 'member':
+          case 'sent-by':        $ptype = 'cal-address';    break;
+          case 'rsvp':           $ptype = 'boolean';        break ;
+          default:               $ptype = 'text';           break;
+        }
+        if( is_array( $parVal )) {
+          foreach( $parVal as $pV )
+            $p2 = $p1->addChild( $ptype, htmlspecialchars( $pV ));
+        }
+        else
+          $p2 = $p1->addChild( $ptype, htmlspecialchars( $parVal ));
+      }
+    }
+  }
+  if( empty( $content ) && ( '0' != $content ))
+    return;
+            /** store content */
+  switch( $type ) {
+    case 'binary':
+      $v = $child->addChild( $type, $content );
+      break;
+    case 'boolean':
+      break;
+    case 'cal-address':
+      $v = $child->addChild( $type, $content );
+      break;
+    case 'date':
+      if( array_key_exists( 'year', $content ))
+        $content = array( $content );
+      foreach( $content as $date ) {
+        $str = sprintf( '%04d-%02d-%02d', $date['year'], $date['month'], $date['day'] );
+        $v = $child->addChild( $type, $str );
+      }
+      break;
+    case 'date-time':
+      if( array_key_exists( 'year', $content ))
+        $content = array( $content );
+      foreach( $content as $dt ) {
+        if( !isset( $dt['hour'] )) $dt['hour'] = 0;
+        if( !isset( $dt['min'] ))  $dt['min']  = 0;
+        if( !isset( $dt['sec'] ))  $dt['sec']  = 0;
+        $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $dt['year'], $dt['month'], $dt['day'], $dt['hour'], $dt['min'], $dt['sec'] );
+        if( isset( $dt['tz'] ) && ( 'Z' == $dt['tz'] ))
+          $str .= 'Z';
+        $v = $child->addChild( $type, $str );
+      }
+      break;
+    case 'duration':
+      $output = (( 'trigger' == $name ) && ( FALSE !== $content['before'] )) ? '-' : '';
+      $v = $child->addChild( $type, $output.iCalUtilityFunctions::_duration2str( $content ) );
+      break;
+    case 'geo':
+      $v1 = $child->addChild( 'latitude',  number_format( (float) $content['latitude'],  6, '.', '' ));
+      $v1 = $child->addChild( 'longitude', number_format( (float) $content['longitude'], 6, '.', '' ));
+      break;
+    case 'integer':
+      $v = $child->addChild( $type, $content );
+      break;
+    case 'period':
+      if( !is_array( $content ))
+        break;
+      foreach( $content as $period ) {
+        $v1 = $child->addChild( $type );
+        $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $period[0]['year'], $period[0]['month'], $period[0]['day'], $period[0]['hour'], $period[0]['min'], $period[0]['sec'] );
+        if( isset( $period[0]['tz'] ) && ( 'Z' == $period[0]['tz'] ))
+          $str .= 'Z';
+        $v2 = $v1->addChild( 'start', $str );
+        if( array_key_exists( 'year', $period[1] )) {
+          $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $period[1]['year'], $period[1]['month'], $period[1]['day'], $period[1]['hour'], $period[1]['min'], $period[1]['sec'] );
+          if( isset($period[1]['tz'] ) && ( 'Z' == $period[1]['tz'] ))
+            $str .= 'Z';
+          $v2 = $v1->addChild( 'end', $str );
+        }
+        else
+          $v2 = $v1->addChild( 'duration', iCalUtilityFunctions::_duration2str( $period[1] ));
+      }
+      break;
+    case 'recur':
+      foreach( $content as $rulelabel => $rulevalue ) {
+        $rulelabel = strtolower( $rulelabel );
+        switch( $rulelabel ) {
+          case 'until':
+            if( isset( $rulevalue['hour'] ))
+              $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02dZ', $rulevalue['year'], $rulevalue['month'], $rulevalue['day'], $rulevalue['hour'], $rulevalue['min'], $rulevalue['sec'] );
+            else
+              $str = sprintf( '%04d-%02d-%02d', $rulevalue['year'], $rulevalue['month'], $rulevalue['day'] );
+            $v = $child->addChild( $rulelabel, $str );
+            break;
+          case 'bysecond':
+          case 'byminute':
+          case 'byhour':
+          case 'bymonthday':
+          case 'byyearday':
+          case 'byweekno':
+          case 'bymonth':
+          case 'bysetpos': {
+            if( is_array( $rulevalue )) {
+              foreach( $rulevalue as $vix => $valuePart )
+                $v = $child->addChild( $rulelabel, $valuePart );
+            }
+            else
+              $v = $child->addChild( $rulelabel, $rulevalue );
+            break;
+          }
+          case 'byday': {
+            if( isset( $rulevalue['DAY'] )) {
+              $str  = ( isset( $rulevalue[0] )) ? $rulevalue[0] : '';
+              $str .= $rulevalue['DAY'];
+              $p    = $child->addChild( $rulelabel, $str );
+            }
+            else {
+              foreach( $rulevalue as $valuePart ) {
+                if( isset( $valuePart['DAY'] )) {
+                  $str  = ( isset( $valuePart[0] )) ? $valuePart[0] : '';
+                  $str .= $valuePart['DAY'];
+                  $p    = $child->addChild( $rulelabel, $str );
+                }
+                else
+                  $p    = $child->addChild( $rulelabel, $valuePart );
+              }
+            }
+            break;
+          }
+          case 'freq':
+          case 'count':
+          case 'interval':
+          case 'wkst':
+          default:
+            $p = $child->addChild( $rulelabel, $rulevalue );
+            break;
+        } // end switch( $rulelabel )
+      } // end foreach( $content as $rulelabel => $rulevalue )
+      break;
+    case 'rstatus':
+      $v = $child->addChild( 'code', number_format( (float) $content['statcode'], 2, '.', ''));
+      $v = $child->addChild( 'description', htmlspecialchars( $content['text'] ));
+      if( isset( $content['extdata'] ))
+        $v = $child->addChild( 'data', htmlspecialchars( $content['extdata'] ));
+      break;
+    case 'text':
+      if( !is_array( $content ))
+        $content = array( $content );
+      foreach( $content as $part )
+        $v = $child->addChild( $type, htmlspecialchars( $part ));
+      break;
+    case 'time':
+      break;
+    case 'uri':
+      $v = $child->addChild( $type, $content );
+      break;
+    case 'utc-offset':
+      if( in_array( substr( $content, 0, 1 ), array( '-', '+' ))) {
+        $str     = substr( $content, 0, 1 );
+        $content = substr( $content, 1 );
+      }
+      else
+        $str     = '+';
+      $str .= substr( $content, 0, 2 ).':'.substr( $content, 2, 2 );
+      if( 4 < strlen( $content ))
+        $str .= ':'.substr( $content, 4 );
+      $v = $child->addChild( $type, $str );
+      break;
+    case 'unknown':
+    default:
+      if( is_array( $content ))
+        $content = implode( '', $content );
+      $v = $child->addChild( 'unknown', htmlspecialchars( $content ));
+      break;
+  }
+}
+/**
+ * parse xml string into iCalcreator instance
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since 2.11.2 - 2012-01-31
+ * @param  string $xmlstr
+ * @param  array  $iCalcfg iCalcreator config array (opt)
+ * @return mixed  iCalcreator instance or FALSE on error
+ */
+function & XMLstr2iCal( $xmlstr, $iCalcfg=array()) {
+  libxml_use_internal_errors( TRUE );
+  $xml = simplexml_load_string( $xmlstr );
+  if( !$xml ) {
+    $str    = '';
+    $return = FALSE;
+    foreach( libxml_get_errors() as $error ) {
+      switch ( $error->level ) {
+        case LIBXML_ERR_FATAL:   $str .= ' FATAL ';   break;
+        case LIBXML_ERR_ERROR:   $str .= ' ERROR ';   break;
+        case LIBXML_ERR_WARNING:
+        default:                 $str .= ' WARNING '; break;
+      }
+      $str .= PHP_EOL.'Error when loading XML';
+      if( !empty( $error->file ))
+        $str .= ',  file:'.$error->file.', ';
+      $str .= ', line:'.$error->line;
+      $str .= ', ('.$error->code.') '.$error->message;
+    }
+    error_log( $str );
+    if( LIBXML_ERR_WARNING != $error->level )
+      return $return;
+    libxml_clear_errors();
+  }
+  return xml2iCal( $xml, $iCalcfg );
+}
+/**
+ * parse xml file into iCalcreator instance
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since  2.11.2 - 2012-01-20
+ * @param  string $xmlfile
+ * @param  array$iCalcfg iCalcreator config array (opt)
+ * @return mixediCalcreator instance or FALSE on error
+ */
+function & XMLfile2iCal( $xmlfile, $iCalcfg=array()) {
+  libxml_use_internal_errors( TRUE );
+  $xml = simplexml_load_file( $xmlfile );
+  if( !$xml ) {
+    $str = '';
+    foreach( libxml_get_errors() as $error ) {
+      switch ( $error->level ) {
+        case LIBXML_ERR_FATAL:   $str .= 'FATAL ';   break;
+        case LIBXML_ERR_ERROR:   $str .= 'ERROR ';   break;
+        case LIBXML_ERR_WARNING:
+        default:                 $str .= 'WARNING '; break;
+      }
+      $str .= 'Failed loading XML'.PHP_EOL;
+      if( !empty( $error->file ))
+        $str .= ' file:'.$error->file.', ';
+      $str .= 'line:'.$error->line.PHP_EOL;
+      $str .= '('.$error->code.') '.$error->message.PHP_EOL;
+    }
+    error_log( $str );
+    if( LIBXML_ERR_WARNING != $error->level )
+      return FALSE;
+    libxml_clear_errors();
+  }
+  return xml2iCal( $xml, $iCalcfg );
+}
+/**
+ * parse SimpleXMLElement instance into iCalcreator instance
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since  2.11.2 - 2012-01-27
+ * @param  object $xmlobj  SimpleXMLElement
+ * @param  array  $iCalcfg iCalcreator config array (opt)
+ * @return mixed  iCalcreator instance or FALSE on error
+ */
+function & XML2iCal( $xmlobj, $iCalcfg=array()) {
+  $iCal = new vcalendar( $iCalcfg );
+  foreach( $xmlobj->children() as $icalendar ) { // vcalendar
+    foreach( $icalendar->children() as $calPart ) { // calendar properties and components
+      if( 'components' == $calPart->getName()) {
+        foreach( $calPart->children() as $component ) { // single components
+          if( 0 < $component->count())
+            _getXMLComponents( $iCal, $component );
+        }
+      }
+      elseif(( 'properties' == $calPart->getName()) && ( 0 < $calPart->count())) {
+        foreach( $calPart->children() as $calProp ) { // calendar properties
+         $propName = $calProp->getName();
+          if(( 'calscale' != $propName ) && ( 'method' != $propName ) && ( 'x-' != substr( $propName,0,2 )))
+            continue;
+          $params = array();
+          foreach( $calProp->children() as $calPropElem ) { // single calendar property
+            if( 'parameters' == $calPropElem->getName())
+              $params = _getXMLParams( $calPropElem );
+            else
+              $iCal->setProperty( $propName, reset( $calPropElem ), $params );
+          } // end foreach( $calProp->children() as $calPropElem )
+        } // end foreach( $calPart->properties->children() as $calProp )
+      } // end if( 0 < $calPart->properties->count())
+    } // end foreach( $icalendar->children() as $calPart )
+  } // end foreach( $xmlobj->children() as $icalendar )
+  return $iCal;
+}
+/**
+ * parse SimpleXMLElement instance property parameters and return iCalcreator property parameter array
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since  2.11.2 - 2012-01-15
+ * @param  object $parameters SimpleXMLElement
+ * @return array  iCalcreator property parameter array
+ */
+function _getXMLParams( & $parameters ) {
+  if( 1 > $parameters->count())
+    return array();
+  $params = array();
+  foreach( $parameters->children() as $parameter ) { // single parameter key
+    $key   = strtoupper( $parameter->getName());
+    $value = array();
+    foreach( $parameter->children() as $paramValue ) // skip parameter value type
+      $value[] = reset( $paramValue );
+    if( 2 > count( $value ))
+      $params[$key] = html_entity_decode( reset( $value ));
+    else
+      $params[$key] = $value;
+  }
+  return $params;
+}
+/**
+ * parse SimpleXMLElement instance components, create iCalcreator component and update
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since  2.11.2 - 2012-01-15
+ * @param  array  $iCal iCalcreator calendar instance
+ * @param  object $component SimpleXMLElement
+ * @return void
+ */
+function _getXMLComponents( & $iCal, & $component ) {
+  $compName = $component->getName();
+  $comp     = & $iCal->newComponent( $compName );
+  $subComponents = array( 'valarm', 'standard', 'daylight' );
+  foreach( $component->children() as $compPart ) { // properties and (opt) subComponents
+    if( 1 > $compPart->count())
+      continue;
+    if( in_array( $compPart->getName(), $subComponents ))
+      _getXMLComponents( $comp, $compPart );
+    elseif( 'properties' == $compPart->getName()) {
+      foreach( $compPart->children() as $property ) // properties as single property
+        _getXMLProperties( $comp, $property );
+    }
+  } // end foreach( $component->children() as $compPart )
+}
+/**
+ * parse SimpleXMLElement instance property, create iCalcreator component property
+ *
+ * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @since  2.11.2 - 2012-01-27
+ * @param  array  $iCal iCalcreator calendar instance
+ * @param  object $component SimpleXMLElement
+ * @return void
+ */
+function _getXMLProperties( & $iCal, & $property ) {
+  $propName  = $property->getName();
+  $value     = $params = array();
+  $valueType = '';
+  foreach( $property->children() as $propPart ) { // calendar property parameters (opt) and value(-s)
+    $valueType = $propPart->getName();
+    if( 'parameters' == $valueType) {
+      $params = _getXMLParams( $propPart );
+      continue;
+    }
+    switch( $valueType ) {
+      case 'binary':
+        $value = reset( $propPart );
+        break;
+      case 'boolean':
+        break;
+      case 'cal-address':
+        $value = reset( $propPart );
+        break;
+      case 'date':
+        $params['VALUE'] = 'DATE';
+      case 'date-time':
+        if(( 'exdate' == $propName ) || ( 'rdate' == $propName ))
+          $value[] = reset( $propPart );
+        else
+          $value = reset( $propPart );
+        break;
+      case 'duration':
+        $value = reset( $propPart );
+        break;
+//        case 'geo':
+      case 'latitude':
+      case 'longitude':
+        $value[$valueType] = reset( $propPart );
+        break;
+      case 'integer':
+        $value = reset( $propPart );
+        break;
+      case 'period':
+        if( 'rdate' == $propName )
+          $params['VALUE'] = 'PERIOD';
+        $pData = array();
+        foreach( $propPart->children() as $periodPart )
+          $pData[] = reset( $periodPart );
+        if( !empty( $pData ))
+          $value[] = $pData;
+        break;
+//        case 'rrule':
+      case 'freq':
+      case 'count':
+      case 'until':
+      case 'interval':
+      case 'wkst':
+        $value[$valueType] = reset( $propPart );
+        break;
+      case 'bysecond':
+      case 'byminute':
+      case 'byhour':
+      case 'bymonthday':
+      case 'byyearday':
+      case 'byweekno':
+      case 'bymonth':
+      case 'bysetpos':
+        $value[$valueType][] = reset( $propPart );
+        break;
+      case 'byday':
+        $byday = reset( $propPart );
+        if( 2 == strlen( $byday ))
+          $value[$valueType][] = array( 'DAY' => $byday );
+        else {
+          $day = substr( $byday, -2 );
+          $key = substr( $byday, 0, ( strlen( $byday ) - 2 ));
+          $value[$valueType][] = array( $key, 'DAY' => $day );
+        }
+        break;
+//      case 'rstatus':
+      case 'code':
+        $value[0] = reset( $propPart );
+        break;
+      case 'description':
+        $value[1] = reset( $propPart );
+        break;
+      case 'data':
+        $value[2] = reset( $propPart );
+        break;
+      case 'text':
+        $text = str_replace( array( "\r\n", "\n\r", "\r", "\n"), '\n', reset( $propPart ));
+        $value['text'][] = html_entity_decode( $text );
+        break;
+      case 'time':
+        break;
+      case 'uri':
+        $value = reset( $propPart );
+        break;
+      case 'utc-offset':
+        $value = str_replace( ':', '', reset( $propPart ));
+        break;
+      case 'unknown':
+      default:
+        $value = html_entity_decode( reset( $propPart ));
+        break;
+    } // end switch( $valueType )
+  } // end  foreach( $property->children() as $propPart )
+  if( 'freebusy' == $propName ) {
+    $fbtype = $params['FBTYPE'];
+    unset( $params['FBTYPE'] );
+    $iCal->setProperty( $propName, $fbtype, $value, $params );
+  }
+  elseif( 'geo' == $propName )
+    $iCal->setProperty( $propName, $value['latitude'], $value['longitude'], $params );
+  elseif( 'request-status' == $propName ) {
+    if( !isset( $value[2] ))
+      $value[2] = FALSE;
+    $iCal->setProperty( $propName, $value[0], $value[1], $value[2], $params );
+  }
+  else {
+    if( isset( $value['text'] ) && is_array( $value['text'] )) {
+      if(( 'categories' == $propName ) || ( 'resources' == $propName ))
+        $value = $value['text'];
+      else
+        $value = reset( $value['text'] );
+    }
+    $iCal->setProperty( $propName, $value, $params );
+  }
+}
+/*********************************************************************************/
+/*          Additional functions to use with vtimezone components                */
+/*********************************************************************************/
+/**
+ * For use with
+ * iCalcreator (kigkonsult.se/iCalcreator/index.php)
+ * copyright (c) 2011 Yitzchok Lavi
+ * icalcreator@onebigsystem.com
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/**
+ * Additional functions to use with vtimezone components
+ *
+ * Before calling the functions, set time zone 'GMT' ('date_default_timezone_set')!
+ *
+ * @author Yitzchok Lavi <icalcreator@onebigsystem.com>
+ *         adjusted for iCalcreator Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
+ * @version 1.0.2 - 2011-02-24
+ *
+ */
+/**
+ * Returns array with the offset information from UTC for a (UTC) datetime/timestamp in the
+ * timezone, according to the VTIMEZONE information in the input array.
+ *
+ * $param array  $timezonesarray, output from function getTimezonesAsDateArrays (below)
+ * $param string $tzid,           time zone identifier
+ * $param mixed  $timestamp,      timestamp or a UTC datetime (in array format)
+ * @return array, time zone data with keys for 'offsetHis', 'offsetSec' and 'tzname'
+ *
+ */
+function getTzOffsetForDate($timezonesarray, $tzid, $timestamp) {
+    if( is_array( $timestamp )) {
+//$disp = sprintf( '%04d%02d%02d %02d%02d%02d', $timestamp['year'], $timestamp['month'], $timestamp['day'], $timestamp['hour'], $timestamp['min'], $timestamp['sec'] ); // test ###
+      $timestamp = gmmktime(
+            $timestamp['hour'],
+            $timestamp['min'],
+            $timestamp['sec'],
+            $timestamp['month'],
+            $timestamp['day'],
+            $timestamp['year']
+            ) ;
+// echo '<td colspan="4">&nbsp;'."\n".'<tr><td>&nbsp;<td class="r">'.$timestamp.'<td class="r">'.$disp.'<td colspan="4">&nbsp;'."\n".'<tr><td colspan="3">&nbsp;'; // test ###
+    }
+    $tzoffset = array();
+    // something to return if all goes wrong (such as if $tzid doesn't find us an array of dates)
+    $tzoffset['offsetHis'] = '+0000';
+    $tzoffset['offsetSec'] = 0;
+    $tzoffset['tzname']    = '?';
+    if( !isset( $timezonesarray[$tzid] ))
+      return $tzoffset;
+    $tzdatearray = $timezonesarray[$tzid];
+    if ( is_array($tzdatearray) ) {
+        sort($tzdatearray); // just in case
+        if ( $timestamp < $tzdatearray[0]['timestamp'] ) {
+            // our date is before the first change
+            $tzoffset['offsetHis'] = $tzdatearray[0]['tzbefore']['offsetHis'] ;
+            $tzoffset['offsetSec'] = $tzdatearray[0]['tzbefore']['offsetSec'] ;
+            $tzoffset['tzname']    = $tzdatearray[0]['tzbefore']['offsetHis'] ; // we don't know the tzname in this case
+        } elseif ( $timestamp >= $tzdatearray[count($tzdatearray)-1]['timestamp'] ) {
+            // our date is after the last change (we do this so our scan can stop at the last record but one)
+            $tzoffset['offsetHis'] = $tzdatearray[count($tzdatearray)-1]['tzafter']['offsetHis'] ;
+            $tzoffset['offsetSec'] = $tzdatearray[count($tzdatearray)-1]['tzafter']['offsetSec'] ;
+            $tzoffset['tzname']    = $tzdatearray[count($tzdatearray)-1]['tzafter']['tzname'] ;
+        } else {
+            // our date somewhere in between
+            // loop through the list of dates and stop at the one where the timestamp is before our date and the next one is after it
+            // we don't include the last date in our loop as there isn't one after it to check
+            for ( $i = 0 ; $i <= count($tzdatearray)-2 ; $i++ ) {
+                if(( $timestamp >= $tzdatearray[$i]['timestamp'] ) && ( $timestamp < $tzdatearray[$i+1]['timestamp'] )) {
+                    $tzoffset['offsetHis'] = $tzdatearray[$i]['tzafter']['offsetHis'] ;
+                    $tzoffset['offsetSec'] = $tzdatearray[$i]['tzafter']['offsetSec'] ;
+                    $tzoffset['tzname']    = $tzdatearray[$i]['tzafter']['tzname'] ;
+                    break;
+                }
+            }
+        }
+    }
+    return $tzoffset;
+}
+/**
+ * Returns an array containing all the timezone data in the vcalendar object
+ *
+ * @param object $vcalendar, iCalcreator calendar instance
+ * @return array, time zone transition timestamp, array before(offsetHis, offsetSec), array after(offsetHis, offsetSec, tzname)
+ *                based on the timezone data in the vcalendar object
+ *
+ */
+function getTimezonesAsDateArrays($vcalendar) {
+    $timezonedata = array();
+    while( $vtz = $vcalendar->getComponent( 'vtimezone' )) {
+        $tzid       = $vtz->getProperty('tzid');
+        $alltzdates = array();
+        while ( $vtzc = $vtz->getComponent( 'standard' )) {
+            $newtzdates = expandTimezoneDates($vtzc);
+            $alltzdates = array_merge($alltzdates, $newtzdates);
+        }
+        while ( $vtzc = $vtz->getComponent( 'daylight' )) {
+            $newtzdates = expandTimezoneDates($vtzc);
+            $alltzdates = array_merge($alltzdates, $newtzdates);
+        }
+        sort($alltzdates);
+        $timezonedata[$tzid] = $alltzdates;
+    }
+    return $timezonedata;
+}
+/**
+ * Returns an array containing time zone data from vtimezone standard/daylight instances
+ *
+ * @param object $vtzc, an iCalcreator calendar standard/daylight instance
+ * @return array, time zone data; array before(offsetHis, offsetSec), array after(offsetHis, offsetSec, tzname)
+ *
+ */
+function expandTimezoneDates($vtzc) {
+    $tzdates = array();
+    // prepare time zone "description" to attach to each change
+    $tzbefore = array();
+    $tzbefore['offsetHis']  = $vtzc->getProperty('tzoffsetfrom') ;
+    $tzbefore['offsetSec'] = iCalUtilityFunctions::_tz2offset($tzbefore['offsetHis']);
+    if(( '-' != substr( (string) $tzbefore['offsetSec'], 0, 1 )) && ( '+' != substr( (string) $tzbefore['offsetSec'], 0, 1 )))
+      $tzbefore['offsetSec'] = '+'.$tzbefore['offsetSec'];
+    $tzafter = array();
+    $tzafter['offsetHis']   = $vtzc->getProperty('tzoffsetto') ;
+    $tzafter['offsetSec']  = iCalUtilityFunctions::_tz2offset($tzafter['offsetHis']);
+    if(( '-' != substr( (string) $tzafter['offsetSec'], 0, 1 )) && ( '+' != substr( (string) $tzafter['offsetSec'], 0, 1 )))
+      $tzafter['offsetSec'] = '+'.$tzafter['offsetSec'];
+    if( FALSE === ( $tzafter['tzname'] = $vtzc->getProperty('tzname')))
+      $tzafter['tzname'] = $tzafter['offsetHis'];
+    // find out where to start from
+    $dtstart = $vtzc->getProperty('dtstart');
+    $dtstarttimestamp = mktime(
+            $dtstart['hour'],
+            $dtstart['min'],
+            $dtstart['sec'],
+            $dtstart['month'],
+            $dtstart['day'],
+            $dtstart['year']
+            ) ;
+    if( !isset( $dtstart['unparsedtext'] )) // ??
+      $dtstart['unparsedtext'] = sprintf( '%04d%02d%02dT%02d%02d%02d', $dtstart['year'], $dtstart['month'], $dtstart['day'], $dtstart['hour'], $dtstart['min'], $dtstart['sec'] );
+    if ( $dtstarttimestamp == 0 ) {
+        // it seems that the dtstart string may not have parsed correctly
+        // let's set a timestamp starting from 1902, using the time part of the original string
+        // so that the time will change at the right time of day
+        // at worst we'll get midnight again
+        $origdtstartsplit = explode('T',$dtstart['unparsedtext']) ;
+        $dtstarttimestamp = strtotime("19020101",0);
+        $dtstarttimestamp = strtotime($origdtstartsplit[1],$dtstarttimestamp);
+    }
+    // the date (in dtstart and opt RDATE/RRULE) is ALWAYS LOCAL (not utc!!), adjust from 'utc' to 'local' timestamp
+    $diff  = -1 * $tzbefore['offsetSec'];
+    $dtstarttimestamp += $diff;
+                // add this (start) change to the array of changes
+    $tzdates[] = array(
+        'timestamp' => $dtstarttimestamp,
+        'tzbefore'  => $tzbefore,
+        'tzafter'   => $tzafter
+        );
+    $datearray = getdate($dtstarttimestamp);
+    // save original array to use time parts, because strtotime (used below) apparently loses the time
+    $changetime = $datearray ;
+    // generate dates according to an RRULE line
+    $rrule = $vtzc->getProperty('rrule') ;
+    if ( is_array($rrule) ) {
+        if ( $rrule['FREQ'] == 'YEARLY' ) {
+            // calculate transition dates starting from DTSTART
+            $offsetchangetimestamp = $dtstarttimestamp;
+            // calculate transition dates until 10 years in the future
+            $stoptimestamp = strtotime("+10 year",time());
+            // if UNTIL is set, calculate until then (however far ahead)
+            if ( isset( $rrule['UNTIL'] ) && ( $rrule['UNTIL'] != '' )) {
+                $stoptimestamp = mktime(
+                    $rrule['UNTIL']['hour'],
+                    $rrule['UNTIL']['min'],
+                    $rrule['UNTIL']['sec'],
+                    $rrule['UNTIL']['month'],
+                    $rrule['UNTIL']['day'],
+                    $rrule['UNTIL']['year']
+                    ) ;
+            }
+            $count = 0 ;
+            $stopcount = isset( $rrule['COUNT'] ) ? $rrule['COUNT'] : 0 ;
+            $daynames = array(
+                        'SU' => 'Sunday',
+                        'MO' => 'Monday',
+                        'TU' => 'Tuesday',
+                        'WE' => 'Wednesday',
+                        'TH' => 'Thursday',
+                        'FR' => 'Friday',
+                        'SA' => 'Saturday'
+                        );
+            // repeat so long as we're between DTSTART and UNTIL, or we haven't prepared COUNT dates
+            while ( $offsetchangetimestamp < $stoptimestamp && ( $stopcount == 0 || $count < $stopcount ) ) {
+                // break up the timestamp into its parts
+                $datearray = getdate($offsetchangetimestamp);
+                if ( isset( $rrule['BYMONTH'] ) && ( $rrule['BYMONTH'] != 0 )) {
+                    // set the month
+                    $datearray['mon'] = $rrule['BYMONTH'] ;
+                }
+                if ( isset( $rrule['BYMONTHDAY'] ) && ( $rrule['BYMONTHDAY'] != 0 )) {
+                    // set specific day of month
+                    $datearray['mday']  = $rrule['BYMONTHDAY'];
+                } elseif ( is_array($rrule['BYDAY']) ) {
+                    // find the Xth WKDAY in the month
+                    // the starting point for this process is the first of the month set above
+                    $datearray['mday'] = 1 ;
+                    // turn $datearray as it is now back into a timestamp
+                    $offsetchangetimestamp = mktime(
+                        $datearray['hours'],
+                        $datearray['minutes'],
+                        $datearray['seconds'],
+                        $datearray['mon'],
+                        $datearray['mday'],
+                        $datearray['year']
+                            );
+                    if ($rrule['BYDAY'][0] > 0) {
+                        // to find Xth WKDAY in month, we find last WKDAY in month before
+                        // we do that by finding first WKDAY in this month and going back one week
+                        // then we add X weeks (below)
+                        $offsetchangetimestamp = strtotime($daynames[$rrule['BYDAY']['DAY']],$offsetchangetimestamp);
+                        $offsetchangetimestamp = strtotime("-1 week",$offsetchangetimestamp);
+                    } else {
+                        // to find Xth WKDAY before the end of the month, we find the first WKDAY in the following month
+                        // we do that by going forward one month and going to WKDAY there
+                        // then we subtract X weeks (below)
+                        $offsetchangetimestamp = strtotime("+1 month",$offsetchangetimestamp);
+                        $offsetchangetimestamp = strtotime($daynames[$rrule['BYDAY']['DAY']],$offsetchangetimestamp);
+                    }
+                    // now move forward or back the appropriate number of weeks, into the month we want
+                    $offsetchangetimestamp = strtotime($rrule['BYDAY'][0] . " week",$offsetchangetimestamp);
+                    $datearray = getdate($offsetchangetimestamp);
+                }
+                // convert the date parts back into a timestamp, setting the time parts according to the
+                // original time data which we stored
+                $offsetchangetimestamp = mktime(
+                    $changetime['hours'],
+                    $changetime['minutes'],
+                    $changetime['seconds'] + $diff,
+                    $datearray['mon'],
+                    $datearray['mday'],
+                    $datearray['year']
+                        );
+                // add this change to the array of changes
+                $tzdates[] = array(
+                    'timestamp' => $offsetchangetimestamp,
+                    'tzbefore'  => $tzbefore,
+                    'tzafter'   => $tzafter
+                    );
+                // update counters (timestamp and count)
+                $offsetchangetimestamp = strtotime("+" . (( isset( $rrule['INTERVAL'] ) && ( $rrule['INTERVAL'] != 0 )) ? $rrule['INTERVAL'] : 1 ) . " year",$offsetchangetimestamp);
+                $count += 1 ;
+            }
+        }
+    }
+    // generate dates according to RDATE lines
+    while ($rdates = $vtzc->getProperty('rdate')) {
+        if ( is_array($rdates) ) {
+
+            foreach ( $rdates as $rdate ) {
+                // convert the explicit change date to a timestamp
+                $offsetchangetimestamp = mktime(
+                        $rdate['hour'],
+                        $rdate['min'],
+                        $rdate['sec'] + $diff,
+                        $rdate['month'],
+                        $rdate['day'],
+                        $rdate['year']
+                        ) ;
+                // add this change to the array of changes
+                $tzdates[] = array(
+                    'timestamp' => $offsetchangetimestamp,
+                    'tzbefore'  => $tzbefore,
+                    'tzafter'   => $tzafter
+                    );
+            }
+        }
+    }
+    return $tzdates;
+}
+?>
diff --git a/www/plugins/icalendar/modeles/prochainement.html b/www/plugins/icalendar/modeles/prochainement.html
new file mode 100644 (file)
index 0000000..16ecf89
--- /dev/null
@@ -0,0 +1,43 @@
+<B_calendrier>\r
+#ANCRE_PAGINATION\r
+<BOUCLE_calendrier(DATA)\r
+ {source ics, #ENV{ics}}\r
+ {par dtstart/str}{pagination 10}\r
+ >\r
+\r
+  [(#VALEUR{dtend/str}|strtotime|>={#DATE|strtotime}|oui)\r
+       [(#VALEUR{dtstart/value/hour}|>{00}|?{\r
+       [(#VALEUR{dtstart/value/month}|=={#VALEUR{dtend/value/month}}|?{\r
+       [(#VALEUR{dtstart/value/day}|=={#VALEUR{dtend/value/day}}|?{\r
+       <dt>\r
+        [(#SET{date_debut,#VALEUR{dtstart/str}})]\r
+    [(#SET{date_fin,#VALEUR{dtend/str}})]\r
+    Le [(#GET{date_debut}|jour) ][(#GET{date_debut}|nom_mois) ][(#GET{date_debut}|annee) ]<span> de [(#GET{date_debut}|affdate{'G'})]:[(#GET{date_debut}|minutes )] à [(#GET{date_fin}|heures)]:[(#GET{date_fin}|minutes )]</span>\r
+  </dt>\r
+  <dd>[(#VALEUR{summary/value})<p>#VALEUR{description/0/value}]</p></dd>\r
+  ,\r
+       <dt>\r
+        [(#SET{date_debut,#VALEUR{dtstart/str}})]\r
+    [(#SET{date_fin,#VALEUR{dtend/str}})]\r
+   Du [(#GET{date_debut}|jour) ][(#GET{date_debut}|nom_mois) ][(#GET{date_debut}|annee) ]\r
+     au [(#GET{date_fin}|jour) ][(#GET{date_fin}|nom_mois) ][(#GET{date_fin}|annee)]<span> de [(#GET{date_debut}|affdate{'G'})]:[(#GET{date_debut}|minutes )] à [(#GET{date_fin}|affdate{'G'})]:[(#GET{date_fin}|minutes)]</span>\r
+  </dt>\r
+  <dd>[(#VALEUR{summary/value})<p>#VALEUR{description/0/value}]</p></dd>\r
+  ,\r
+       <dt>\r
+        [(#SET{date_debut,#VALEUR{dtstart/str}})])\r
+    Du [(#GET{date_debut}|jour) ][(#GET{date_debut}|nom_mois)]\r
+    [(#SET{date_fin,#VALEUR{dtend/str}})]\r
+    [ au (#GET{date_fin}|jour) ][(#GET{date_fin}|nom_mois) ][(#GET{date_fin}|annee)] <span> [(#GET{date_debut}|heures)]:[(#GET{date_debut}|minutes )]</span>\r
+  </dt>\r
+  <dd> [(#VALEUR{summary/value})<p>#VALEUR{description/0/value}]</p></dd>})]  })]\r
+  ,\r
+  <dt>[(#SET{date_debut,#VALEUR{dtstart/str}})]\r
+    Le [(#GET{date_debut}|jour) ][(#GET{date_debut}|nom_mois) ][(#GET{date_debut}|annee)]\r
+  </dt>\r
+  <dd>[(#VALEUR{summary/value})<p>#VALEUR{description/0/value}]</p></dd>})]\r
+  ]\r
+   \r
+</BOUCLE_calendrier>\r
+#PAGINATION{precedent_suivant}\r
+</B_calendrier>\r
diff --git a/www/plugins/icalendar/paquet.xml b/www/plugins/icalendar/paquet.xml
new file mode 100644 (file)
index 0000000..f6e318f
--- /dev/null
@@ -0,0 +1,20 @@
+<paquet
+       prefix="icalendar"
+       categorie="date"
+       version="0.4.1"
+       etat="dev"
+       compatibilite="[2.1.0;3.0.*]"
+       logo="icalendar.png"
+       documentation="http://contrib.spip.net/Plugin-iCalendar"
+>      
+
+       <nom>iCalendar</nom>
+       <!-- Faire des boucles iCalendar (format ics) -->
+
+       <auteur>Fil</auteur>
+
+       <copyright>2010-2011</copyright>
+
+       <licence lien="http://www.gnu.org/licenses/gpl-3.0.html">GPL 3</licence>
+       <utilise nom="http" compatibilite="[0.1.0;0.1.*]" />
+</paquet>
diff --git a/www/plugins/icalendar/plugin.xml b/www/plugins/icalendar/plugin.xml
new file mode 100644 (file)
index 0000000..201b581
--- /dev/null
@@ -0,0 +1,16 @@
+<plugin>
+       <nom>iCalendar</nom>
+       <auteur>Fil</auteur>
+       <licence>GNU/GPL - (c) 2010-2011</licence>
+       <version>0.4.0</version>
+       <etat>dev</etat>
+       <description>Faire des boucles iCalendar (format ics)</description>
+       <prefix>icalendar</prefix>
+       <chemin dir='' />
+       <icon>icalendar.png</icon>
+       <lien>http://contrib.spip.net/Plugin-iCalendar</lien>
+       <categorie>date</categorie>
+       <necessite id="SPIP" version="[2.1.0;3.0.99]" />
+       <utilise id="iterateurs" version="[0.6.1;[" />
+       <utilise id="http" version="[0.1.0;0.1.99]" />
+</plugin>
\ No newline at end of file
diff --git a/www/plugins/icalendar/svn.revision b/www/plugins/icalendar/svn.revision
new file mode 100644 (file)
index 0000000..32e1b76
--- /dev/null
@@ -0,0 +1,10 @@
+<svn_revision>
+<text_version>
+Origine: file:///home/svn/repository/spip-zone/_plugins_/icalendar
+Revision: 87001
+Dernier commit: 2015-01-06 21:00:03 +0100 
+</text_version>
+<origine>file:///home/svn/repository/spip-zone/_plugins_/icalendar</origine>
+<revision>87001</revision>
+<commit>2015-01-06 21:00:03 +0100 </commit>
+</svn_revision>
\ No newline at end of file
diff --git a/www/plugins/import_ics/action/supprimer_almanach.php b/www/plugins/import_ics/action/supprimer_almanach.php
new file mode 100644 (file)
index 0000000..e66934f
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+#tout est pompé du tutoriel de marcimat "chat"
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+function action_supprimer_almanach_dist() {
+       $securiser_action = charger_fonction('securiser_action', 'inc');
+       $arg = $securiser_action();
+
+       if (!preg_match(",^(\d+)$,", $arg, $r)) {
+                spip_log("action_supprimer_almanach_dist $arg pas compris");
+       } else {
+               action_supprimer_almanach_post($r[1]);
+       }
+}
+
+function action_supprimer_almanach_post($id_almanach) {
+       //on supprime l'almanach
+       sql_delete("spip_almanachs", "id_almanach=" . intval($id_almanach));
+
+       include_spip('inc/invalideur');
+       suivre_invalideur("id='id_almanach/$id_almanach'");
+}
+?>
diff --git a/www/plugins/import_ics/action/supprimer_evenements_almanach.php b/www/plugins/import_ics/action/supprimer_evenements_almanach.php
new file mode 100644 (file)
index 0000000..89710c0
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+#tout est pompé du tutoriel de marcimat "chat"
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+function action_supprimer_evenements_almanach_dist() {
+       $securiser_action = charger_fonction('securiser_action', 'inc');
+       $arg = $securiser_action();
+
+       if (!preg_match(",^(\d+)$,", $arg, $r)) {
+                spip_log("action_supprimer_evenements_almanach_dist $arg pas compris");
+       } else {
+               action_supprimer_evenements_almanach_post($r[1]);
+       }
+}
+
+function action_supprimer_evenements_almanach_post($id_almanach) {
+       //recuperer tous les evenemments lies à l'almanach en cours
+       $all = sql_allfetsel('id_objet', 'spip_almanachs_liens','id_almanach='.intval($id_almanach));
+       //pour chacun d'entre eux supprimer l'entree correspondante dans la table evenement
+       foreach ($all as $id_evenement_array) {
+               $id_evenement=$id_evenement_array['id_objet'];
+               sql_delete("spip_evenements","id_evenement=".intval($id_evenement));
+       }
+       //on supprime les entrees de la table de liaison
+       sql_delete("spip_almanachs_liens","id_almanach=".intval($id_almanach));
+       
+       include_spip('inc/invalideur');
+       suivre_invalideur(1);
+}
+?>
diff --git a/www/plugins/import_ics/action/synchro_almanach.php b/www/plugins/import_ics/action/synchro_almanach.php
new file mode 100644 (file)
index 0000000..c0cd720
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+/***************************************************************************\
+ *  SPIP, Systeme de publication pour l'internet                           *
+ *                                                                         *
+ *  Copyright (c) 2001-2013                                                *
+ *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
+ *                                                                         *
+ *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
+ *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
+\***************************************************************************/
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+include_spip('lib/iCalcreator.class'); /*pour la librairie icalcreator incluse dans le plugin icalendar*/
+
+function action_synchro_almanach_dist() {
+
+//vérification de l'auteur en cours//
+$securiser_action = charger_fonction('securiser_action', 'inc');
+$arg = $securiser_action();
+
+       if (!preg_match(",^(\d+)$,", $arg, $r)) {
+                spip_log("action_synchro_almanach_dist $arg pas compris");
+                return;
+       }
+
+       $id_almanach = $r[1];
+
+}
+
+?>
\ No newline at end of file
diff --git a/www/plugins/import_ics/base/import_ics.php b/www/plugins/import_ics/base/import_ics.php
new file mode 100644 (file)
index 0000000..ef10101
--- /dev/null
@@ -0,0 +1,119 @@
+<?php
+/**
+ * Déclarations relatives à la base de données
+ *
+ * @plugin     Import_ics
+ * @copyright  2013
+ * @author     Amaury
+ * @licence    GNU/GPL
+ * @package    SPIP\Import_ics\Pipelines
+ */
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+/**
+ * Déclaration des alias de tables et filtres automatiques de champs
+ *
+ * @pipeline declarer_tables_interfaces
+ * @param array $interfaces
+ *     Déclarations d'interface pour le compilateur
+ * @return array
+ *     Déclarations d'interface pour le compilateur
+ */
+function import_ics_declarer_tables_interfaces($interfaces) {
+
+       $interfaces['table_des_tables']['almanachs'] = 'almanachs';
+
+       return $interfaces;
+}
+
+
+/**
+ * Déclaration des objets éditoriaux
+ *
+ * @pipeline declarer_tables_objets_sql
+ * @param array $tables
+ *     Description des tables
+ * @return array
+ *     Description complétée des tables
+ */
+function import_ics_declarer_tables_objets_sql($tables) {
+
+       $tables['spip_almanachs'] = array(
+               'type' => 'almanach',
+               'principale' => "oui",
+               'field'=> array(
+                       "id_almanach"        => "bigint(21) NOT NULL",
+                       "titre"              => "text NOT NULL DEFAULT ''",
+                       "url"                => "text NOT NULL DEFAULT ''",
+                       "id_article"         => "bigint(21) NOT NULL DEFAULT 0",
+                       "id_mot"             => "bigint(21) NOT NULL DEFAULT 0",
+                       "id_ressource"       => "bigint(21) NOT NULL DEFAULT 0",
+                       "date"               => "datetime NOT NULL DEFAULT '0000-00-00 00:00:00'", 
+                       "statut"             => "varchar(20) DEFAULT '0' NOT NULL", 
+                       "maj"                => "TIMESTAMP",
+                       "statut_maj"         => "varchar(20) DEFAULT '0' NOT NULL"
+               ),
+               'key' => array(
+                       "PRIMARY KEY"        => "id_almanach",
+                       "KEY statut"         => "statut", 
+               ),
+               'titre' => "titre AS titre, '' AS lang",
+               'date' => "date",
+               'champs_editables'  => array('titre', 'url', 'id_article', 'id_mot', 'id_ressource'),
+               'champs_versionnes' => array('titre', 'url', 'id_article', 'id_mot', 'id_ressource'),
+               'rechercher_champs' => array(),
+               'tables_jointures'  => array('spip_almanachs_liens'),
+               'statut_textes_instituer' => array(
+                       'prepa'    => 'texte_statut_en_cours_redaction',
+                       'prop'     => 'texte_statut_propose_evaluation',
+                       'publie'   => 'texte_statut_publie',
+                       'refuse'   => 'texte_statut_refuse',
+                       'poubelle' => 'texte_statut_poubelle',
+               ),
+               'statut'=> array(
+                       array(
+                               'champ'     => 'statut',
+                               'publie'    => 'publie',
+                               'previsu'   => 'publie,prop,prepa',
+                               'post_date' => 'date', 
+                               'exception' => array('statut','tout')
+                       )
+               ),
+               'texte_changer_statut' => 'almanach:texte_changer_statut_almanach', 
+               
+
+       );
+
+       return $tables;
+}
+
+
+/**
+ * Déclaration des tables secondaires (liaisons)
+ *
+ * @pipeline declarer_tables_auxiliaires
+ * @param array $tables
+ *     Description des tables
+ * @return array
+ *     Description complétée des tables
+ */
+function import_ics_declarer_tables_auxiliaires($tables) {
+
+       $tables['spip_almanachs_liens'] = array(
+               'field' => array(
+                       "id_almanach"        => "bigint(21) DEFAULT '0' NOT NULL",
+                       "id_objet"           => "bigint(21) DEFAULT '0' NOT NULL",
+                       "objet"              => "VARCHAR(25) DEFAULT '' NOT NULL",
+                       "vu"                 => "VARCHAR(6) DEFAULT 'non' NOT NULL"
+               ),
+               'key' => array(
+                       "PRIMARY KEY"        => "id_almanach,id_objet,objet",
+                       "KEY id_almanach"    => "id_almanach"
+               )
+       );
+
+       return $tables;
+}
+
+
+?>
\ No newline at end of file
diff --git a/www/plugins/import_ics/fabrique_diff.diff b/www/plugins/import_ics/fabrique_diff.diff
new file mode 100644 (file)
index 0000000..6f75491
--- /dev/null
@@ -0,0 +1,10 @@
+diff -r -x . -x .. -x fabrique_diff.diff -x fabrique_import_ics.php ../plugins/fabrique_auto/.backup/import_ics/paquet.xml ../plugins/fabrique_auto/import_ics/paquet.xml
+12c12
+<              Paquet genere le 2013-04-15 14:07:29
+---
+>              Paquet genere le 2013-04-15 14:09:23
+Binary files ../plugins/fabrique_auto/.backup/import_ics/prive/themes/spip/images/almanach-24.png and ../plugins/fabrique_auto/import_ics/prive/themes/spip/images/almanach-24.png differ
+Binary files ../plugins/fabrique_auto/.backup/import_ics/prive/themes/spip/images/almanach-add-24.png and ../plugins/fabrique_auto/import_ics/prive/themes/spip/images/almanach-add-24.png differ
+Binary files ../plugins/fabrique_auto/.backup/import_ics/prive/themes/spip/images/almanach-del-24.png and ../plugins/fabrique_auto/import_ics/prive/themes/spip/images/almanach-del-24.png differ
+Binary files ../plugins/fabrique_auto/.backup/import_ics/prive/themes/spip/images/almanach-edit-24.png and ../plugins/fabrique_auto/import_ics/prive/themes/spip/images/almanach-edit-24.png differ
+Binary files ../plugins/fabrique_auto/.backup/import_ics/prive/themes/spip/images/almanach-new-24.png and ../plugins/fabrique_auto/import_ics/prive/themes/spip/images/almanach-new-24.png differ
\ No newline at end of file
diff --git a/www/plugins/import_ics/fabrique_import_ics.php b/www/plugins/import_ics/fabrique_import_ics.php
new file mode 100644 (file)
index 0000000..1c2897d
--- /dev/null
@@ -0,0 +1,224 @@
+<?php
+
+/**
+ *  Fichier généré par la Fabrique de plugin v5
+ *   le 2013-04-15 14:09:23
+ *
+ *  Ce fichier de sauvegarde peut servir à recréer
+ *  votre plugin avec le plugin «Fabrique» qui a servi à le créer.
+ *
+ *  Bien évidemment, les modifications apportées ultérieurement
+ *  par vos soins dans le code de ce plugin généré
+ *  NE SERONT PAS connues du plugin «Fabrique» et ne pourront pas
+ *  être recréées par lui !
+ *
+ *  La «Fabrique» ne pourra que régénerer le code de base du plugin
+ *  avec les informations dont il dispose.
+ *
+**/
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+$data = array (
+  'fabrique' => 
+  array (
+    'version' => 5,
+  ),
+  'paquet' => 
+  array (
+    'nom' => 'Import_ics',
+    'slogan' => 'Importez vos événements',
+    'description' => 'Importez les événements de sites distants dans votre base de données d\'événements SPIP',
+    'prefixe' => 'import_ics',
+    'version' => '1.0.0',
+    'auteur' => 'Amaury',
+    'auteur_lien' => '',
+    'licence' => 'GNU/GPL',
+    'categorie' => 'date',
+    'etat' => 'dev',
+    'compatibilite' => '[3.0.7;3.0.*]',
+    'documentation' => '',
+    'administrations' => 'on',
+    'schema' => '1.0.0',
+    'formulaire_config' => '',
+    'formulaire_config_titre' => '',
+    'inserer' => 
+    array (
+      'paquet' => '<necessite nom="agenda"/>\r
+<necessite nom="icalendar"/>\r
+<necessite nom="cextras" compatibilite="[3.0.5;[" />\r
+<necessite nom="seminaire" />\r
+\r
+',
+      'administrations' => 
+      array (
+        'maj' => '',
+        'desinstallation' => '',
+        'fin' => '',
+      ),
+      'base' => 
+      array (
+        'tables' => 
+        array (
+          'fin' => '',
+        ),
+      ),
+    ),
+    'scripts' => 
+    array (
+      'pre_copie' => '',
+      'post_creation' => '',
+    ),
+    'exemples' => '',
+  ),
+  'objets' => 
+  array (
+    0 => 
+    array (
+      'nom' => 'Almanachs',
+      'nom_singulier' => 'Almanach',
+      'genre' => 'masculin',
+      'logo_variantes' => 'on',
+      'table' => 'spip_almanachs',
+      'cle_primaire' => 'id_almanach',
+      'cle_primaire_sql' => 'bigint(21) NOT NULL',
+      'table_type' => 'almanach',
+      'champs' => 
+      array (
+        0 => 
+        array (
+          'nom' => 'Titre',
+          'champ' => 'titre',
+          'sql' => 'text NOT NULL DEFAULT \'\'',
+          'caracteristiques' => 
+          array (
+            0 => 'editable',
+            1 => 'versionne',
+            2 => 'obligatoire',
+          ),
+          'recherche' => '',
+          'saisie' => 'input',
+          'explication' => 'Titre de l\'almanach',
+          'saisie_options' => '',
+        ),
+        1 => 
+        array (
+          'nom' => 'URL',
+          'champ' => 'url',
+          'sql' => 'text NOT NULL DEFAULT \'\'',
+          'caracteristiques' => 
+          array (
+            0 => 'editable',
+            1 => 'versionne',
+            2 => 'obligatoire',
+          ),
+          'recherche' => '',
+          'saisie' => 'input',
+          'explication' => 'URL d\'origine du calendrier',
+          'saisie_options' => '',
+        ),
+        2 => 
+        array (
+          'nom' => 'Article d\'accueil de l\'almanach',
+          'champ' => 'id_article',
+          'sql' => 'bigint(21) NOT NULL DEFAULT 0',
+          'caracteristiques' => 
+          array (
+            0 => 'editable',
+            1 => 'versionne',
+            2 => 'obligatoire',
+          ),
+          'recherche' => '',
+          'saisie' => 'selecteur_article',
+          'explication' => 'Choisissez un article qui va recevoir les événements importés',
+          'saisie_options' => '',
+        ),
+      ),
+      'champ_titre' => 'titre',
+      'champ_date' => 'date',
+      'statut' => 'on',
+      'chaines' => 
+      array (
+        'titre_objets' => 'Almanachs',
+        'titre_objet' => 'Almanach',
+        'info_aucun_objet' => 'Aucun almanach',
+        'info_1_objet' => 'Un almanach',
+        'info_nb_objets' => '@nb@ almanachs',
+        'icone_creer_objet' => 'Créer un almanach',
+        'icone_modifier_objet' => 'Modifier cet almanach',
+        'titre_logo_objet' => 'Logo de cet almanach',
+        'titre_langue_objet' => 'Langue de cet almanach',
+        'titre_objets_rubrique' => 'Almanachs de la rubrique',
+        'info_objets_auteur' => 'Les almanachs de cet auteur',
+        'retirer_lien_objet' => 'Retirer cet almanach',
+        'retirer_tous_liens_objets' => 'Retirer tous les almanachs',
+        'ajouter_lien_objet' => 'Ajouter cet almanach',
+        'texte_ajouter_objet' => 'Ajouter un almanach',
+        'texte_creer_associer_objet' => 'Créer et associer un almanach',
+        'texte_changer_statut_objet' => 'Cet almanach est :',
+      ),
+      'table_liens' => 'on',
+      'roles' => '',
+      'auteurs_liens' => '',
+      'vue_auteurs_liens' => '',
+      'autorisations' => 
+      array (
+        'objet_creer' => '',
+        'objet_voir' => '',
+        'objet_modifier' => '',
+        'objet_supprimer' => '',
+        'associerobjet' => '',
+      ),
+      'boutons' => 
+      array (
+        0 => 'menu_edition',
+        1 => 'outils_rapides',
+      ),
+    ),
+  ),
+  'images' => 
+  array (
+    'paquet' => 
+    array (
+      'logo' => 
+      array (
+        0 => 
+        array (
+          'extension' => 'png',
+          'contenu' => 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEYklEQVRYhbWWy48UVRTGf+fcW9UjL3FswAwsMGF8xYVxAQtXxsiCuBU2OjPMX+D4P2hciY+4ImlmuoHEhQtWRiVhMQsTMjFgjOuBIAExxjAz/ajuusdFVT9qHj09DJykU/VV3T73u985X90rS0tL/tc/7l5bvH3nTBqMpxVOhZcnXlj+8N03J4HOVuM8MLZ4+86Z77/8hObaylMjMLZ3P+c+/eq4me0RkcdbEjCzsTQNACz/9QBEQeTJZzYDC7z2yn7yvPGw4d7MNBPe0LgEz+1BoujJ52+3oVEHDAPMTGXIgnz/VnD79vLfoTLSewIKBMBGwOTXg4/+yd9uHzoIXFwCYNyM8UbCoWbCL5fmOdxocmi1zpFGk+uVSxzZBJfrDcpmKBCVSr0FbBcDChiqGR8HIIonEHUSSq0G1umgqow11yg11gghRdX1sDcDMcaAMZdNPUz6bsjNmzcPf3F58eEPX89xf6XBwwN7e3KWgOp7Z/n91ZNYu42IICKYGWYBEe3hYAaRR3yEddq4NCWOHF43NrVT4a0TL/12+tTkqYICvtPmxsUKH83MYGlKyQm35r7hp2/nqK8+7hc+G76JxpJNZgZYRniTVecWffv9kyf29AkEw5nhWw3c6gohTXGxx1pNwLh7/+EzsWifgBhOs/rHTgliRE4Ry5b7rCxasKGqwznlypXLmBmlSPNG0mdmUe29EcldIExPzzA1NcX01AzBAoZtsGi5lfDzfJVyK+HFenMoHm8kjFtGo5unG34QqGre4YYFw8iuGyyqihNBRXDiQMCJ2xqrFeboa9IjkInmVPHeE0URwTl85LMSGHjL5HokguVETgN/T39MOgLulsSv23AHvoSCOkVVqVar1GpVatUFRAUs9Cx6rB2YaLaZaAeuz9eYGBEfawduXKzgO+2NCnShqgDG7OwsIaQ456ktfg5WtKil2Tu/toJbXcXSzgjYZf83g7ChBLkGqhgQzAjBcA5CCJhQsKjDcE7wIsROcOgIWHFkeXJvb9KEkjWXU8XlTSci6DqLhhBwzhFFnqtXr5Cm6bZYNSOh6hi0YYEAgIkwv7AAwFjss64tWHS6N0GtVmNqanS8sLDQy9etu4fiJ332/PkCocr1z/L+GLBo/gsh7Aib9e1cUMC54sNiX2QbzKBFMzkdURTtCHvv89JmzQ7gRSRMHn3+z7NzF97opCHfychkF6EUe0ALFg0hoDmhnWDVLM/6Hkg+eOf1c61Wq2xmBSlCCPF31279mJWgb9FeTefnmZ09PzKuVCp5nr71vYjUoyi6G0XRg/UE0jQ92C/FgEXNEDPS/H5UbHme9T3Q6Z7bhx2hBi2qqgj07DoyFkFlkyYcNboWNctOO+rcjrBtssBtCQyz6G6im3dr/+UxzKK7iW7eoQqISDha3nfv7NyFYwWL7iZE8E45Wt53T0SCLC0tbTnWzA4kSXI8SZLx9Q7ZHQcJcRz/G8fx8nYK1OM4Xo7j+P7TJgAkIlL/HzTWviqSC0lGAAAAAElFTkSuQmCC',
+        ),
+      ),
+    ),
+    'objets' => 
+    array (
+      0 => 
+      array (
+        'logo' => 
+        array (
+          0 => 
+          array (
+            'extension' => 'png',
+            'contenu' => 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAGMElEQVRYhb2WX4xVVxnFf9/e+/y7w9xpaSv9gyR9oiQ2JkVNgRiNoTRFrCFS+tCkPlIfjPFNEx/0yTcTTTT0xWiNMXYKdRjslA5UYy0YmZJYTG0Q+4ciZQYc5t65czn3nLP39mGfmXNBbGzrdCfnJHfdfc639lrr22fLzMyMWffPZybWX/jlTrzjIxmiOH/nY8/N3rXnywZI18+O72THj8FZQEBWqbCvb0qz/tg3d87etSc13vsUnUBVwCs/BNGrS8Bb2PwN0Ane+9R47xVxC/qXYHR9ILCaw9tQK27hvVcGACPw7NeaSatqAXDyV7Dp06E0wOPJBN//3h24es5q1hdAAd9+9l2+zvlAIC47ZNzB0/ufRABfUxVRiCi8d/i6Qz4UhuCBvU/sIy47jQLOVxjAiOfeR5/A28BYafjr+JN8Yu++0CAfEhMNp3+9H1PXXCFgXWColKAczQN1R4oD+T9gSkKN4ZoGoHIeAbRSKFWbBCgF6n/AtAatDEaD9ddiWgOueVYrhdQ1VwiUPhAQpYki8HUKlQGlDXEE7j0wE0Ec3UQrBqcbC+LoJpIIgtggJtSQumZjga0VMIbTB36Or+URrTFxwqsHn8Jbe0NMUESmzeH5PTy3H+r3IhIIqoMHKasuHocohTYGqWsOWeBWWu++PV9tQmjg1IFfsHnP47jqxpgIJBEc/glsvx/ckNxH/wSfemQ3gzIQEw2nDjxVWzCcAetRgBJBHCsERADvQ4j+GyZBbufAmSbA1JhyYZ748KwSQV2fAetrBUQw0nirJXhmVFjR8spEaYw0Sddh88AnDXmvA6YlFPESukBqAtY7QOoQuqCA0YbINCE0BrJ4LWuSoXDVWBJBVROIDYgCSesVU/9W4T/nw6U06ChiYQBSVkC0rEATwpOHxvHOAorYjPH0+Z0c+BErRwVRtfKTz1NUYTdLzBgWOHa4madUUPLExBHKqgPiUVqTxhEzZy6Ri2osKFdC6Nm6+xGcDfJmMYz/ALZvuZbA9An47Fce5GoRZDYRyMQLfOZLD1AMwmq1gROHprjviw8iHiITwvrTn/2G6o0LPPyxAdBq2lBT+1OHUAip9Q78ULhUjYkNl/fBssX8Cp1FKAqLKMFoRV50mZsvWOwt8c5ch1NnLyDlKPu23sPbrxxrCCwnElEhULW3ps6CGwrXchaMhIB5oD+A3FbkRYWzFZMvv87pN2fpLymyud+xrp2ydjRjy8Z1bPr4bSRpQl7YxoKq1tcYjTF1WgGj6jZLr+1vBCIFcQRLAygrj/OKynms9Zx87QyHvrM3mOqhO6jo5RWLeUln4Ln45xex9rpvQRBA89LkJM45BEUShXBNT/5nCH//2z9Q2C7zvT7WCRbN8alnGJSWdTffAkB/YOkOSvqFpZ9XLJae3uV3yeKIjh1SoKy3RSXCFx7eRU2OSIFSf2T7Q9sY2l84OvUyn3toG4sFXOqWFJXnpamDfPLzu3jz4r+4fPYtrINuXtIbVPQHFb3S0+/10L1Zsnabyl5sCNihDdzTrNYqKN0iOWDrrVibgDmgLCxVUVBZT2kdZVnwztwCmzbcRjcvWSrsSvGlvIQr50jTlFaWUZbhhQrALlsggiaES0tYrcfV23R91e2qAYXgvV058TjreHuuw51rR1nMq2uK+4uv09KQpSlJmlFVQweSstZca0MMWN3IbaLoBliYp7QQRwYrHiWa0jrm+zn33n07l7t5KN6/ip39O61IkWQZSZrRaiVUVTncBcvHVThyZHrlmyoiiFK8cGQafz02fZQrvZylvGJgLcZoxqdeZPe2zSz0SxZL6M5fopp7g5FWStrKSNOULEtJ05SiuMGRbMeOB3g/o1t4Or2CvKz4x2yHv8yc5f6NG7i8sMT8ub/hrnYYGVlD1mrRSjOyLKszMIK1Qxn4oMfwdizcMqJZ6Oecmevyrd1buPvWlNnXjiPFEu12m9H2GO3RNqPtNmPtNjePjZEkMW75cywibtft7vja7z6/tbSOITfec4iEm0XzaHqOjXGfiVcrHMGicCQPL/PeD13hYJqmaQ9AZmZm1hRFsaEoiludc+YDivG+R5Ik83Ecv2WAPI7jc1EUzXnv1UdFQEQKEen9GxBUOLHjDYGLAAAAAElFTkSuQmCC',
+          ),
+          32 => 
+          array (
+            'extension' => 'png',
+            'contenu' => 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAGMElEQVRYhb2WX4xVVxnFf9/e+/y7w9xpaSv9gyR9oiQ2JkVNgRiNoTRFrCFS+tCkPlIfjPFNEx/0yTcTTTT0xWiNMXYKdRjslA5UYy0YmZJYTG0Q+4ciZQYc5t65czn3nLP39mGfmXNBbGzrdCfnJHfdfc639lrr22fLzMyMWffPZybWX/jlTrzjIxmiOH/nY8/N3rXnywZI18+O72THj8FZQEBWqbCvb0qz/tg3d87etSc13vsUnUBVwCs/BNGrS8Bb2PwN0Ane+9R47xVxC/qXYHR9ILCaw9tQK27hvVcGACPw7NeaSatqAXDyV7Dp06E0wOPJBN//3h24es5q1hdAAd9+9l2+zvlAIC47ZNzB0/ufRABfUxVRiCi8d/i6Qz4UhuCBvU/sIy47jQLOVxjAiOfeR5/A28BYafjr+JN8Yu++0CAfEhMNp3+9H1PXXCFgXWColKAczQN1R4oD+T9gSkKN4ZoGoHIeAbRSKFWbBCgF6n/AtAatDEaD9ddiWgOueVYrhdQ1VwiUPhAQpYki8HUKlQGlDXEE7j0wE0Ec3UQrBqcbC+LoJpIIgtggJtSQumZjga0VMIbTB36Or+URrTFxwqsHn8Jbe0NMUESmzeH5PTy3H+r3IhIIqoMHKasuHocohTYGqWsOWeBWWu++PV9tQmjg1IFfsHnP47jqxpgIJBEc/glsvx/ckNxH/wSfemQ3gzIQEw2nDjxVWzCcAetRgBJBHCsERADvQ4j+GyZBbufAmSbA1JhyYZ748KwSQV2fAetrBUQw0nirJXhmVFjR8spEaYw0Sddh88AnDXmvA6YlFPESukBqAtY7QOoQuqCA0YbINCE0BrJ4LWuSoXDVWBJBVROIDYgCSesVU/9W4T/nw6U06ChiYQBSVkC0rEATwpOHxvHOAorYjPH0+Z0c+BErRwVRtfKTz1NUYTdLzBgWOHa4madUUPLExBHKqgPiUVqTxhEzZy6Ri2osKFdC6Nm6+xGcDfJmMYz/ALZvuZbA9An47Fce5GoRZDYRyMQLfOZLD1AMwmq1gROHprjviw8iHiITwvrTn/2G6o0LPPyxAdBq2lBT+1OHUAip9Q78ULhUjYkNl/fBssX8Cp1FKAqLKMFoRV50mZsvWOwt8c5ch1NnLyDlKPu23sPbrxxrCCwnElEhULW3ps6CGwrXchaMhIB5oD+A3FbkRYWzFZMvv87pN2fpLymyud+xrp2ydjRjy8Z1bPr4bSRpQl7YxoKq1tcYjTF1WgGj6jZLr+1vBCIFcQRLAygrj/OKynms9Zx87QyHvrM3mOqhO6jo5RWLeUln4Ln45xex9rpvQRBA89LkJM45BEUShXBNT/5nCH//2z9Q2C7zvT7WCRbN8alnGJSWdTffAkB/YOkOSvqFpZ9XLJae3uV3yeKIjh1SoKy3RSXCFx7eRU2OSIFSf2T7Q9sY2l84OvUyn3toG4sFXOqWFJXnpamDfPLzu3jz4r+4fPYtrINuXtIbVPQHFb3S0+/10L1Zsnabyl5sCNihDdzTrNYqKN0iOWDrrVibgDmgLCxVUVBZT2kdZVnwztwCmzbcRjcvWSrsSvGlvIQr50jTlFaWUZbhhQrALlsggiaES0tYrcfV23R91e2qAYXgvV058TjreHuuw51rR1nMq2uK+4uv09KQpSlJmlFVQweSstZca0MMWN3IbaLoBliYp7QQRwYrHiWa0jrm+zn33n07l7t5KN6/ip39O61IkWQZSZrRaiVUVTncBcvHVThyZHrlmyoiiFK8cGQafz02fZQrvZylvGJgLcZoxqdeZPe2zSz0SxZL6M5fopp7g5FWStrKSNOULEtJ05SiuMGRbMeOB3g/o1t4Or2CvKz4x2yHv8yc5f6NG7i8sMT8ub/hrnYYGVlD1mrRSjOyLKszMIK1Qxn4oMfwdizcMqJZ6Oecmevyrd1buPvWlNnXjiPFEu12m9H2GO3RNqPtNmPtNjePjZEkMW75cywibtft7vja7z6/tbSOITfec4iEm0XzaHqOjXGfiVcrHMGicCQPL/PeD13hYJqmaQ9AZmZm1hRFsaEoiludc+YDivG+R5Ik83Ecv2WAPI7jc1EUzXnv1UdFQEQKEen9GxBUOLHjDYGLAAAAAElFTkSuQmCC',
+          ),
+          16 => 
+          array (
+            'extension' => 'png',
+            'contenu' => 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACV0lEQVQ4jZWTzW+MURTGf/e+9513SlJsfFdSLNiRUDOC6VdQpMT4isSysbD1T0gXVhWJxMaCoBRRRVuKmU5b0URorDSpSDBUlbbz8d57LGYilSD1bJ6cxXnOc06eowYun5aar5cADSjmBgEcg4tOoOT8cqGhDZz7v36toecUBg/IvoA37aDm4kJAHKxLggfqyNUxuXC4ircF0F5Z4x9wDsTC6gi0XHuH8fITBFQxT2Co/QZKa5Sa7UKhKKmKCDYsUHMgSQB4+Ql0wRbxgKgG39PUHzqA73nUJffjG0NDspmoqWRnch9Rv5L6Y0kiunTygi1icqFFAUZDJIgw2HmfIIgydK+baFDB864Mz6YTpK+AdeDdfsympgQayIUWnQsdCvABoz1q9+wiahbQ0NRI1CykfncMDdTFSlNrmxP4irKAw+StQ5cdGD/g+aNhurI1PLwMViDoe4sV6E2X6sHuIWKNm/lehGkLOheGABgFEd9QV7cRBdTGQSvYnqjm4KrXtB4Xjla/pLFxM2PZGdpuZkguncbkig7KKyilSKdeETroSZUmplIjODVFun+AO28maX0ySlBRwZ4NVSyeGYe1Z3pFRKQgIn1P02JFpL9/RKTMTkRS6YyIiJw6d1fej0/J97zI8GhWOjpuiSmE9peD7dviAMRi63/jrfEtAIxOOjzj83rsEwvsBLl8AW2RWZH5e5DPdg6zd9MaPnz8wvz8Z1auWEZoLerkxQd919+5HUUryCyx2Um0StGy5BvxyjwFp3ACKIUx5ofKZDKrwzBc+O8P+DOUUu4n1NgJ8fF1wIEAAAAASUVORK5CYII=',
+          ),
+          24 => 
+          array (
+            'extension' => 'png',
+            'contenu' => 'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAABfJJREFUSA2dlstvXHcVxz/3Oe+Xx0wS202cuEkDcts0TkqrUAmpLLqCRZGoukACiRV/AxISSKyQ2IKQumXFAtQFFCGkSmlp3aZOXEPiJBMnfs3Dc2fmznju+3J+12EBEhLKT/PT/H73nuf3fM+Z0bZ//YOrpzXnL+VcvhGjpxrI59lXCqlBok18zzlMG98yF+6+t1T89k8aaaWVaEf7OrYtIiL2LEvT0MJAS+cWkrLbbSz84WdLprm0YuBskd77QKP3MZrY5xntq9zTQPS/8prsRZRtc7P2fS65AfnqtTSde/spPCpREZSITtZ/3VWG2at/i//nXYsiPHfCvdpLmGsP3+J3P3qVldwe7Ye7gpApdiVVwySJwgwuddY0Xe4qPNBNW1BMSOMoC0I3reyciuMgiDh/YYkH3iLv/PYT9FbcpRG77H3yd8ZTk0hv4U4MHnz8KX5SJ5R7f8/hyeZdQqOVbXVWz9Q7JaNklY7SVTaUrUbiomybXQkiiRNKtSq55VVqjRyz8Sni0SELF1fQDchbGtNySe5nM2Ti0QHF5hmay2clK/AOtjmz8lUKVZtSs4X5SJyLTWVbJ44z0ig0dQLZ8i2VEkDkW53V80i2IvEJ9LZmUDJNiibZLhgFDEK0TDbIgsiIKLZNgjjDUUsSHn/0ZyrVGlHo4zl9dm7+SYTF7WxM5Pu0j11svcDDg8842L5N7c4iUeIz6Q+4HnuYlsVo7LByqiJ6ErLYNlUGalu2xfLV16nW5/EmDntfrrO8dkMgsnB2H3A8PGJx9Tp5w6a7/pjZ7nucqoIvdZ8ay5y78gtyhTqO08Pq38V7alccCCYSvS5gV2rzVGs2ltYgZxep18rCGAiHdVLPk3d5CnIvFWpIHMwtrOGFWwwH56hW58gVDeKkSTIwJfJEAuckA4WdKTSMJgNis0k8HQmeQkN/JtYt2VPSMCSe+MIUm3A2oicMroaf4UkGbtQWXQctrhFMjjJYXV+MxoE48OOsnzSpypPNdcZSg1Aa5f0vf85fdxxso0bP/Zwr8z8VYdUWCV5vzHz4Q2gvoCcejdDlyT82KRZtqZjP/uExt9Me37lYVjonNVAFWr3xTRr1Mv5sxgdth+cubCA6hDuweP48V7/xdcFWmLNR4dXS2zSXWuISbn/4Ea3Lq2hWjp2dR/xztMMrFwIuSqbCIiFRkmLoGgUptMJYFTaXq1MQnPP2K+TGt8jpFkUxFgmPxzNhme4z7QT87ebnjLt7FA9CEtEzhctvvnGdauLw6Rf74iD2sygUw7u7bUIpVnA8pTO+yfGeRGvf4n4PvpYe0u0MORhN6XUdyrWUR/f3eKEJ33v3u3SHE0ZexDQ2GA4dHm1vYNcWVAZPG03mz8wZYAm9Ai/k9blfcra6iCFz5qX5QwrM0zvssD9wmU4nzKS67Y7H2hsvIvzADVMmoUbHcYm6bSxTZxb4qgaKpdIQlsnq2svU8zZuEApVDa5dE2V5v98bMTjq8dzl57G7cOxHpFaZpi0EkUG453h0Rj5DP+G40+Z0XmQoyXyaZEXPRrOuy2+NGBNSZjtIpjIgxLnsMJ4JbRXlRDQJ0YXXrjslFAM16ZWujHtlfHbwkFIwoFafw5KAJ66rIAqEeime52t3bn0hClU8YVFfcN68s5k14NBxBDaP4XHCrjPD6R6w3Q1YuXSJQqnCvd0ek/37lOIxpXqDUrmkjcYj3KFy4IaawMWVq9ejQb+nG4aezf7VF1YIQ6GYGmBLp1HPh9Jo5aovI6HKXtrhtRef58njXRkbd6lL+s3WGenoGkuLi8lwOLIOe/3U5HI5ftyf0p9o1kyfE8wEdTGaBqk4KmXjM41VVwo+uQp65HE4GfLmy8vUkyPubHwoI0QaqiwjQvCMwkjgcw1fhmOlUra03/z+/Ut/3Bj+eH3Xn8/raZyk6o+FGtEn+KuzWuquBlPTDLlRP6Blh8yilEKhRCI9FMsoSaWfwiiWHVIuFdMLy+d+paXr6xZrC1Lbfk7Z+P/WWUaj/y0pP7latVoNtra2gn8BFOvwO70K96wAAAAASUVORK5CYII=',
+          ),
+        ),
+      ),
+    ),
+  ),
+);
+
+?>
\ No newline at end of file
diff --git a/www/plugins/import_ics/formulaires/editer_almanach.html b/www/plugins/import_ics/formulaires/editer_almanach.html
new file mode 100644 (file)
index 0000000..e78a501
--- /dev/null
@@ -0,0 +1,91 @@
+<script language="JavaScript">
+$( document ).ready(function() {
+  $('.editer_id_ressource').hide();
+});
+function toggle(className, obj) {
+    var $input = $(obj);
+    if ($input.prop('checked')) $(className).show();
+    else $(className).hide();
+}
+</script>
+
+<div class="ajax">
+<div class='formulaire_spip formulaire_editer formulaire_#FORM formulaire_#FORM-#ENV{id_almanach,nouveau}'>
+       [(#REM) titre pour un formulaire en plusieurs étapes
+       <h3> Création d'un almanach et importation d'événements : étape #ENV{_etape}/#ENV{_etapes}</h3>]
+       <h3> Création d'un almanach et importation d'événements</h3>
+       [<p class="reponse_formulaire reponse_formulaire_ok">(#ENV**{message_ok})</p>]
+       [<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV*{message_erreur})</p>]
+
+[(#REM) on définit un tableau vide, on rajoute dynamiquement les ensembles clé-valeur sortis de la boucle sur le groupe de mot-clés Type. On l'utilise ensuite pour la selection des mots clés par une saisie. on fait la même chose pour les ressources]
+#SET{tableau_type_evenement, #ARRAY}
+
+[(#REM)Attention astuce inside pour que les clés numériques ne soient pas réindexées, on inverse l'ordre d'intégration dasn le tableau et ensuite on flip le tableau]
+<BOUCLE_groupe(GROUPES_MOTS){titre=Type}>
+<BOUCLE_mots(MOTS){id_groupe}>
+#SET{tableau_type_evenement, #GET{tableau_type_evenement}|array_merge{#ARRAY{#TITRE,#ID_MOT}}
+</BOUCLE_mots>
+</BOUCLE_groupe>
+[(#SET{tableau_type_evenement, #GET{tableau_type_evenement}|array_flip})]
+
+[(#REM)même chose avec les ressources mais on conditionne à la présence du plugin orr]
+#SET{tableau_ressources,#ARRAY}
+<BOUCLE_ressources(ORR_RESSOURCES){si #PLUGIN{orr}| }>
+#SET{tableau_ressources, #GET{tableau_ressources}|array_merge{#ARRAY{#ORR_RESSOURCE_NOM,#ID_ORR_RESSOURCE}}
+</BOUCLE_ressources>
+[(#SET{tableau_ressources, #GET{tableau_ressources}|array_flip})]
+
+       [<p class="reponse_formulaire reponse_formulaire_ok">(#ENV**{message_ok})</p>]
+       [<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV**{message_erreur})</p>]
+
+       [(#ENV{editable})
+       <form method='post' action='#ENV{action}' enctype='multipart/form-data'><div>
+               #ACTION_FORMULAIRE{#ENV{action}}
+               <input type='hidden' name='id_almanach' value='#ENV{id_almanach}' />
+               <input type='hidden' name='id_article' value='#ENV{id_article}' />
+               <ul>
+
+                       [(#SAISIE{input, titre, obligatoire=oui,
+                               label=<:almanach:label_titre:>,
+                               explication=<:almanach:explication_titre:> })]
+
+                       [(#SAISIE{url, url, obligatoire=oui,
+                               label=<:almanach:label_url:>,
+                               explication=<:almanach:explication_url:>,
+                               verifier => array('type' => 'url',),
+                       })]
+
+                       [(#SAISIE{input, id_article, obligatoire=oui,
+                               label=<:agenda:evenement_article:>,
+                               explication=<:almanach:explication_id_article:>
+                       })]
+
+                       [(#SAISIE{selection, id_mot, obligatoire=oui,
+                               label=<:seminaire:choix_mot:>,
+                               explication=<:almanach:type_evenement:>,
+                               datas=#GET{tableau_type_evenement}      
+                       })]
+
+                       [(#PLUGIN{orr}|oui)
+                       <li class="editer_resa_auto">
+                               <div class="choix">
+                                       <input type='checkbox' onclick="toggle('.editer_id_ressource', this)" class="checkbox" name='resa_auto' value='oui' id='resa_auto_oui'[ (#ENV{resa_auto}oui{non}|oui)checked="checked"] />
+                                       <label for='resa_auto_oui'>Activer les réservations automatiques</label>
+                               </div>
+                       </li>
+
+                       [(#SAISIE{selection, id_ressource, obligatoire=non,
+                               label=<:almanach:resa_auto:>,
+                               explication=<:almanach:choix_salle:>,
+                               datas=#GET{tableau_ressources}
+                       })]
+                       ]
+               </ul>
+               [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ]
+               <!--extra-->
+               [(#REM)<p class="boutons"><input type='submit' class='submit' value='Suivant' /></p> ça ce sera quand le formulaire en deux parties sera correct pour l'instant on fait un formulaire en une partie]
+               <p class="boutons"><input type='submit' class='submit' value='<:bouton_enregistrer:>' /></p>
+       </div></form>
+       ]
+</div>
+</div>
diff --git a/www/plugins/import_ics/formulaires/editer_almanach.php b/www/plugins/import_ics/formulaires/editer_almanach.php
new file mode 100644 (file)
index 0000000..f83e301
--- /dev/null
@@ -0,0 +1,170 @@
+<?php
+/**
+ * Gestion du formulaire de d'édition de almanach
+ *
+ * @plugin     Import_ics
+ * @copyright  2013
+ * @author     Amaury Adon
+ * @licence    GNU/GPL
+ * @package    SPIP\Import_ics\Formulaires
+ */
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+include_spip('inc/actions');
+include_spip('action/editer_liens');
+include_spip('inc/editer');
+include_spip('lib/iCalcreator.class'); /*pour la librairie icalcreator incluse dans le plugin icalendar*/
+/**
+ * Identifier le formulaire en faisant abstraction des paramètres qui ne représentent pas l'objet edité
+ *
+ */
+function formulaires_editer_almanach_identifier_dist($id_almanach='new', $retour='', $lier_trad=0, $config_fonc='', $row=array(), $hidden=''){
+       return serialize(array(intval($id_almanach)));
+}
+
+/**
+ * Chargement du formulaire d'édition de almanach
+ *
+ * Déclarer les champs postés et y intégrer les valeurs par défaut
+ */
+function formulaires_editer_almanach_charger_dist($id_almanach='new', $retour='', $lier_trad=0, $config_fonc='', $row=array(), $hidden=''){
+       $valeurs = formulaires_editer_objet_charger('almanach',$id_almanach,'',$lier_trad,$retour,$config_fonc,$row,$hidden);
+       //$valeurs[_etapes]=2;//on rajoute  un couple clé/valeur pour le nombre d'étapes du formulaire (pas la peine tant que je n'arrive pas à avoir un résutat correct)
+       $valeurs['resa_auto']='non';
+       return $valeurs;
+
+}
+
+/**
+ * Vérifications du formulaire d'édition de almanach
+ *
+ */
+function formulaires_editer_almanach_verifier_dist($id_almanach='new', $retour='', $lier_trad=0, $config_fonc='', $row=array(), $hidden=''){
+       //version de base de la fabrique
+       //return formulaires_editer_objet_verifier('almanach',$id_almanach, array('titre', 'url', 'id_article'));
+       $erreurs = formulaires_editer_objet_verifier('almanach',$id_almanach, array('titre', 'url', 'id_article', 'id_mot'));
+       //verification supplementaires
+       return $erreurs;
+}
+
+
+/**
+* Importation d'un événement dans la base
+**/
+function importation_evenement($objet_evenement,$id_almanach){
+       #on recupere les infos de l'evenement dans des variables
+           $attendee = $objet_evenement->getProperty( "attendee" ); #nom de l'attendee
+           $lieu = $objet_evenement->getProperty("location");#récupération du lieu
+           $summary_array = $objet_evenement->getProperty("summary", 1, TRUE); #summary est un array on recupere la valeur dans l'insertion attention, summary c'est pour le titre !
+               $titre_evt=str_replace('SUMMARY:', '', $summary_array["value"]);
+               $url = $objet_evenement->getProperty( "URL");#on récupère l'url de l'événement pour la mettre dans les notes histoire de pouvoir relier à l'événement original
+           $descriptif_array = $objet_evenement->getProperty("DESCRIPTION", 1,TRUE);
+           $organizer = $objet_evenement->getProperty("ORGANIZER");#organisateur de l'evenement
+       #données de localisation de l'évenement
+           $localisation = $objet_evenement->getProperty( "GEO" );#c'est un array array( "latitude"  => <latitude>, "longitude" => <longitude>))
+           $latitude = $localisation['latitude'];
+           $longitude = $localisation['longitude'];
+       //un petit coup avec l'uid
+           $uid_distante = $objet_evenement->getProperty("UID");#uid de l'evenement
+       #les 3 lignes suivantes servent à récupérer la date de début et à la mettre dans le bon format
+           $dtstart_array = $objet_evenement->getProperty("dtstart", 1, TRUE); 
+               $dtstart = $dtstart_array["value"];
+                       $startDate = "{$dtstart["year"]}-{$dtstart["month"]}-{$dtstart["day"]}";
+                       $startTime = '';#on initialise le temps de début
+               if (!in_array("DATE", $dtstart_array["params"])) {
+                                $startTime = " {$dtstart["hour"]}:{$dtstart["min"]}:{$dtstart["sec"]}";
+                       }
+               #on fait une variable qui contient le résultat des deux précédentes actions
+               $date_debut = $startDate.$startTime;
+       #les 3 lignes suivantes servent à récupérer la date de fin et à la mettre dans le bon format
+               $dtend_array = $objet_evenement->getProperty("dtend", 1, TRUE);
+                       $dtend = $dtend_array["value"];
+               $endDate = "{$dtend["year"]}-{$dtend["month"]}-{$dtend["day"]}";
+               $endTime = '';#on initialise le temps de fin
+               if (!in_array("DATE", $dtend_array["params"])) {
+                               $endTime = " {$dtend["hour"]}:{$dtend["min"]}:{$dtend["sec"]}";
+                       }
+               #on fait une variable qui contient le résultat des deux précédentes actions
+               $date_fin = $endDate.$endTime;
+       #on insere les infos des événements dans la base 
+       # ca ce sera pour quand j'arriverai à faire fonctionner le selecteur d'articles $id_article = preg_replace('(article\|)','',_request('id_article')); #le selecteur d'article fournit un tableau, on se débarasse du mot article dedans et on appellera ensuite la première valeur (il pourrait y avoir des saisies multiples même si ici on ne les autorise pas)
+       $id_mot = _request('id_mot');
+       $id_article = _request('id_article'); 
+       $id_evenement= sql_insertq('spip_evenements',array('id_article' =>$id_article,'date_debut'=>$date_debut,'date_fin'=>$date_fin,'titre'=>$titre_evt,'descriptif'=>'<math>'.$descriptif_array["value"].'</math>','lieu'=>$lieu,'adresse'=>'','inscription'=>'0','places'=>'0','horaire'=>'oui','statut'=>'publie','attendee'=>str_replace('MAILTO:', '', $attendee),'id_evenement_source'=>'0','uid'=>$uid_distante,'sequence'=>$sequence_distante,'notes'=>$url));
+       
+       #on associe l'évéenement à l'almanach
+       #objet_associer(array('almanach'=>$id_almanach),array('evenement'=>$id_evenement),array('vu'=>'oui'));
+       sql_insertq("spip_almanachs_liens",array('id_almanach'=>$id_almanach,'id_objet'=>$id_evenement,'objet'=>'evenement','vu'=>'oui'));
+       #on associe l'événement à son mot
+       sql_insertq("spip_mots_liens",array('id_mot'=>$id_mot,'id_objet'=>$id_evenement,'objet'=>'evenement'));
+       #on ajoute la resa si on le doit
+       if ((_request("id_ressource"))>0) {
+               $id_ressource=_request("id_ressource");
+               ajout_resa($titre_evt,$id_ressource,$date_debut,$date_fin);
+       }
+}
+
+
+/**
+*ajout d'une reservation à l'événeemnt si c'est coché
+**/
+
+function ajout_resa($titre_evt,$id_ressource,$date_debut,$date_fin){
+       $id_orr_reservation = sql_insertq("spip_orr_reservations",array('orr_reservation_nom'=>$titre_evt,'orr_date_debut'=>$date_debut,'orr_date_fin'=>$date_fin));
+       sql_insertq("spip_orr_reservations_liens",array('id_orr_reservation'=>$id_orr_reservation,'id_objet'=>$id_ressource,'objet'=>'orr_ressource','vu'=>'non'));
+echo "ajout résa";
+
+}
+/**
+ * Traitement du formulaire d'édition de almanach
+ *
+ * Traiter les champs postés
+ *
+ */
+function formulaires_editer_almanach_traiter_dist($id_almanach='new', $retour='', $lier_trad=0, $config_fonc='', $row=array(), $hidden=''){
+       $chargement = formulaires_editer_objet_traiter('almanach',$id_almanach,'',$lier_trad,$retour,$config_fonc,$row,$hidden);
+       #on recupère l'id de l'almanach dont on aura besoin plus tard
+       $id_almanach = $chargement['id_almanach'];
+       #on associe le mot à l'almanach
+       $id_mot = _request('id_mot');
+       if (_request('resa_auto')=='oui')
+       $resa_auto=1;
+       sql_insertq("spip_mots_liens",array('id_mot'=>$id_mot,'id_objet'=>$id_almanach,'objet'=>'almanach'));
+       sql_insertq("spip_almanachs",array('resa_auto'=>$resa_auto));
+       echo $resa_auto;
+       #configuration nécessaire à la récupération
+       $config = array("unique_id"=>"","url"=>_request("url"));
+       $cal = new vcalendar($config);
+       $cal->parse();
+       //ON fait un appel dans la base de spip pour vpouvoir vérifier si un événement y est déjà (ça ne se fait pas en une ligne...)
+       $liens = sql_allfetsel('id_evenement, uid, sequence', 'spip_evenements');
+       // on definit un tableau des uid présentes dans la base
+       $uid ="";
+       foreach ($liens as $u ) {
+               $uid[] = $u['uid'];
+       };
+ while ($comp = $cal->getComponent())
+ {
+#les variables qui vont servir à vérifier l'existence et l'unicité 
+               $sequence_distante = $comp->getProperty( "SEQUENCE" );#sequence d l'evenement http://kigkonsult.se/iCalcreator/docs/using.html#SEQUENCE
+           $uid_distante = $comp->getProperty("UID");#uid de l'evenement
+               if (!is_int($sequence_distante)){$sequence_distante="0";}//au cas où le flux ics ne fournirait pas le champ sequence, on initialise la valeur à 0 comme lors d'un import
+//est-ce que c'est un googlecal ? Dans ce cas, on a un traitement un peu particulier
+
+//On commence à vérifier l'existence et l'unicité  maintenant et on met à jour ou on importe selon le cas
+       if (in_array($uid_distante, $uid)){//si l'uid_distante est présente dans la bdd
+               $cle = array_search($uid_distante, $uid); // on utilise le fait que les deux tableaux ont le même index pour le récupérer
+               $sequence = $liens[$cle]['sequence'];//sequence presente dans la base ayant le meme index
+
+               if ($sequence < $sequence_distante ){//si la sequecne de la bdd est plus petite, il y a eu mise à jour et il faut intervenir
+                       echo "c'est pas pareil, il faut mettre à jour l'événement ".$liens[$cle]['id_evenement']."<br/>";
+               } 
+       } else {importation_evenement($comp,$id_almanach);};//l'evenement n'est pas dans la bdd, on va l'y mettre
+ }
+
+       return $chargement;
+}
+
+
+?>
\ No newline at end of file
diff --git a/www/plugins/import_ics/formulaires/editer_almanach_2.html b/www/plugins/import_ics/formulaires/editer_almanach_2.html
new file mode 100644 (file)
index 0000000..8131d60
--- /dev/null
@@ -0,0 +1,20 @@
+<div class="ajax">
+<div class='formulaire_spip formulaire_editer formulaire_#FORM'>
+       <h3> Création d'un almanach et importation d'événements : étape #ENV{_etape}/#ENV{_etapes}</h3>
+       [<p class="reponse_formulaire reponse_formulaire_ok">(#ENV*{message_ok})</p>]
+       [<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV*{message_erreur})</p>]
+       <form method='post' action='#ENV{action}' enctype='multipart/form-data'><div>
+                [(#REM) les hidden qui declencheront le service du formulaire parametre : url d'action ]
+                #ACTION_FORMULAIRE{#ENV{action},#FORM}
+                       <p>Les événements contenus à l'adresse <strong>#ENV{url}</strong> sont :</p>
+
+
+                       [(#ENV{url}|mon_filtre)]
+
+
+                       <p>Si ces événements sont ceux que vous vous attendiez à trouver, vous pouvez valider la saisie et les associer à l'almanach <strong>[(#ENV{titre}) ]</strong> et à l'article <strong>#INFO_TITRE{article,#ENV{id_article}}</strong>, sinon retournez à l'étape précédente.</p>
+               <p class='boutons'><input type='submit' class='submit' name="_retour_etape_1" value='<:retour:>' /> <input type='submit' class='submit' value='<:bouton_enregistrer:>' /></p>
+       </div></form>
+</div>
+</div>
\ No newline at end of file
diff --git a/www/plugins/import_ics/genie/import_ics_synchro.php b/www/plugins/import_ics/genie/import_ics_synchro.php
new file mode 100644 (file)
index 0000000..a83a2c8
--- /dev/null
@@ -0,0 +1,135 @@
+<?php
+
+/**
+ * Gestion du génie import_ics_synchro
+ *
+ * @plugin import_ics pour SPIP
+ * @license GPL
+ * 
+ */
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+/**
+ * Actualise tous les almanachs
+ *
+ * @genie import_ics_synchro
+ *
+ * @param int $last
+ *     Timestamp de la dernière exécution de cette tâche
+ * @return int
+ *     Positif : la tâche a été effectuée
+ */
+function genie_import_ics_synchro_dist($t){
+
+//on recupère toutes les infos sur les almanachs
+if(
+       $resultats = sql_allfetsel('*', 'spip_almanachs')
+       and is_array($resultats)
+)
+{
+//librairie icalcreator incluse dans le plugin icalendar
+include_spip('lib/iCalcreator.class');
+
+//pour chacun des almanachs, on va traiter les différences
+foreach ($resultats as $r) {
+               //      on va faire une sélection des evenemnts associés à l'almanach en cours 
+               //donc jointure sur les table spip_evenemnts et spip_almanachs_liens
+               $evenements_lies = sql_allfetsel('E.uid, E.id_evenement, E.sequence',
+                       'spip_evenements AS E 
+                       INNER JOIN spip_almanachs_liens AS L
+                       ON E.id_evenement = L.id_objet AND L.id_almanach='.intval($r['id_almanach']));
+
+               //tableau des uid associés à cet almanach tiré du tableau précédent
+                       $uid ="";
+                       foreach ($evenements_lies as $u ) {
+                               $uid[] = $u['uid'];
+                       };
+
+               //configuration nécessaire à la récupération et parsing du calendrier distant
+
+                       $config = array("unique_id" => "distant",
+                                                       "url" => $r['url']);
+                       $v = new vcalendar($config);
+                       $v->parse();
+
+                       echo "almanach".$r["id_almanach"]."</br>";
+                       while ($comp = $v->getComponent())
+                       {
+               //les variables qui vont servir à vérifier l'existence et l'unicité 
+                               $sequence_distante = $comp->getProperty( "SEQUENCE" );#sequence d l'evenement http://kigkonsult.se/iCalcreator/docs/using.html#SEQUENCE
+                               $uid_distante = $comp->getProperty("UID");#uid de l'evenement;
+                               //au cas où le flux ics ne fournirait pas le champ sequence, on initialise la valeur à 0 comme lors d'un import
+                               if (!is_int($sequence_distante)){$sequence_distante="0";}
+                               //On commence à vérifier l'existence et l'unicité  maintenant et on met à jour 
+                               //ou on importe selon le cas
+                               if (in_array($uid_distante, $uid)){//si l'uid_distante est présente dans la bdd
+                                       // on utilise le fait que les deux tableaux ont le même index pour le récupérer
+                                       $cle = array_search($uid_distante, $uid); 
+                                       //sequence presente dans la base ayant le meme index
+                                       $sequence = $evenements_lies[$cle]['sequence'];
+                                       if ($sequence < $sequence_distante) {
+                                               importation_evenement($comp,$r);
+                                       }
+                               } else {importation_evenement($comp,$r);}
+                       }
+               }
+}
+
+return 1;
+
+}
+
+
+/**
+* Importation d'un événement dans la base
+**/
+function importation_evenement($objet_evenement,$tableau_almanach){
+       #on recupere les infos de l'evenement dans des variables
+           $attendee = $objet_evenement->getProperty( "attendee" ); #nom de l'attendee
+           $lieu = $objet_evenement->getProperty("location");#récupération du lieu
+           $summary_array = $objet_evenement->getProperty("summary", 1, TRUE); #summary est un array on recupere la valeur dans l'insertion attention, summary c'est pour le titre !
+               $url = $objet_evenement->getProperty( "URL");#on récupère l'url de l'événement pour la mettre dans les notes histoire de pouvoir relier à l'événement original
+           $descriptif_array = $objet_evenement->getProperty("DESCRIPTION", 1,TRUE);
+           $organizer = $objet_evenement->getProperty("ORGANIZER");#organisateur de l'evenement
+       #données de localisation de l'évenement
+           $localisation = $objet_evenement->getProperty( "GEO" );#c'est un array array( "latitude"  => <latitude>, "longitude" => <longitude>))
+           $latitude = $localisation['latitude'];
+           $longitude = $localisation['longitude'];
+       //un petit coup avec l'uid
+           $uid_distante = $objet_evenement->getProperty("UID");#uid de l'evenement
+       #les 3 lignes suivantes servent à récupérer la date de début et à la mettre dans le bon format
+           $dtstart_array = $objet_evenement->getProperty("dtstart", 1, TRUE); 
+               $dtstart = $dtstart_array["value"];
+                       $startDate = "{$dtstart["year"]}-{$dtstart["month"]}-{$dtstart["day"]}";
+                       $startTime = '';#on initialise le temps de début
+               if (!in_array("DATE", $dtstart_array["params"])) {
+                                $startTime = " {$dtstart["hour"]}:{$dtstart["min"]}:{$dtstart["sec"]}";
+                       }
+               #on fait une variable qui contient le résultat des deux précédentes actions
+               $date_debut = $startDate.$startTime;
+       #les 3 lignes suivantes servent à récupérer la date de fin et à la mettre dans le bon format
+               $dtend_array = $objet_evenement->getProperty("dtend", 1, TRUE);
+                       $dtend = $dtend_array["value"];
+               $endDate = "{$dtend["year"]}-{$dtend["month"]}-{$dtend["day"]}";
+               $endTime = '';#on initialise le temps de fin
+               if (!in_array("DATE", $dtend_array["params"])) {
+                               $endTime = " {$dtend["hour"]}:{$dtend["min"]}:{$dtend["sec"]}";
+                       }
+               #on fait une variable qui contient le résultat des deux précédentes actions
+               $date_fin = $endDate.$endTime;
+       #on insere les infos des événements dans la base 
+       //les infos de l'almanach
+       $id_mot = $tableau_almanach['id_mot'];
+       $id_article = $tableau_almanach['id_article'];
+       $id_almanach = $tableau_almanach['id_almanach'];
+
+       //insertion de l'evenement en bdd
+       $id_evenement= sql_insertq('spip_evenements',array('id_article' =>$id_article,'date_debut'=>$date_debut,'date_fin'=>$date_fin,'titre'=>str_replace('SUMMARY:', '', $summary_array["value"]),'descriptif'=>'<math>'.$descriptif_array["value"].'</math>','lieu'=>$lieu,'adresse'=>'','inscription'=>'0','places'=>'0','horaire'=>'oui','statut'=>'publie','attendee'=>str_replace('MAILTO:', '', $attendee),'id_evenement_source'=>'0','uid'=>$uid_distante,'sequence'=>$sequence_distante,'notes'=>$url));
+       //on associe l'événement à l'almanach
+       sql_insertq("spip_almanachs_liens",array('id_almanach'=>$id_almanach,'id_objet'=>$id_evenement,'objet'=>'evenement','vu'=>'oui'));
+       //on associe l'événement à son mot
+       sql_insertq("spip_mots_liens",array('id_mot'=>$id_mot,'id_objet'=>$id_evenement,'objet'=>'evenement'));
+}
+
+?>
\ No newline at end of file
diff --git a/www/plugins/import_ics/genie/synchro.php b/www/plugins/import_ics/genie/synchro.php
new file mode 100644 (file)
index 0000000..bb71150
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+
+genie_synchro_dist(){
+
+// récupérer url fichier distant
+// parser le fichier distant
+
+// pour chaque événement du fichier distant
+//     récupération uid_distant
+
+//             pour chaque enregistrement déjà présent dans la base
+//             si uid_distant = uid_local alors
+//                     récupérer sequence_distant
+//                     si sequence_distant != sequence_local alors 
+//                             supprimer l événement local et le remplacer par l événement distant
+//             sinon insérer l événement distant dans la base 
+
+
+
+}
+
+?>
\ No newline at end of file
diff --git a/www/plugins/import_ics/import_ics_administrations.php b/www/plugins/import_ics/import_ics_administrations.php
new file mode 100644 (file)
index 0000000..614b787
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+/**
+ * Fichier gérant l'installation et désinstallation du plugin Import_ics
+ *
+ * @plugin     Import_ics
+ * @copyright  2013
+ * @author     Amaury
+ * @licence    GNU/GPL
+ * @package    SPIP\Import_ics\Installation
+ */
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+
+/**
+ * Fonction d'installation et de mise à jour du plugin Import_ics.
+ *
+ * @param string $nom_meta_base_version
+ *     Nom de la meta informant de la version du schéma de données du plugin installé dans SPIP
+ * @param string $version_cible
+ *     Version du schéma de données dans ce plugin (déclaré dans paquet.xml)
+ * @return void
+**/
+function import_ics_upgrade($nom_meta_base_version, $version_cible) {
+       $maj = array();
+
+       $maj['create'] = array(
+               array('maj_tables',array('spip_almanachs', 'spip_almanachs_liens')),
+               array('maj_tables',array('spip_evenements')),
+               array('sql_alter',"TABLE spip_evenements ADD uid text NOT NULL"),
+               array('sql_alter',"TABLE spip_evenements ADD sequence bigint(21) DEFAULT '0' NOT NULL"),
+       );
+
+       $maj['1.0.1'] = array(
+               array('sql_alter',"TABLE spip_almanachs ADD id_ressource bigint(21) NOT NULL DEFAULT '0'"),
+       );
+       
+       include_spip('base/upgrade');
+       maj_plugin($nom_meta_base_version, $version_cible, $maj);
+}
+
+
+/**
+ * Fonction de désinstallation du plugin Import_ics.
+ *
+ * @param string $nom_meta_base_version
+ *     Nom de la meta informant de la version du schéma de données du plugin installé dans SPIP
+ * @return void
+**/
+function import_ics_vider_tables($nom_meta_base_version) {
+
+       sql_drop_table("spip_almanachs");
+       sql_drop_table("spip_almanachs_liens");
+
+       # Nettoyer les versionnages et forums
+       sql_delete("spip_versions",              sql_in("objet", array('almanach')));
+       sql_delete("spip_versions_fragments",    sql_in("objet", array('almanach')));
+       sql_delete("spip_forum",                 sql_in("objet", array('almanach')));
+
+       effacer_meta($nom_meta_base_version);
+}
+
+?>
\ No newline at end of file
diff --git a/www/plugins/import_ics/import_ics_autorisations.php b/www/plugins/import_ics/import_ics_autorisations.php
new file mode 100644 (file)
index 0000000..b4cd673
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+/**
+ * Définit les autorisations du plugin Import_ics
+ *
+ * @plugin     Import_ics
+ * @copyright  2013
+ * @author     Amaury
+ * @licence    GNU/GPL
+ * @package    SPIP\Import_ics\Autorisations
+ */
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+
+/**
+ * Fonction d'appel pour le pipeline
+ * @pipeline autoriser */
+function import_ics_autoriser(){}
+
+
+// -----------------
+// Objet almanachs
+
+
+/**
+ * Autorisation de voir un élément de menu (almanachs)
+ *
+ * @param  string $faire Action demandée
+ * @param  string $type  Type d'objet sur lequel appliquer l'action
+ * @param  int    $id    Identifiant de l'objet
+ * @param  array  $qui   Description de l'auteur demandant l'autorisation
+ * @param  array  $opt   Options de cette autorisation
+ * @return bool          true s'il a le droit, false sinon
+**/
+function autoriser_almanachs_menu_dist($faire, $type, $id, $qui, $opt){
+       return true;
+} 
+
+
+/**
+ * Autorisation de voir le bouton d'accès rapide de création (almanach)
+ *
+ * @param  string $faire Action demandée
+ * @param  string $type  Type d'objet sur lequel appliquer l'action
+ * @param  int    $id    Identifiant de l'objet
+ * @param  array  $qui   Description de l'auteur demandant l'autorisation
+ * @param  array  $opt   Options de cette autorisation
+ * @return bool          true s'il a le droit, false sinon
+**/
+function autoriser_almanachcreer_menu_dist($faire, $type, $id, $qui, $opt){
+       return autoriser('creer', 'almanach', '', $qui, $opt);
+} 
+
+/**
+ * Autorisation de créer (almanach)
+ *
+ * @param  string $faire Action demandée
+ * @param  string $type  Type d'objet sur lequel appliquer l'action
+ * @param  int    $id    Identifiant de l'objet
+ * @param  array  $qui   Description de l'auteur demandant l'autorisation
+ * @param  array  $opt   Options de cette autorisation
+ * @return bool          true s'il a le droit, false sinon
+**/
+function autoriser_almanach_creer_dist($faire, $type, $id, $qui, $opt) {
+       return in_array($qui['statut'], array('0minirezo', '1comite')); 
+}
+
+/**
+ * Autorisation de voir (almanach)
+ *
+ * @param  string $faire Action demandée
+ * @param  string $type  Type d'objet sur lequel appliquer l'action
+ * @param  int    $id    Identifiant de l'objet
+ * @param  array  $qui   Description de l'auteur demandant l'autorisation
+ * @param  array  $opt   Options de cette autorisation
+ * @return bool          true s'il a le droit, false sinon
+**/
+function autoriser_almanach_voir_dist($faire, $type, $id, $qui, $opt) {
+       return true;
+}
+
+/**
+ * Autorisation de modifier (almanach)
+ *
+ * @param  string $faire Action demandée
+ * @param  string $type  Type d'objet sur lequel appliquer l'action
+ * @param  int    $id    Identifiant de l'objet
+ * @param  array  $qui   Description de l'auteur demandant l'autorisation
+ * @param  array  $opt   Options de cette autorisation
+ * @return bool          true s'il a le droit, false sinon
+**/
+function autoriser_almanach_modifier_dist($faire, $type, $id, $qui, $opt) {
+       return in_array($qui['statut'], array('0minirezo', '1comite'));
+}
+
+/**
+ * Autorisation de supprimer (almanach)
+ *
+ * @param  string $faire Action demandée
+ * @param  string $type  Type d'objet sur lequel appliquer l'action
+ * @param  int    $id    Identifiant de l'objet
+ * @param  array  $qui   Description de l'auteur demandant l'autorisation
+ * @param  array  $opt   Options de cette autorisation
+ * @return bool          true s'il a le droit, false sinon
+**/
+function autoriser_almanach_supprimer_dist($faire, $type, $id, $qui, $opt) {
+       return $qui['statut'] == '0minirezo' AND !$qui['restreint'];
+}
+
+
+/**
+ * Autorisation de lier/délier l'élément (almanachs)
+ *
+ * @param  string $faire Action demandée
+ * @param  string $type  Type d'objet sur lequel appliquer l'action
+ * @param  int    $id    Identifiant de l'objet
+ * @param  array  $qui   Description de l'auteur demandant l'autorisation
+ * @param  array  $opt   Options de cette autorisation
+ * @return bool          true s'il a le droit, false sinon
+**/
+function autoriser_associeralmanachs_dist($faire, $type, $id, $qui, $opt) {
+       return $qui['statut'] == '0minirezo' AND !$qui['restreint'];
+}
+
+
+?>
\ No newline at end of file
diff --git a/www/plugins/import_ics/import_ics_fonctions.php b/www/plugins/import_ics/import_ics_fonctions.php
new file mode 100644 (file)
index 0000000..3b273e2
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+//require_once 'lib/iCalcreator.class.php';/*appeler la librairie qui se trouve dans le plugin icalendar*/
+function mon_filtre ($url) {
+    $config = array("unique_id" => "latp",
+        "url" => $url);
+//var_dump($url);
+    $v = new vcalendar($config);
+
+    $v->parse();
+
+    while ($comp = $v->getComponent()) {
+        echo "<div>";
+
+        /*date de début*/
+        $dtstart_array = $comp->getProperty("dtstart", 1, true);
+        $dtstart = $dtstart_array["value"];
+        $startDate = "{$dtstart["year"]}-{$dtstart["month"]}-{$dtstart["day"]}";
+        echo "start: ", $startDate;
+        if (!in_array("DATE", $dtstart_array["params"])) {
+            $startTime = "{$dtstart["hour"]}:{$dtstart["min"]}:{$dtstart["sec"]}";
+            echo "T", $startTime;
+        }
+        echo "\n";
+
+        /*date de fin*/
+        $dtend_array = $comp->getProperty("dtend", 1, true);
+        $dtend = $dtend_array["value"];
+        $endDate = "{$dtend["year"]}-{$dtend["month"]}-{$dtend["day"]}";
+        echo "end: ", $endDate;
+        if (!in_array("DATE", $dtend_array["params"])) {
+            $endTime = "{$dtend["hour"]}:{$dtend["min"]}:{$dtend["sec"]}";
+            echo "T", $endTime;
+        }
+        echo "\n";
+        /*attendee*/
+        $attendee = $comp->getProperty("attendee");
+        echo "<strong>attendee : ", str_replace('MAILTO:', '', $attendee)."</strong><br/>";
+        /*summary*/
+        $summary_array = $comp->getProperty("summary", 1, true);
+        echo "summary : ", str_replace('SUMMARY:', '', $summary_array["value"]), "\n";
+        /*categorie*/
+        $categories = $comp->getProperty("categories");
+        echo "<strong>categories : ", $categories."</strong><br/>";
+
+        echo "</div>";
+    }
+
+//return $url;
+}
+
+
+?>
\ No newline at end of file
diff --git a/www/plugins/import_ics/import_ics_pipelines.php b/www/plugins/import_ics/import_ics_pipelines.php
new file mode 100644 (file)
index 0000000..736ebea
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Utilisations de pipelines par Import_ics
+ *
+ * @plugin     Import_ics
+ * @copyright  2013
+ * @author     Amaury
+ * @licence    GNU/GPL
+ * @package    SPIP\Import_ics\Pipelines
+ */
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+       
+/**
+ * Optimiser la base de données en supprimant les liens orphelins
+ * de l'objet vers quelqu'un et de quelqu'un vers l'objet.
+ *
+ * @pipeline optimiser_base_disparus
+ * @param  array $flux Données du pipeline
+ * @return array       Données du pipeline
+ */
+function import_ics_optimiser_base_disparus($flux){
+       include_spip('action/editer_liens');
+       $flux['data'] += objet_optimiser_liens(array('almanach'=>'*'),'*');
+       return $flux;
+}
+
+
+function import_ics_taches_generales_cron($taches_generales){
+       $taches_generales['import_ics_synchro'] = 3600*24;/*mettre à jour toutes les 24 heures parait bien*/
+       return $taches_generales;
+}
+?>
\ No newline at end of file
diff --git a/www/plugins/import_ics/lang/almanach_fr.php b/www/plugins/import_ics/lang/almanach_fr.php
new file mode 100644 (file)
index 0000000..f7004a9
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'ajouter_lien_almanach' => 'Ajouter cet almanach',
+       'aucun_evenement' => 'Cet almanach ne contient aucun événement. Si vous le désirez, vous pouvez le supprimer dans la liste des almanachs.
+',
+       //C
+       'confirmation_suppression_evenements' => 'Êtes vous certain(e) de vouloir supprimer les événements de l\'almanach \"@titre_almanach@\" ?',
+       'confirmation_mise_a_jour_evenements' => 'Voulez-vous réellement mettre à jour la liste des événements de l\'almanach \"@titre_almanach@\" ?\nCela peut prendre un certain temps.',
+       'choix_salle' => 'Tous les événements se verront attribuer cette salle dans le gestionnaire de ressources.',
+       // E
+       'explication_id_article' => 'Choisissez un article qui va recevoir les événements importés.',
+       'explication_titre' => 'Titre de l\'almanach',
+       'explication_url' => 'URL d\'origine du calendrier',
+       'explication_resa_auto' => 'On peut réserver automatiquement une salle pour tous les événements d\'un même almanach (modifiable individuellement ensuite).',
+
+       // I
+       'icone_creer_almanach' => 'Créer un almanach',
+       'icone_modifier_almanach' => 'Modifier cet almanach',
+       'info_1_almanach' => 'Un almanach',
+       'info_almanachs_auteur' => 'Les almanachs de cet auteur',
+       'info_evenement_almanach' => 'Les événements de cet almanach',
+       'info_aucun_almanach' => 'Aucun almanach',
+       'info_derniere_synchronisation' => 'La dernière synchronisation de cet almanach a été effectuée le ',
+       'info_nb_almanachs' => '@nb@ almanachs',
+       'info_supprimer_almanach' => 'Supprimer',
+       'info_supprimer_evenements' => 'Supprimer ces événements',
+
+       // L
+       'label_id_article' => 'Article d\'accueil de l\'almanach',
+       'label_id_mot' => 'Type des événements de cet almanach :',
+       'label_titre' => 'Titre',
+       'label_url' => 'URL',
+       'lien_synchro_almanach' => 'Mettre à jour cet almanach maintenant',
+
+       //P 
+       'plusieurs_evenements' => '@nb@ événements',
+       'purger_almanach' => 'Si vous voulez supprimer cet almanach, vous devez tout d\'abord en supprimer le contenu.',
+       // R
+       'regenerer_almanach' => 'Vous pouvez aussi restaurer son contenu en tentant une nouvelle synchronisation.',
+       'resa_auto' => 'Réservation automatique',
+       'reservation' => 'Choix de la salle à réserver',
+       'retirer_lien_almanach' => 'Retirer cet almanach',
+       'retirer_tous_liens_almanachs' => 'Retirer tous les almanachs',
+       'retour_liste' => 'Retour à la liste',
+
+       // T
+       'texte_ajouter_almanach' => 'Ajouter un almanach',
+       'texte_changer_statut_almanach' => 'Cet almanach est :',
+       'texte_creer_associer_almanach' => 'Créer et associer un almanach',
+       'titre_almanach' => 'Almanach',
+       'titre_almanachs' => 'Almanachs',
+       'titre_almanachs_rubrique' => 'Almanachs de la rubrique',
+       'titre_langue_almanach' => 'Langue de cet almanach',
+       'titre_logo_almanach' => 'Logo de cet almanach',
+       'type_evenement' => 'Tous les événements de l\'almanach auront le même type. Vous pourrez modifier cela individuellement.',
+
+       //U
+       'un_evenement' => '@nb@ événement',
+);
+
+?>
\ No newline at end of file
diff --git a/www/plugins/import_ics/lang/import_ics_fr.php b/www/plugins/import_ics/lang/import_ics_fr.php
new file mode 100644 (file)
index 0000000..4474a10
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // I
+       'import_ics_titre' => 'Import_ics',
+);
+
+?>
\ No newline at end of file
diff --git a/www/plugins/import_ics/lang/paquet-import_ics_fr.php b/www/plugins/import_ics/lang/paquet-import_ics_fr.php
new file mode 100644 (file)
index 0000000..3a7880b
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // I
+       'import_ics_description' => 'Importez les événements de sites distants dans votre base de données d\'événements SPIP',
+       'import_ics_nom' => 'Import_ics',
+       'import_ics_slogan' => 'Importez vos événements',
+);
+
+?>
\ No newline at end of file
diff --git a/www/plugins/import_ics/paquet.xml b/www/plugins/import_ics/paquet.xml
new file mode 100644 (file)
index 0000000..fb278f4
--- /dev/null
@@ -0,0 +1,39 @@
+<paquet\r
+       prefix="import_ics"\r
+       categorie="date"\r
+       version="1.1.2"\r
+       etat="test"\r
+       compatibilite="[3.0.7;3.0.*]"\r
+       logo="prive/themes/spip/images/import_ics-64.png"\r
+       documentation="http://contrib.spip.net/Import-ICS"\r
+       schema="1.0.1"\r
+>\r
+       <!--\r
+               Paquet genere le 2013-04-15 14:09:23\r
+       -->\r
+\r
+       <nom>Import_ics</nom>\r
+\r
+       <auteur>Amaury</auteur>\r
+\r
+       <licence>GNU/GPL</licence>\r
+       <necessite nom="agenda"/>\r
+       <necessite nom="icalendar"/>\r
+       <necessite nom="cextras" compatibilite="[3.0.5;[" />\r
+       <necessite nom="seminaire" />\r
+\r
+\r
+       <necessite nom="saisies" compatibilite="[1.24.0;]" />\r
+\r
+       <pipeline nom="autoriser" inclure="import_ics_autorisations.php" />\r
+\r
+       <pipeline nom="declarer_tables_objets_sql" inclure="base/import_ics.php" />\r
+       <pipeline nom="declarer_tables_interfaces" inclure="base/import_ics.php" />\r
+       <pipeline nom="declarer_tables_auxiliaires" inclure="base/import_ics.php" />\r
+       <pipeline nom="optimiser_base_disparus" inclure="import_ics_pipelines.php" />\r
+       <pipeline nom="taches_generales_cron" inclure="import_ics_pipelines.php"/>\r
+\r
+\r
+       <menu nom="almanachs" titre="almanach:titre_almanachs" parent="menu_edition" icone="images/almanach-16.png" action="almanachs" />\r
+       <menu nom="almanach_creer" titre="almanach:icone_creer_almanach" parent="outils_rapides" icone="images/almanach-new-16.png" action="almanach_edit" parametres="new=oui" />\r
+</paquet>
\ No newline at end of file
diff --git a/www/plugins/import_ics/prive/objets/contenu/almanach.html b/www/plugins/import_ics/prive/objets/contenu/almanach.html
new file mode 100644 (file)
index 0000000..d33765b
--- /dev/null
@@ -0,0 +1,88 @@
+<BOUCLE_almanach(ALMANACHS){id_almanach}{statut?}>
+
+[<div class="champ contenu_titre[ (#TITRE*|strlen|?{'',vide})]">
+       <label><:almanach:label_titre:> : </label>
+       <span dir='#LANG_DIR' class='#EDIT{titre} titre'>(#TITRE)</span>
+</div>]
+
+[<div class="champ contenu_url[ (#URL*|strlen|?{'',vide})]">
+       <label><strong><:almanach:label_url:> : </strong></label>
+       <span dir='#LANG_DIR' class='#EDIT{url} url'>(#URL)</span>
+</div>]
+
+<div class="champ contenu_id_article[ (#ID_ARTICLE*|strlen|?{'',vide})]">
+<label><strong><:almanach:label_id_article:> :</strong> </label> 
+<span dir='#LANG_DIR' class='#EDIT{id_article} id_article'>
+       <a href="#INFO_URL_ARTICLE{article,#ID_ARTICLE}">#INFO_TITRE{article,#ID_ARTICLE}&nbsp;(article n°#ID_ARTICLE)</a>
+</span>        
+</div>
+
+<div class="champ contenu_id_mot[ (#ID_MOT*|strlen|?{'',vide})]">
+<BOUCLE_mot_lie(MOTS){id_mot=#ID_MOT}>
+       <label><strong><:almanach:label_id_mot:></strong></label>
+       <span dir='#LANG_DIR' class='#EDIT{id_mot} id_mot'><a href="#URL_ECRIRE{mot,id_mot=#ID_MOT}">#TITRE (mot n°#ID_MOT)</a> </span>        
+</BOUCLE_mot_lie>
+
+<p></p>
+</div>
+
+
+<B_liste_evenements>
+#ANCRE_PAGINATION
+<div class="liste-objets">
+       [(#REM) On boucle sur la table de liens pour récupérer l'id de l'objet evenement voulu puis on boucle sur la table evenement pour recuperer les détails de l'événement ]
+       <table class='spip liste'>
+       <caption><span class="image_loading"></span><strong class="caption"><:almanach:info_evenement_almanach:></strong></caption>
+       <thead>
+               <tr class='first_row'>
+                       <th class='date' scope='col'><:date:></th>
+                       <th class='orateur' scope='col'><:seminaire:attendee:></th>
+                       <th class='titre' scope='col'><:info_titre:></th>
+               </tr>
+       </thead>
+       <tbody>
+               <BOUCLE_liste_evenements(spip_almanachs_liens evenements){id_almanach=#ID_ALMANACH}{objet=evenement}{pagination 10}>
+               <tr class="[(#COMPTEUR_BOUCLE|alterner{row_odd,row_even})]">
+               <BOUCLE_details_evenement(EVENEMENTS){id_evenement=#ID_OBJET}>
+                       <td class='date secondaire'>[(#DATE_DEBUT|agenda_affdate_debut_fin{#DATE_FIN,#HORAIRE,'hcal'})]</td>
+                       <td class="orateur">#ATTENDEE</td>
+                       <td class='titre principale'>
+                               #SET{attendee,#ATTENDEE}[(#REM) on met attendee dans une variable pour pouvoir l'éliminer dans le titre si jamais il est présent ainsi que les espaces et le tiret suivant]
+                               [<a href="#URL_ECRIRE{evenement,id_evenement=#ID_EVENEMENT}">(#TITRE|replace{#GET{attendee}\h-\h})</a>]<br/>
+                               [<strong><:seminaire:lieu:> : </strong>(#LIEU)]
+                       </td>
+               </BOUCLE_details_evenement>     
+               </tr>
+               </BOUCLE_liste_evenements>
+       </tbody>
+</table>
+[<p class='pagination'>(#PAGINATION{prive})</p>]
+</div>
+</B_liste_evenements>
+</B_liste_evenements>
+<p><:almanach:aucun_evenement:></p>
+[(#BOUTON_ACTION{<:almanach:retour_liste:>,#URL_ECRIRE{almanachs}})]
+<p><:almanach:regenerer_almanach:></p>
+<//B_liste_evenements>
+
+
+       [(#MAJ|oui)
+               <div class='maj'><:almanach:info_derniere_synchronisation:>[ (#MAJ|affdate_heure)]</div>
+       ]
+
+       <div class='synchro_almanach'>
+               [(#BOUTON_ACTION{<:almanach:lien_synchro_almanach:>,#URL_ACTION_AUTEUR{synchro_almanach,#ID_ALMANACH,#SELF},ajax,<:almanach:confirmation_mise_a_jour_evenements{titre_almanach=#TITRE}:>,})]
+       </div>
+
+
+       <div class='purger_almanach'>
+       <BOUCLE_nb_evenement(spip_almanachs_liens){id_almanach=#ID_ALMANACH}{objet=evenement}>
+       </BOUCLE_nb_evenement>
+       <div class='purge'><:almanach:purger_almanach:></div>
+       [(#AUTORISER{supprimer, almanach, #ID_ALMANACH}|oui)
+               [(#BOUTON_ACTION{<:almanach:info_supprimer_evenements:>,#URL_ACTION_AUTEUR{supprimer_evenements_almanach,#ID_ALMANACH,#SELF},ajax,<:almanach:confirmation_suppression_evenements{titre_almanach=#TITRE}:>,'','(function(me){$(me).parents("tr").animateRemove();return true;})(this)'})]
+       ]       
+       </B_nb_evenement>
+       </div>
+
+</BOUCLE_almanach>
\ No newline at end of file
diff --git a/www/plugins/import_ics/prive/objets/liste/almanachs.html b/www/plugins/import_ics/prive/objets/liste/almanachs.html
new file mode 100644 (file)
index 0000000..5caae9f
--- /dev/null
@@ -0,0 +1,55 @@
+[(#SET{defaut_tri,#ARRAY{
+       titre,1, 
+       date,-1, 
+       id_almanach,1,
+       points,-1
+}})]<B_liste_almanachs>
+#ANCRE_PAGINATION
+<div class="liste-objets almanachs">
+<table class='spip liste'>
+       [<caption><strong class="caption">(#ENV*{titre,#GRAND_TOTAL|singulier_ou_pluriel{almanach:info_1_almanach,almanach:info_nb_almanachs}})</strong></caption>]
+       <thead>
+               <tr class='first_row'>
+                       <th class='picto' scope='col'></th>
+                       <th class='statut' scope='col'>[(#TRI{statut,<span title="<:lien_trier_statut|attribut_html:>">#</span>,ajax})]</th> 
+                       <th class='titre' scope='col'>[(#TRI{titre,<:almanach:label_titre:>,ajax})]</th>
+                       <th class='utilisations' scope='col'></th>
+                       <th class='action' scope='col'></th>
+                       <th class='date' scope='col'>[(#TRI{date,<:date:>,ajax})]</th> 
+                       <th class='id' scope='col'>[(#TRI{id_almanach,<:info_numero_abbreviation:>,ajax})]</th>
+               </tr>
+       </thead>
+       <tbody>
+       <BOUCLE_liste_almanachs(ALMANACHS){id_article?}{id_mot?}{id_auteur?}{where?}{statut?}{recherche?}{tri #ENV{par,num titre},#GET{defaut_tri}}{par titre}{pagination #ENV{nb,10}}>
+               <tr class='[(#COMPTEUR_BOUCLE|alterner{row_odd,row_even})]'>
+                       <td class='picto'>[(#CHEMIN_IMAGE{almanach-16.png}|balise_img)]</td>
+                       <td class='statut'>[(#STATUT|puce_statut{almanach,#ID_ALMANACH})]</td> 
+                       <td class='titre principale'>[(#LOGO_ALMANACH|image_reduire{20,26})]<a href="[(#ID_ALMANACH|generer_url_entite{almanach})]" title="<:info_numero_abbreviation|attribut_html:> #ID_ALMANACH">[(#RANG). ]#TITRE</a></td>
+                       <td class='utilisations secondaire'>
+                       <!--on compte le nombre d'événemnts liés à cet almanach et on regarde donc s'il est utilisé-->
+                       <BOUCLE_combien(spip_almanachs_liens) {id_almanach}></BOUCLE_combien>
+                       #SET{utilise,#TOTAL_BOUCLE}
+                       [(#TOTAL_BOUCLE|singulier_ou_pluriel{almanach:un_evenement,almanach:plusieurs_evenements})]
+                       <//B_combien>
+                       </td>
+                       <td class='action'>
+                               [(#GET{utilise}|=={0}|oui)
+                                       [(#AUTORISER{supprimer, almanach, #ID_ALMANACH}|oui)
+                                               [(#BOUTON_ACTION{<:almanach:info_supprimer_almanach:>,#URL_ACTION_AUTEUR{supprimer_almanach,#ID_ALMANACH,#SELF},ajax,'','','(function(me){$(me).parents("tr").animateRemove();return true;})(this)'})]
+                                       ]
+                               ]
+                       </td>
+                       <td class='date secondaire'>[(#DATE|affdate_jourcourt)]</td> 
+                       <td class='id'>[(#AUTORISER{modifier,almanach,#ID_ALMANACH}|?{
+                               <a href="[(#URL_ECRIRE{almanach_edit,id_almanach=#ID_ALMANACH})]">#ID_ALMANACH</a>,
+                               #ID_ALMANACH
+                       })]</td>
+               </tr>
+       </BOUCLE_liste_almanachs>
+       </tbody>
+</table>
+[<p class='pagination'>(#PAGINATION{prive})</p>]
+</div>
+</B_liste_almanachs>[
+<div class="liste-objets almanachs caption-wrap"><strong class="caption">(#ENV*{sinon,''})</strong></div>
+]<//B_liste_almanachs>
\ No newline at end of file
diff --git a/www/plugins/import_ics/prive/themes/spip/images/almanach-12.png b/www/plugins/import_ics/prive/themes/spip/images/almanach-12.png
new file mode 100644 (file)
index 0000000..79b552a
Binary files /dev/null and b/www/plugins/import_ics/prive/themes/spip/images/almanach-12.png differ
diff --git a/www/plugins/import_ics/prive/themes/spip/images/almanach-16.png b/www/plugins/import_ics/prive/themes/spip/images/almanach-16.png
new file mode 100644 (file)
index 0000000..79b552a
Binary files /dev/null and b/www/plugins/import_ics/prive/themes/spip/images/almanach-16.png differ
diff --git a/www/plugins/import_ics/prive/themes/spip/images/almanach-24.png b/www/plugins/import_ics/prive/themes/spip/images/almanach-24.png
new file mode 100644 (file)
index 0000000..5f2ee2f
Binary files /dev/null and b/www/plugins/import_ics/prive/themes/spip/images/almanach-24.png differ
diff --git a/www/plugins/import_ics/prive/themes/spip/images/almanach-32.png b/www/plugins/import_ics/prive/themes/spip/images/almanach-32.png
new file mode 100644 (file)
index 0000000..6758862
Binary files /dev/null and b/www/plugins/import_ics/prive/themes/spip/images/almanach-32.png differ
diff --git a/www/plugins/import_ics/prive/themes/spip/images/almanach-add-16.png b/www/plugins/import_ics/prive/themes/spip/images/almanach-add-16.png
new file mode 100644 (file)
index 0000000..2f58755
Binary files /dev/null and b/www/plugins/import_ics/prive/themes/spip/images/almanach-add-16.png differ
diff --git a/www/plugins/import_ics/prive/themes/spip/images/almanach-add-24.png b/www/plugins/import_ics/prive/themes/spip/images/almanach-add-24.png
new file mode 100644 (file)
index 0000000..150fca0
Binary files /dev/null and b/www/plugins/import_ics/prive/themes/spip/images/almanach-add-24.png differ
diff --git a/www/plugins/import_ics/prive/themes/spip/images/almanach-add-32.png b/www/plugins/import_ics/prive/themes/spip/images/almanach-add-32.png
new file mode 100644 (file)
index 0000000..e157dfc
Binary files /dev/null and b/www/plugins/import_ics/prive/themes/spip/images/almanach-add-32.png differ
diff --git a/www/plugins/import_ics/prive/themes/spip/images/almanach-del-16.png b/www/plugins/import_ics/prive/themes/spip/images/almanach-del-16.png
new file mode 100644 (file)
index 0000000..14b2514
Binary files /dev/null and b/www/plugins/import_ics/prive/themes/spip/images/almanach-del-16.png differ
diff --git a/www/plugins/import_ics/prive/themes/spip/images/almanach-del-24.png b/www/plugins/import_ics/prive/themes/spip/images/almanach-del-24.png
new file mode 100644 (file)
index 0000000..34a29a8
Binary files /dev/null and b/www/plugins/import_ics/prive/themes/spip/images/almanach-del-24.png differ
diff --git a/www/plugins/import_ics/prive/themes/spip/images/almanach-del-32.png b/www/plugins/import_ics/prive/themes/spip/images/almanach-del-32.png
new file mode 100644 (file)
index 0000000..6e6d8c7
Binary files /dev/null and b/www/plugins/import_ics/prive/themes/spip/images/almanach-del-32.png differ
diff --git a/www/plugins/import_ics/prive/themes/spip/images/almanach-edit-16.png b/www/plugins/import_ics/prive/themes/spip/images/almanach-edit-16.png
new file mode 100644 (file)
index 0000000..402e3a8
Binary files /dev/null and b/www/plugins/import_ics/prive/themes/spip/images/almanach-edit-16.png differ
diff --git a/www/plugins/import_ics/prive/themes/spip/images/almanach-edit-24.png b/www/plugins/import_ics/prive/themes/spip/images/almanach-edit-24.png
new file mode 100644 (file)
index 0000000..d115451
Binary files /dev/null and b/www/plugins/import_ics/prive/themes/spip/images/almanach-edit-24.png differ
diff --git a/www/plugins/import_ics/prive/themes/spip/images/almanach-edit-32.png b/www/plugins/import_ics/prive/themes/spip/images/almanach-edit-32.png
new file mode 100644 (file)
index 0000000..239d000
Binary files /dev/null and b/www/plugins/import_ics/prive/themes/spip/images/almanach-edit-32.png differ
diff --git a/www/plugins/import_ics/prive/themes/spip/images/almanach-new-16.png b/www/plugins/import_ics/prive/themes/spip/images/almanach-new-16.png
new file mode 100644 (file)
index 0000000..0c50f7d
Binary files /dev/null and b/www/plugins/import_ics/prive/themes/spip/images/almanach-new-16.png differ
diff --git a/www/plugins/import_ics/prive/themes/spip/images/almanach-new-24.png b/www/plugins/import_ics/prive/themes/spip/images/almanach-new-24.png
new file mode 100644 (file)
index 0000000..59c11ae
Binary files /dev/null and b/www/plugins/import_ics/prive/themes/spip/images/almanach-new-24.png differ
diff --git a/www/plugins/import_ics/prive/themes/spip/images/almanach-new-32.png b/www/plugins/import_ics/prive/themes/spip/images/almanach-new-32.png
new file mode 100644 (file)
index 0000000..286b0a0
Binary files /dev/null and b/www/plugins/import_ics/prive/themes/spip/images/almanach-new-32.png differ
diff --git a/www/plugins/import_ics/prive/themes/spip/images/import_ics-128.png b/www/plugins/import_ics/prive/themes/spip/images/import_ics-128.png
new file mode 100644 (file)
index 0000000..854b0a0
Binary files /dev/null and b/www/plugins/import_ics/prive/themes/spip/images/import_ics-128.png differ
diff --git a/www/plugins/import_ics/prive/themes/spip/images/import_ics-32.png b/www/plugins/import_ics/prive/themes/spip/images/import_ics-32.png
new file mode 100644 (file)
index 0000000..854b0a0
Binary files /dev/null and b/www/plugins/import_ics/prive/themes/spip/images/import_ics-32.png differ
diff --git a/www/plugins/import_ics/prive/themes/spip/images/import_ics-64.png b/www/plugins/import_ics/prive/themes/spip/images/import_ics-64.png
new file mode 100644 (file)
index 0000000..854b0a0
Binary files /dev/null and b/www/plugins/import_ics/prive/themes/spip/images/import_ics-64.png differ
diff --git a/www/plugins/import_ics/svn.revision b/www/plugins/import_ics/svn.revision
new file mode 100644 (file)
index 0000000..47cb340
--- /dev/null
@@ -0,0 +1,10 @@
+<svn_revision>
+<text_version>
+Origine: file:///home/svn/repository/spip-zone/_plugins_/import_ics
+Revision: 83395
+Dernier commit: 2014-06-20 01:59:14 +0200 
+</text_version>
+<origine>file:///home/svn/repository/spip-zone/_plugins_/import_ics</origine>
+<revision>83395</revision>
+<commit>2014-06-20 01:59:14 +0200 </commit>
+</svn_revision>
\ No newline at end of file
diff --git a/www/plugins/pages/base/pages_tables.php b/www/plugins/pages/base/pages_tables.php
new file mode 100644 (file)
index 0000000..4b2d4a7
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+/**
+ * Déclarer le champ supplémentaire sur spip_articles
+ *
+ * @plugin     Pages Uniques
+ * @copyright  2013
+ * @author     RastaPopoulos
+ * @licence    GNU/GPL
+ * @package    SPIP\Pages\Pipelines
+ * @link       http://contrib.spip.net/Pages-uniques
+ */
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+function pages_declarer_tables_objets_sql($tables){
+
+       $tables['spip_articles']['field']['page'] = "VARCHAR(255) DEFAULT '' NOT NULL";
+       return $tables;
+
+}
+
+?>
diff --git a/www/plugins/pages/content/articles-resume.html b/www/plugins/pages/content/articles-resume.html
new file mode 100644 (file)
index 0000000..b3d1f9f
--- /dev/null
@@ -0,0 +1,12 @@
+<B_articles>
+<div class="liste resume articles">
+       #ANCRE_PAGINATION
+       <h2 class="h2">[(#ENV{titre,<:derniers_articles:>})]</h2>
+       <ul class="liste-items">
+               <BOUCLE_articles(ARTICLES){id_rubrique?} {id_rubrique >= 0} {!par date} {pagination #ENV{nb,5}}>
+               #INCLURE{fond=inclure/article-resume,id_article}
+               </BOUCLE_articles>
+       </ul>
+       [<p class="pagination">(#PAGINATION)</p>]
+</div>
+</B_articles>
diff --git a/www/plugins/pages/demo/inc-articles.html b/www/plugins/pages/demo/inc-articles.html
new file mode 100644 (file)
index 0000000..f7cbd77
--- /dev/null
@@ -0,0 +1,10 @@
+<B_articles>
+<ul>
+<BOUCLE_articles(ARTICLES) {id_article?}{id_rubrique?}{where?}{statut?}>
+       <li>=> #ID_ARTICLE - #TITRE \(#STATUT\)</li>
+</BOUCLE_articles>
+</ul>
+</B_articles>
+       <p>Aucun article</p>
+<//B_articles>
+<br />
diff --git a/www/plugins/pages/demo/pages.html b/www/plugins/pages/demo/pages.html
new file mode 100644 (file)
index 0000000..eb37d3f
--- /dev/null
@@ -0,0 +1,129 @@
+<h1>BOUCLES PAGES UNIQUES</h1>
+<h2>BOUCLE_p1(ARTICLES) {page=mentions_legales}{id_rubrique=-1}</h2>
+<B_p1>
+<ul>
+<BOUCLE_p1(ARTICLES) {page=mentions_legales}{id_rubrique=-1}>
+       <li>=> #ID_ARTICLE - #TITRE \(#STATUT\)</li>
+</BOUCLE_p1>
+</ul>
+</B_p1>
+       <p>Aucun article</p>
+<//B_p1>
+<br />
+
+<h2>BOUCLE_p2(ARTICLES) {page?}</h2>
+<B_p2>
+<ul>
+<BOUCLE_p2(ARTICLES) {page?}>
+       <li>=> #ID_ARTICLE - #TITRE \(#STATUT\)</li>
+</BOUCLE_p2>
+</ul>
+</B_p2>
+       <p>Aucun article</p>
+<//B_p2>
+<br />
+
+<h2>BOUCLE_p3(ARTICLES) {page}</h2>
+<B_p3>
+<ul>
+<BOUCLE_p3(ARTICLES) {page}>
+       <li>=> #ID_ARTICLE - #TITRE \(#STATUT\)</li>
+</BOUCLE_p3>
+</ul>
+</B_p3>
+       <p>Aucun article</p>
+<//B_p3>
+<br />
+
+<h2>BOUCLE_p4(ARTICLES){id_rubrique<0}</h2>
+<B_p4>
+<ul>
+<BOUCLE_p4(ARTICLES){id_rubrique<0}>
+       <li>=> #ID_ARTICLE - #TITRE \(#STATUT\)</li>
+</BOUCLE_p4>
+</ul>
+</B_p4>
+       <p>Aucun article</p>
+<//B_p4>
+<br />
+
+<h2>BOUCLE_p5(ARTICLES){id_rubrique=-1}</h2>
+<B_p5>
+<ul>
+<BOUCLE_p5(ARTICLES){id_rubrique=-1}>
+       <li>=> #ID_ARTICLE - #TITRE \(#STATUT\)</li>
+</BOUCLE_p5>
+</ul>
+</B_p5>
+       <p>Aucun article</p>
+<//B_p5>
+<br />
+
+<h2>BOUCLE_p6(ARTICLES){id_rubrique=-1}{statut?}</h2>
+<B_p6>
+<ul>
+<BOUCLE_p6(ARTICLES){id_rubrique=-1}{statut?}>
+       <li>=> #ID_ARTICLE - #TITRE \(#STATUT\)</li>
+</BOUCLE_p6>
+</ul>
+</B_p6>
+       <p>Aucun article</p>
+<//B_p6>
+<br />
+
+
+<h1>BOUCLES ARTICLES SANS INCLURE</h1>
+<h2>BOUCLE_a1(ARTICLES)</h2>
+<B_a1>
+<ul>
+<BOUCLE_a1(ARTICLES)>
+       <li>=> #ID_ARTICLE - #TITRE \(#STATUT\)</li>
+</BOUCLE_a1>
+</ul>
+</B_a1>
+       <p>Aucun article</p>
+<//B_a1>
+<br />
+
+<h2>BOUCLE_a2(ARTICLES){id_rubrique=2}</h2>
+<B_a2>
+<ul>
+<BOUCLE_a2(ARTICLES){id_rubrique=2}>
+       <li>=> #ID_ARTICLE - #TITRE</li>
+</BOUCLE_a2>
+</ul>
+</B_a2>
+       <p>Aucun article</p>
+<//B_a2>
+<br />
+
+<h2>BOUCLE_a2(ARTICLES){page=''}</h2>
+<B_a3>
+<ul>
+<BOUCLE_a3(ARTICLES){page=''}>
+       <li>=> #ID_ARTICLE - #TITRE</li>
+</BOUCLE_a3>
+</ul>
+</B_a3>
+       <p>Aucun article</p>
+<//B_a3>
+<br />
+
+
+<h1>BOUCLES ARTICLES AVEC INCLURE inc-articles</h1>
+<h2>BOUCLE -> article : inclure rubrique=1</h2>
+<INCLURE{fond=demo/inc-articles,
+       id_rubrique=1,
+       env} />
+<br />
+
+<h2>BOUCLE -> article : inclure rubrique=2</h2>
+<INCLURE{fond=demo/inc-articles,
+       id_rubrique=2,
+       env} />
+<br />
+
+<h2>BOUCLE -> article : aucun parametre</h2>
+<INCLURE{fond=demo/inc-articles,
+       env} />
+<br />
diff --git a/www/plugins/pages/formulaires/editer_identifiant_page.html b/www/plugins/pages/formulaires/editer_identifiant_page.html
new file mode 100644 (file)
index 0000000..94b725b
--- /dev/null
@@ -0,0 +1,40 @@
+<div class="formulaire_spip formulaire_dater formulaire_#FORM formulaire_#FORM-#ENV{objet}-#ENV{id,nouveau}">
+       [<p class="reponse_formulaire reponse_formulaire_ok">(#ENV*{message_ok})</p>]
+       [<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV*{message_erreur})</p>]
+       [(#ENV{editable})
+       <form method='post' action='#ENV{action}'><div>
+               [(#REM) declarer les hidden qui declencheront le service du formulaire
+               parametre : url d'action ]
+               #ACTION_FORMULAIRE{#ENV{action}}
+       ]
+               <ul>
+                       #SET{name,champ_page} #SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}} #SET{obli,''}
+                       <li class="editer editer_[(#GET{name})][ (#GET{obli})][ (#GET{erreurs}|oui)erreur]">
+                               <label for="champ_#GET{name}"><:pages:label_champ_page:></label>
+                               [<span class="erreur_message">(#GET{erreurs})</span>]
+                               <span[ (#ENV{#GET{name}}|oui)class="affiche"][(#ENV{_saisie_en_cours}|oui)style="display:none;"]>
+                                       [(#ENV{#GET{name}}|sinon{<em><:pagesbis:info_aucun_champ_page:></em>})]
+                               </span>
+       [(#ENV{editable})
+                               <span class="toggle_box_link"[(#ENV{_saisie_en_cours}|oui)style="display:none;"]>
+                                       &#91;<a href="#"
+                                               onclick="var f=jQuery(this).parents('form').eq(0);f.find('li .input').show('fast').siblings('span').hide('fast');f.find('.boutons').show('fast');f.find('input.page').eq(0).focus();return false;"><:bouton_changer:></a>&#93;
+                               </span>
+                               <span class="input"[(#ENV{_saisie_en_cours}|non)style="display:none;"]>
+                                       <input type="text" class="text page" name="#GET{name}" id="champ_#GET{name}" value="#ENV{#GET{name}}" size="40"/>
+                               </span>
+       ]
+                       </li>
+               </ul>
+       [(#ENV{editable})
+       [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ]
+       <!--extra-->
+       <p class='boutons'[(#ENV{_saisie_en_cours}|non)style="display:none;"]>
+                       <span class='image_loading'>&nbsp;</span>
+                       <input type='submit' class='over' name='changer' value='<:bouton_changer:>' />
+                       <input type='submit' class='submit' name='annuler' value='<:bouton_annuler:>' />
+                       <input type='submit' class='submit' name='changer' value='<:bouton_changer:>' />
+               </p>
+       </div></form>
+       ]
+</div>
diff --git a/www/plugins/pages/formulaires/editer_identifiant_page.php b/www/plugins/pages/formulaires/editer_identifiant_page.php
new file mode 100644 (file)
index 0000000..6df164b
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Editer l'identifiant page d'un article
+ *
+ * @plugin     Pages Uniques
+ * @copyright  2013
+ * @author     RastaPopoulos
+ * @licence    GNU/GPL
+ * @package    SPIP\Pages\Formulaires
+ * @link       http://contrib.spip.net/Pages-uniques
+ */
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+function formulaires_editer_identifiant_page_charger($id_article, $retour=''){
+       $valeurs['champ_page'] = generer_info_entite($id_article,'article','page');
+       $valeurs['_saisie_en_cours'] = (_request('champ_page')!==null);
+       return $valeurs;
+}
+
+/**
+ * Identifier le formulaire en faisant abstraction des parametres qui
+ * ne representent pas l'objet edite
+ */
+function formulaires_editer_identifiant_page_identifier_dist($id_article, $retour=''){
+       return serialize(array('article', $id_article));
+}
+
+/**
+ * Verification avant traitement
+ *
+ * @param integer $id_article
+ * @param string $retour
+ * @return Array Tableau des erreurs
+ */
+function formulaires_editer_identifiant_page_verifier_dist($id_article, $retour=''){
+       $erreurs = array();
+/*
+       if ($page = _request('champ_page')) {
+               // nombre de charactères : 40 max
+               if (strlen($page) > 40)
+                        $erreurs['champ_page'] = _T('pages:erreur_champ_page_taille');
+               // format : charactères alphanumériques en minuscules ou "_"
+               elseif (!preg_match('/^[a-z0-9_]+$/', $page))
+                        $erreurs['champ_page'] = _T('pages:erreur_champ_page_format');
+               // doublon
+               elseif (sql_countsel(table_objet_sql('article'), "page=".sql_quote($page) . " AND id_article!=".intval($id_article)))
+                       $erreurs['champ_page'] = _T('pages:erreur_champ_page_doublon');
+       }
+*/
+       return $erreurs;
+}
+
+/**
+ * Traitement 
+ *
+ * @param integer $id_article
+ * @param string $retour
+ * @return Array
+ */
+function formulaires_editer_identifiant_page_traiter_dist($id_article, $retour=''){
+
+       if (
+               _request('changer')
+               and $page = _request('champ_page')
+       ) {
+               include_spip('action/editer_objet');
+               objet_modifier('article',$id_article,array('page'=>$page));
+       }
+
+       set_request('champ_page');
+       $res['editable'] = true;
+       if ($retour)
+               $res['redirect'] = $retour;
+
+       return $res;
+}
+
+?>
diff --git a/www/plugins/pages/images/page-128.png b/www/plugins/pages/images/page-128.png
new file mode 100644 (file)
index 0000000..eedf501
Binary files /dev/null and b/www/plugins/pages/images/page-128.png differ
diff --git a/www/plugins/pages/lang/pages.xml b/www/plugins/pages/lang/pages.xml
new file mode 100644 (file)
index 0000000..2772e78
--- /dev/null
@@ -0,0 +1,26 @@
+<traduction module="pages" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/pages/trunk/lang/" reference="fr">
+       <langue code="ar" url="http://trad.spip.net/tradlang_module/pages?lang_cible=ar" total="12" traduits="6" relire="0" modifs="2" nouveaux="4" pourcent="50.00">
+               <traducteur nom="George" lien="http://trad.spip.net/auteur/جورج-قندلفت" />
+       </langue>
+       <langue code="en" url="http://trad.spip.net/tradlang_module/pages?lang_cible=en" total="12" traduits="12" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
+               <traducteur nom="tcharlss" lien="http://trad.spip.net/auteur/drbouvierleduc" />
+       </langue>
+       <langue code="es" url="http://trad.spip.net/tradlang_module/pages?lang_cible=es" total="12" traduits="12" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Raquel S. Bujaldón" lien="http://trad.spip.net/auteur/raquel-s-bujaldon" />
+       </langue>
+       <langue code="fa" url="http://trad.spip.net/tradlang_module/pages?lang_cible=fa" total="12" traduits="6" relire="0" modifs="2" nouveaux="4" pourcent="50.00">
+               <traducteur nom="Davood Hossein" lien="http://trad.spip.net/auteur/davood-hossein" />
+       </langue>
+       <langue code="fr" url="http://trad.spip.net/tradlang_module/pages?lang_cible=fr" total="12" traduits="12" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       </langue>
+       <langue code="nl" url="http://trad.spip.net/tradlang_module/pages?lang_cible=nl" total="12" traduits="12" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
+       </langue>
+       <langue code="ru" url="http://trad.spip.net/tradlang_module/pages?lang_cible=ru" total="12" traduits="6" relire="0" modifs="2" nouveaux="4" pourcent="50.00">
+               <traducteur nom="Serge Markitanenko" lien="http://trad.spip.net/auteur/serge-markitanenko" />
+       </langue>
+       <langue code="sk" url="http://trad.spip.net/tradlang_module/pages?lang_cible=sk" total="12" traduits="12" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="jaro" lien="http://trad.spip.net/auteur/jaro" />
+       </langue>
+</traduction>
diff --git a/www/plugins/pages/lang/pages_ar.php b/www/plugins/pages/lang/pages_ar.php
new file mode 100644 (file)
index 0000000..c8b1af8
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/pages?lang_cible=ar
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucune_page' => 'لا توجد صفحات في هذه اللحظة.', # MODIF
+
+       // C
+       'convertir_article' => 'تحويل الى مقال',
+       'convertir_page' => 'تحويل الى صفحة',
+       'creer_page' => 'إنشاء صفحة جديدة',
+
+       // M
+       'modifier_page' => 'تغيير الصفحة :', # MODIF
+
+       // P
+       'pages_uniques' => 'صفحات فريدة',
+
+       // T
+       'titre_page' => 'صفحة',
+       'toutes_les_pages' => 'كل الصفحات'
+);
+
+?>
diff --git a/www/plugins/pages/lang/pages_en.php b/www/plugins/pages/lang/pages_en.php
new file mode 100644 (file)
index 0000000..3c9900a
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/pages?lang_cible=en
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucune_page' => 'There are no pages at the moment.',
+
+       // C
+       'convertir_article' => 'Convert to an article',
+       'convertir_page' => 'Convert to a page',
+       'creer_page' => 'Create a new page',
+
+       // E
+       'erreur_champ_page_doublon' => 'That ID is already in use',
+       'erreur_champ_page_format' => 'Lowercase alphanumerical characters or "_" only',
+       'erreur_champ_page_taille' => 'Up to 255 characters max',
+
+       // L
+       'label_champ_page' => 'Page :',
+
+       // M
+       'modifier_page' => 'Edit page:',
+
+       // P
+       'pages_uniques' => 'Unique pages',
+
+       // T
+       'titre_page' => 'Page',
+       'toutes_les_pages' => 'All pages'
+);
+
+?>
diff --git a/www/plugins/pages/lang/pages_es.php b/www/plugins/pages/lang/pages_es.php
new file mode 100644 (file)
index 0000000..8b38c1b
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/pages?lang_cible=es
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucune_page' => 'No hay ninguna página por el momento.',
+
+       // C
+       'convertir_article' => 'Convertir en artículo',
+       'convertir_page' => 'Convertir en página',
+       'creer_page' => 'Crear una nueva página',
+
+       // E
+       'erreur_champ_page_doublon' => 'Este identificador ya existe',
+       'erreur_champ_page_format' => 'Caracteres alfanuméricos en minúsculas o "_" únicamente',
+       'erreur_champ_page_taille' => '255 caracteres máximo',
+
+       // L
+       'label_champ_page' => 'Página:',
+
+       // M
+       'modifier_page' => 'Modificar la página:',
+
+       // P
+       'pages_uniques' => 'Páginas únicas',
+
+       // T
+       'titre_page' => 'Página',
+       'toutes_les_pages' => 'Todas las páginas'
+);
+
+?>
diff --git a/www/plugins/pages/lang/pages_fa.php b/www/plugins/pages/lang/pages_fa.php
new file mode 100644 (file)
index 0000000..ec5a92f
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/pages?lang_cible=fa
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucune_page' => ' براي الأن هيچ صفحه‌ي نيست', # MODIF
+
+       // C
+       'convertir_article' => 'تبديل به يك مقاله',
+       'convertir_page' => 'تبديل به يك صفحه',
+       'creer_page' => 'آفرينش يك صفحه‌ي نو',
+
+       // M
+       'modifier_page' => 'اصلاح صفحه: ', # MODIF
+
+       // P
+       'pages_uniques' => 'صفحه‌هاي تك',
+
+       // T
+       'titre_page' => 'صفحه',
+       'toutes_les_pages' => 'تمام صفه‌ها'
+);
+
+?>
diff --git a/www/plugins/pages/lang/pages_fr.php b/www/plugins/pages/lang/pages_fr.php
new file mode 100644 (file)
index 0000000..d4a286d
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// Fichier source, a modifier dans svn://zone.spip.org/spip-zone/_plugins_/pages/trunk/lang/
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucune_page' => 'Il n’y a aucune page pour l’instant.',
+
+       // C
+       'convertir_article' => 'Convertir en article',
+       'convertir_page' => 'Convertir en page',
+       'creer_page' => 'Créer une nouvelle page',
+
+       // E
+       'erreur_champ_page_doublon' => 'Cet identifiant existe déjà',
+       'erreur_champ_page_format' => 'Charactères alphanumériques en minuscules ou "_" uniquement',
+       'erreur_champ_page_taille' => '255 charactères maximum',
+
+       // L
+       'label_champ_page' => 'Page :',
+
+       // M
+       'modifier_page' => 'Modifier la page :',
+
+       // P
+       'pages_uniques' => 'Pages uniques',
+
+       // T
+       'titre_page' => 'Page',
+       'toutes_les_pages' => 'Toutes les pages'
+);
+
+?>
diff --git a/www/plugins/pages/lang/pages_nl.php b/www/plugins/pages/lang/pages_nl.php
new file mode 100644 (file)
index 0000000..b0d85e0
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/pages?lang_cible=nl
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucune_page' => 'Er is momenteel geen enkele bladzijde.',
+
+       // C
+       'convertir_article' => 'In een artikel omzetten',
+       'convertir_page' => 'In een bladzijde omzetten',
+       'creer_page' => 'Een nieuwe bladzijde maken',
+
+       // E
+       'erreur_champ_page_doublon' => 'Deze identificatie bestaat al',
+       'erreur_champ_page_format' => 'Uitsluitend kleine letters, cijfers of "_"',
+       'erreur_champ_page_taille' => 'maximaal 255 tekens',
+
+       // L
+       'label_champ_page' => 'Bladzijde:',
+
+       // M
+       'modifier_page' => 'Aanpassen van bladzijde:',
+
+       // P
+       'pages_uniques' => 'Unieke bladzijdes',
+
+       // T
+       'titre_page' => 'Bladzijde',
+       'toutes_les_pages' => 'Alle bladzijdes'
+);
+
+?>
diff --git a/www/plugins/pages/lang/pages_ru.php b/www/plugins/pages/lang/pages_ru.php
new file mode 100644 (file)
index 0000000..90c1053
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/pages?lang_cible=ru
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucune_page' => 'Пока нет страниц.', # MODIF
+
+       // C
+       'convertir_article' => 'Преобразовать в статью',
+       'convertir_page' => 'Преобразовать в страницу',
+       'creer_page' => 'Новая страница',
+
+       // M
+       'modifier_page' => 'Изменить страницу:', # MODIF
+
+       // P
+       'pages_uniques' => 'Отдельные страницы',
+
+       // T
+       'titre_page' => 'Страница',
+       'toutes_les_pages' => 'Все страницы'
+);
+
+?>
diff --git a/www/plugins/pages/lang/pages_sk.php b/www/plugins/pages/lang/pages_sk.php
new file mode 100644 (file)
index 0000000..ac4220c
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/pages?lang_cible=sk
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucune_page' => 'Momentálne tu nie je žiadna stránka.',
+
+       // C
+       'convertir_article' => 'Zmeniť na článok',
+       'convertir_page' => 'Zmeniť na stránku',
+       'creer_page' => 'Vytvoriť novú stránku',
+
+       // E
+       'erreur_champ_page_doublon' => 'Tento identifikátor už existuje',
+       'erreur_champ_page_format' => 'Malé písmená alebo jeden "_"',
+       'erreur_champ_page_taille' => 'Maximum je 255 znakov',
+
+       // L
+       'label_champ_page' => 'Stránka:',
+
+       // M
+       'modifier_page' => 'Upraviť stránku:',
+
+       // P
+       'pages_uniques' => 'Jedinečné stránky',
+
+       // T
+       'titre_page' => 'Stránka',
+       'toutes_les_pages' => 'Všetky stránky'
+);
+
+?>
diff --git a/www/plugins/pages/lang/paquet-pages.xml b/www/plugins/pages/lang/paquet-pages.xml
new file mode 100644 (file)
index 0000000..1dbb464
--- /dev/null
@@ -0,0 +1,23 @@
+<traduction module="paquet-pages" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/pages/trunk/lang/" reference="fr">
+       <langue code="ar" url="http://trad.spip.net/tradlang_module/paquet-pages?lang_cible=ar" total="2" traduits="2" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       </langue>
+       <langue code="en" url="http://trad.spip.net/tradlang_module/paquet-pages?lang_cible=en" total="2" traduits="2" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       </langue>
+       <langue code="es" url="http://trad.spip.net/tradlang_module/paquet-pages?lang_cible=es" total="2" traduits="2" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Raquel S. Bujaldón" lien="http://trad.spip.net/auteur/raquel-s-bujaldon" />
+       </langue>
+       <langue code="fa" url="http://trad.spip.net/tradlang_module/paquet-pages?lang_cible=fa" total="2" traduits="2" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Davood Hossein" lien="http://trad.spip.net/auteur/davood-hossein" />
+       </langue>
+       <langue code="fr" url="http://trad.spip.net/tradlang_module/paquet-pages?lang_cible=fr" total="2" traduits="2" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       </langue>
+       <langue code="nl" url="http://trad.spip.net/tradlang_module/paquet-pages?lang_cible=nl" total="2" traduits="2" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
+       </langue>
+       <langue code="ru" url="http://trad.spip.net/tradlang_module/paquet-pages?lang_cible=ru" total="2" traduits="2" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Serge Markitanenko" lien="http://trad.spip.net/auteur/serge-markitanenko" />
+       </langue>
+       <langue code="sk" url="http://trad.spip.net/tradlang_module/paquet-pages?lang_cible=sk" total="2" traduits="2" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="jaro" lien="http://trad.spip.net/auteur/jaro" />
+       </langue>
+</traduction>
diff --git a/www/plugins/pages/lang/paquet-pages_ar.php b/www/plugins/pages/lang/paquet-pages_ar.php
new file mode 100644 (file)
index 0000000..93f5e6f
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-pages?lang_cible=ar
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // P
+       'pages_description' => 'هذا البرنامج المساعد يسمح لك إنشاء صفحات من البنود التي لا ترتبط بأي تسلسل معين. بيد أنها قد تترافق مع اسم القالب
+يأذن للإنشاء صفحة من المعلومات القانونية ، من نحن ، اتصال ، وما إلى ذلك.
+',
+       'pages_slogan' => ' '
+);
+
+?>
diff --git a/www/plugins/pages/lang/paquet-pages_en.php b/www/plugins/pages/lang/paquet-pages_en.php
new file mode 100644 (file)
index 0000000..763b2bf
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-pages?lang_cible=en
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // P
+       'pages_description' => 'This plugin allows you to create pages of articles that are not linked to any particular hierarchy.
+However they may be associated with a name template.
+This allows the creation of pages of legal information, about, contact, etc..
+',
+       'pages_slogan' => 'Unlinked pages'
+);
+
+?>
diff --git a/www/plugins/pages/lang/paquet-pages_es.php b/www/plugins/pages/lang/paquet-pages_es.php
new file mode 100644 (file)
index 0000000..40b6eb7
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-pages?lang_cible=es
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // P
+       'pages_description' => 'Este plugin permite crear páginas de artículos que no están asociadas a ninguna jerarquía particular.
+Por contra, pueden asociarse a un nombre de esqueleto. Ello permite crear páginas de aviso legal, acerca de, contacto, etc.',
+       'pages_slogan' => 'Páginas sin sección'
+);
+
+?>
diff --git a/www/plugins/pages/lang/paquet-pages_fa.php b/www/plugins/pages/lang/paquet-pages_fa.php
new file mode 100644 (file)
index 0000000..bb4d42b
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-pages?lang_cible=fa
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // P
+       'pages_description' => 'اين پلاگين اجازه‌ي ايجاد صفحه‌هاي مقاله‌هايي را مي‌دهد كه به هيچ سلسله مراتبي متكي نيستند. 
+در عضو مي‌توانند به نام يك اسلكت مرتبط شوند. 
+اين پلاگين اجازه‌ي ايجاد صفحه‌هايي مانند اطلاعات حقوقي، در باره‌ي ما، تماس با ما و غيره را خواهد داد.',
+       'pages_slogan' => 'صفحه‌هاي بدون بخش'
+);
+
+?>
diff --git a/www/plugins/pages/lang/paquet-pages_fr.php b/www/plugins/pages/lang/paquet-pages_fr.php
new file mode 100644 (file)
index 0000000..672ddd0
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// Fichier source, a modifier dans svn://zone.spip.org/spip-zone/_plugins_/pages/trunk/lang/
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // P
+       'pages_description' => 'Ce plugin permet de créer des pages d’articles qui ne sont reliées à aucune hiérarchie particulière.
+En revanche elles peuvent être associées à un nom de squelette.
+Cela permet notamment de créer des pages de notice légale, d’à-propos, de contact, etc.',
+       'pages_slogan' => 'Des pages sans rubrique'
+);
+
+?>
diff --git a/www/plugins/pages/lang/paquet-pages_nl.php b/www/plugins/pages/lang/paquet-pages_nl.php
new file mode 100644 (file)
index 0000000..645f1d4
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-pages?lang_cible=nl
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // P
+       'pages_description' => 'Met deze plugin kun je bladzijdes met artikelen maken die niet in de hiërarchie van de site zijn opgenomen.
+Ze kunnen worden gekoppeld aan de naam van een skelet.
+Op deze manier kunnen bladzijdes zoals wettelijke vermeldingen, contactinformatie, disclaimers, enz. worden gemaakt.',
+       'pages_slogan' => 'Bladzijdes zonder rubriek'
+);
+
+?>
diff --git a/www/plugins/pages/lang/paquet-pages_ru.php b/www/plugins/pages/lang/paquet-pages_ru.php
new file mode 100644 (file)
index 0000000..70ac5ea
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-pages?lang_cible=ru
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // P
+       'pages_description' => 'Данный плагин позволяет создавать отдельные страницы вне любых разделов сайта. Это отличное решения для размещения контактной информации, условий и правил использования, а так же для любой информации, которая логически не встраивается в структуру сайта.
+Cela permet notamment de créer des pages de notice légale, d’à-propos, de contact, etc.',
+       'pages_slogan' => 'Отдельные страницы'
+);
+
+?>
diff --git a/www/plugins/pages/lang/paquet-pages_sk.php b/www/plugins/pages/lang/paquet-pages_sk.php
new file mode 100644 (file)
index 0000000..b2917f6
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-pages?lang_cible=sk
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // P
+       'pages_description' => 'Tento zásuvný modul vám umožňuje vytvárať stránky s článkami, ktoré nemajú žiadnu konkrétnu hierarchickú štruktúru.
+Môžete ich však prepojiť s názvom šablóny.
+To vám umožňuje vytvárať stránky s informáciami právneho charakteru, časové osi, zmluvy, a i.',
+       'pages_slogan' => 'Stránky bez rubriky'
+);
+
+?>
diff --git a/www/plugins/pages/pages_administrations.php b/www/plugins/pages/pages_administrations.php
new file mode 100644 (file)
index 0000000..2511025
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Fichier gérant l'installation et désinstallation du plugin Pages Uniques
+ *
+ * @plugin     Pages
+ * @copyright  2013
+ * @author     RastaPopoulos 
+ * @licence    GNU/GPL
+ * @package    SPIP\Pages\Installation
+ * @link       http://contrib.spip.net/Pages-uniques
+ */
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+
+/**
+ * Fonction d'installation et de mise à jour du plugin
+ *
+ * @param string $nom_meta_base_version
+ *     Nom de la meta informant de la version du schéma de données du plugin installé dans SPIP
+ * @param string $version_cible
+ *     Version du schéma de données dans ce plugin (déclaré dans paquet.xml)
+ * @return void
+**/
+function pages_upgrade($nom_meta_base_version, $version_cible) {
+       $maj = array();
+
+       $maj['create'] = array(
+               array('maj_tables', 'spip_articles')
+       );
+       $maj['1.0.1'] = array(
+               array('sql_alter', "TABLE spip_articles CHANGE page page VARCHAR(255) DEFAULT '' NOT NULL"),
+       );
+
+       include_spip('base/upgrade');
+       maj_plugin($nom_meta_base_version, $version_cible, $maj);
+}
+
+
+/**
+ * Fonction de désinstallation du plugin
+ * Supprimer la colonne 'page' du plugin
+ *
+ * TODO : que deviennent les article avec un id_rubrique=-1 ? Ne faut-il pas les traiter ?
+ *
+ * @param string $nom_meta_base_version
+ *     Nom de la meta informant de la version du schéma de données du plugin installé dans SPIP
+ * @return void
+**/
+function pages_vider_tables($nom_meta_base_version) {
+       sql_alter("TABLE spip_articles DROP page");
+       effacer_meta($nom_meta_base_version);
+}
+
+?>
diff --git a/www/plugins/pages/pages_autorisations.php b/www/plugins/pages/pages_autorisations.php
new file mode 100644 (file)
index 0000000..2a8c26f
--- /dev/null
@@ -0,0 +1,161 @@
+<?php
+/**
+ * Fonction pour le pipeline, n'a rien a effectuer
+ *
+ * @return
+ */
+function pages_autoriser() {}
+
+
+/* ----------------------- AUTORISATIONS DE L'OBJET PAGE UNIQUE ----------------------- */
+
+/**
+ * Autorisation de créer un page unique.
+ *
+ * Cette page unique peut être créée soit à partir de rien
+ * soit en convertissant un article existant.
+ * Par défaut seuls les administrateurs complets sont autorisés.
+ *
+ * @param object $faire
+ * @param object $type
+ * @param object $id
+ * @param object $qui
+ * @param object $opt
+ * @return
+ */
+function autoriser_page_creer_dist($faire, $type, $id, $qui, $opt) {
+
+       // Conditions :
+       // - l'auteur connecté est un administrateur complet
+       $autoriser = pages_autorisation_defaut_dist($qui);
+
+       return $autoriser;
+}
+
+/**
+ * Autorisation de modifier une page unique existante.
+ *
+ * Cette page peut être modifiée soit au travers du formulaire d'édition
+ * soit en convertissant une page en article éditorial.
+ * Par défaut seuls les administrateurs complets sont autorisés.
+ *
+ * @param object $faire
+ * @param object $type
+ * @param object $id
+ * @param object $qui
+ * @param object $opt
+ * @return
+ */
+function autoriser_page_modifier_dist($faire, $type, $id, $qui, $opt) {
+
+       $autoriser = false;
+
+       // Conditions :
+       // - l'auteur connecté est un administrateur complet
+       if ($id_article = intval($id)) {
+               $autoriser = pages_autorisation_defaut_dist($qui);
+       }
+
+       return $autoriser;
+}
+
+
+/**
+ * Autorisation d'afficher une page unique.
+ *
+ * Par défaut seuls les administrateurs complets sont autorisés.
+ *
+ * @param object $faire
+ * @param object $type
+ * @param object $id
+ * @param object $qui
+ * @param object $opt
+ * @return
+ */
+function autoriser_page_voir_dist($faire, $type, $id, $qui, $opt) {
+
+       $autoriser = false;
+
+       // Conditions :
+       // - l'auteur connecté est un administrateur complet
+       if ($id_article = intval($id)) {
+               $autoriser = pages_autorisation_defaut_dist($qui);
+       }
+
+       return $autoriser;
+}
+
+
+/**
+ * Autorisation d'afficher la liste des pages uniques.
+ *
+ * Par défaut seuls les administrateurs complets sont autorisés.
+ *
+ * @param object $faire
+ * @param object $type
+ * @param object $id
+ * @param object $qui
+ * @param object $opt
+ * @return
+ */
+function autoriser_pages_voir_dist($faire, $type, $id, $qui, $opt) {
+
+       // Conditions :
+       // - l'auteur connecté est un administrateur complet
+       $autoriser = pages_autorisation_defaut_dist($qui);
+
+       return $autoriser;
+}
+
+
+/**
+ * Autorisation d'accéder à la liste des pages uniques.
+ *
+ * Cette autorisation coîncide avec l'autorisation pages_voir.
+ *
+ * @param object $faire
+ * @param object $type
+ * @param object $id
+ * @param object $qui
+ * @param object $opt
+ * @return
+ */
+function autoriser_pages_menu_dist($faire, $type, $id, $qui, $opt) {
+
+       // Conditions :
+       // - l'auteur connecté doit posséder l'autorisation pages_voir
+       $autoriser = autoriser('voir', '_pages', $id, $qui, $opt);
+
+       return $autoriser;
+}
+
+/**
+ * Autorisation d'afficher le bouton créer une page unique inclus
+ * dans la barre des outils rapides .
+ *
+ * Cette autorisation coîncide avec l'autorisation page_creer.
+ *
+ * @param object $faire
+ * @param object $type
+ * @param object $id
+ * @param object $qui
+ * @param object $opt
+ *
+*@return
+ */
+function autoriser_pagecreer_menu_dist($faire, $type, $id, $qui, $opt) {
+
+       // Conditions :
+       // - l'auteur connecté est un administrateur complet
+       $autoriser = autoriser('creer', 'page', $id, $qui, $opt);
+
+       return $autoriser;
+}
+
+
+function pages_autorisation_defaut_dist($qui) {
+       return (($qui['statut'] == '0minirezo')
+                       AND !$qui['restreint']);
+}
+
+?>
diff --git a/www/plugins/pages/pages_fonctions.php b/www/plugins/pages/pages_fonctions.php
new file mode 100644 (file)
index 0000000..9f1d50c
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Déclaration des filtres et balises
+ *
+ * @plugin     Pages
+ * @copyright  2013
+ * @author     RastaPopoulos
+ * @licence    GNU/GPL
+ * @package    SPIP\Pages\Pipelines
+ * @link       http://contrib.spip.net/Pages-uniques
+ */
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+
+
+// http://doc.spip.org/@balise_URL_ARTICLE_dist
+function balise_URL_PAGE_UNIQUE_dist($p) {
+
+       $_id = interprete_argument_balise(1,$p);
+       if (!$_id) {
+               $msg = array('zbug_balise_sans_argument', array('balise' => ' URL_PAGE_UNIQUE'));
+               erreur_squelette($msg, $p);
+               $p->interdire_scripts = false;
+               return $p;
+       }
+
+       if (!function_exists("generer_generer_url_arg"))
+               include_spip("balise/url_");
+
+       $_id = "sql_getfetsel('id_article','spip_articles','page='.sql_quote($_id))";
+       $p->code = generer_generer_url_arg('article', $p, $_id);
+       if (!$p->etoile)
+               $p->code = "vider_url($p->code)";
+       $p->interdire_scripts = false;
+       return $p;
+}
diff --git a/www/plugins/pages/pages_pipelines.php b/www/plugins/pages/pages_pipelines.php
new file mode 100644 (file)
index 0000000..f8ba588
--- /dev/null
@@ -0,0 +1,383 @@
+<?php
+/**
+ * Déclaration des pipelines utilisés par le plugin
+ *
+ * @plugin     Pages
+ * @copyright  2013
+ * @author     RastaPopoulos 
+ * @licence    GNU/GPL
+ * @package    SPIP\Pages\Pipelines
+ * @link       http://contrib.spip.net/Pages-uniques
+ */
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+// Change l'entête du formulaire des articles pour montrer que c'est une page
+function pages_affiche_milieu_ajouter_page($flux){
+
+       if ($flux['args']['exec'] == 'article_edit'){
+               include_spip('base/abstract_sql');
+               if (
+                       _request('modele') == 'page'
+                       or
+                       (
+                               ($id_article = $flux['args']['id_article']) > 0
+                               and
+                               (sql_getfetsel('page', 'spip_articles', 'id_article='.intval($id_article)))
+                       )
+               )
+               {
+                       
+                       //On force l'id parent à -1
+                       //Par principe une page nouvelle ou existante est dans la rubrique parent -1
+                       $cherche = "/(<input[^>]*name=('|\")id_parent[^>]*>)/is";
+                       if (!preg_match($cherche,$flux['data'])) {
+                               $cherche = "/(<input[^>]*name=('|\")id_rubrique[^>]*>)/is";
+                               $remplace = "$1<input type=\"hidden\" name=\"id_parent\" value=\"-1\" />\n";
+                               $flux['data'] = preg_replace($cherche, $remplace, $flux['data']);
+                       }
+                       
+                       // On cherche et remplace l'entete de la page : "modifier la page"
+                       $cherche = "/(<div[^>]*class=('|\")entete-formulaire.*?<\/span>).*?(<h1>.*?<\/h1>.*?<\/div>)/is";
+                       $surtitre = _T('pages:modifier_page');
+                       $remplace = "$1$surtitre$3";
+                       $flux['data'] = preg_replace($cherche, $remplace, $flux['data']);
+                       
+                       // Si c'est une nouvelle page, on remplace le lien de retour dans l'entete
+                       if (_request('new') == 'oui'){
+                               $cherche = "/(<span[^>]*class=(?:'|\")icone[^'\"]*retour[^'\"]*(?:'|\")>"
+                                                       . "<a[^>]*href=(?:'|\"))[^'\"]*('|\")/is";
+                               $retour = generer_url_ecrire("pages_tous");
+                               $remplace = "$1$retour$2";
+                               $flux['data'] = preg_replace($cherche, $remplace, $flux['data']);
+                       }
+               }
+       }
+
+       return $flux;
+}
+
+
+/**
+ * Saisie de l'identifiant de la page sur la fiche d'une page
+ * 
+ * @param array $flux
+ *             Le contexte du pipeline
+ * @return array $flux
+ *             Le contexte du pipeline modifié
+ */
+function pages_affiche_milieu_identifiant($flux){
+       $texte = "";
+       $e = trouver_objet_exec($flux['args']['exec']);
+
+       // Si on est sur la fiche d'un article...
+       if (!$e['edition'] and $e['type']=='article' ) {
+               include_spip('base/abstract_sql');
+               $id_article = isset($flux['args'][$e['id_table_objet']]) ? $flux['args'][$e['id_table_objet']] : false;
+               // ... et s'il s'agit d'une page
+               if (
+                       _request('modele') == 'page'
+                       or
+                       (
+                               intval($id_article) > 0
+                               and
+                               (sql_getfetsel('page', 'spip_articles', 'id_article='.intval($id_article)))
+                       )
+               ) {
+                       $texte .= recuperer_fond('prive/objets/editer/identifiant_page',
+                               array('id_article' => $id_article),
+                               array('ajax'=>true)
+                       );
+               }
+       }
+
+       if ($texte) {
+               if ($p=strpos($flux['data'],"<!--affiche_milieu-->"))
+                       $flux['data'] = substr_replace($flux['data'],$texte,$p,0);
+               else
+                       $flux['data'] .= $texte;
+       }
+
+       return $flux;
+}
+
+
+// Vérifier que la page n'est pas vide
+function pages_formulaire_charger($flux){
+
+       // Si on est dans l'édition d'un article
+       if (is_array($flux) and $flux['args']['form'] == 'editer_article'){
+       
+               // Si on est dans un article de modele page
+               if (_request('modele') == 'page' or ($flux['data']['page'] and _request('modele') != 'article')){
+                       $flux['data']['modele'] = 'page';
+                       $flux['data']['champ_page'] = $flux['data']['page'];
+               }
+               unset($flux['data']['page']);
+       }
+
+       return $flux;
+}
+
+
+/**
+ * Vérifications de l'identifiant d'une page
+ * 
+ * @param array $flux
+ *             Le contexte du pipeline
+ * @return array $flux
+ *             Le contexte du pipeline modifié
+ */
+function pages_formulaire_verifier($flux){
+       // Si on est dans l'édition d'un article/page ou dans le formulaire d'édition d'un identifiant page
+       if ( 
+               is_array($flux)
+               and (
+                       ( $flux['args']['form'] == 'editer_article' and _request('modele') == 'page' )
+                       or $flux['args']['form'] == 'editer_identifiant_page'
+               )
+       ){
+               $erreur = '';
+               $page = _request('champ_page');
+               $id_page = $flux['args']['args'][0];
+               // champ vide
+               $lang = sql_getfetsel('lang','spip_articles','id_article='.intval($id_page));
+               if (!$page)
+                       $erreur .= _T('info_obligatoire');
+               // nombre de charactères : 40 max
+               elseif (strlen($page) > 255)
+                        $erreur = _T('pages:erreur_champ_page_taille');
+               // format : charactères alphanumériques en minuscules ou "_"
+               elseif (!preg_match('/^[a-z0-9_]+$/', $page))
+                        $erreur = _T('pages:erreur_champ_page_format');
+               // doublon
+               elseif (sql_countsel(table_objet_sql('article'), "page=".sql_quote($page) . " AND id_article!=".intval($id_page)." AND lang=".sql_quote($lang)))
+                       $erreur = _T('pages:erreur_champ_page_doublon');
+
+               if ($erreur)
+                       $flux['data']['champ_page'] .= $erreur;
+       }
+       return $flux;
+
+}
+
+
+/**
+ * Insertion dans le pipeline editer_contenu_objet (SPIP)
+ * 
+ * Sur les articles considérés comme pages uniques, on remplace l'élément de choix de rubriques par :
+ * -* un input hidden id_rubrique et id_parent avec pour valeur -1
+ * -* un input hidden modele avec comme valeur "page"
+ * -* un champ d'édition de l'identifiant de la page unique
+ * 
+ * @param array $flux
+ *             Le contexte du pipeline
+ * @return array $flux
+ *             Le contexte du pipeline modifié
+ */
+function pages_editer_contenu_objet($flux){
+       $args = $flux['args'];
+       if ($args['type'] == 'article' && isset($args['contexte']['modele']) && $args['contexte']['modele'] == 'page'){
+               $erreurs = $args['contexte']['erreurs'];
+               // On cherche et remplace l'édition de la rubrique
+               $cherche = "/<li[^>]*class=('|\")editer editer_parent.*?<\/li>/is";
+               $remplace = '<li class="editer editer_page obligatoire'.($erreurs['champ_page'] ? ' erreur' : '').'">';
+               $remplace .= '<input type="hidden" name="id_parent" value="-1" />';
+               $remplace .= '<input type="hidden" name="id_rubrique" value="-1" />';
+               $remplace .= '<input type="hidden" name="modele" value="page" />';
+               $remplace .= '<label for="id_page">'._T('pages:titre_page').'</label>';
+               if ($erreurs['champ_page'])
+                       $remplace .= '<span class="erreur_message">'.$erreurs['champ_page'].'</span>';
+               $value = $args['contexte']['champ_page'] ? $args['contexte']['champ_page'] : $args['contexte']['page'];
+               $remplace .= '<input type="text" class="text" name="champ_page" id="id_page" value="'.$value.'" />';
+               $remplace .= '</li>';
+               if (preg_match($cherche,$flux['data'])) {
+                       $flux['data'] = preg_replace($cherche, $remplace, $flux['data'],1);
+                       $flux['data'] = preg_replace($cherche, '', $flux['data']);
+               } else {
+                       $cherche = "/(<li[^>]*class=('|\")editer editer_soustitre.*?<\/li>)/is";
+                       if (preg_match($cherche,$flux['data'])) {
+                               $flux['data'] = preg_replace($cherche,'$1'.$remplace, $flux['data']);
+                       } else {
+                               $cherche = "/(<li[^>]*class=('|\")editer editer_titre.*?<\/li>)/is";
+                               $flux['data'] = preg_replace($cherche,'$1'.$remplace, $flux['data']);
+                       }
+               }
+       }
+       return $flux;
+}
+
+/**
+ * Insertion dans le pipeline pre_edition (SPIP)
+ * 
+ * Si on édite un article :
+ * - Si on récupère un champ "champ_page" dans les _request() et qu'il est différent de "article", 
+ * on transforme l'article en page unique, id_rubrique devient -1 
+ * - Si on ne récupère pas de champ_page et que id_parent est supérieur à 0, on le passe en article et on vide
+ * son champ page pour pouvoir réaliser le processus inverse dans le futur
+ * 
+ * @param array $flux Le contexte du pipeline
+ * @return array $flux Le contexte modifié
+ */
+function pages_pre_edition_ajouter_page($flux){
+       if (is_array($flux) and isset($flux['args']['type']) && $flux['args']['type'] == 'article'){
+               if ((($page = _request('champ_page')) != '') AND ($page != 'article')){
+                       /**
+                        * On ajoute le "champ_page" du formulaire qui deviendra "page" dans la table
+                        * On force l'id_rubrique à -1
+                        */
+                       $flux['data']['page'] = $page;
+                       $flux['data']['id_rubrique'] = '-1';
+                       $flux['data']['id_secteur'] = '0';
+               }
+               /**
+                * si l'id_parent est supérieur à 0 on que l'on ne récupère pas de champ_page,
+                * on pense à vider le champ "page", pour pouvoir revenir après coup en page
+                */
+               if (!_request('champ_page') && (_request('id_parent') > 0)){
+                       $flux['data']['page'] = '';
+               }
+       }
+       return $flux;
+}
+
+/**
+ * Insertion dans le pipeline boite_infos (SPIP)
+ * 
+ * Ajouter un lien pour transformer un article éditorial en page ou inversement
+ * 
+ * @param array $flux 
+ *             Le contexte du pipeline
+ * @return array $flux
+ *             Le contexte modifié
+ */
+function pages_boite_infos($flux){
+       if ($flux['args']['type'] == 'article') {
+               include_spip('inc/presentation');
+               if (sql_getfetsel('page', 'spip_articles', 'id_article='. intval($flux['args']['id'])) == '') {
+                       if (autoriser('creer', 'page', $flux['args']['id']))
+                               $flux['data'] .= icone_horizontale(_T('pages:convertir_page'), parametre_url(parametre_url(generer_url_ecrire('article_edit'), 'id_article', $flux['args']['id']), 'modele', 'page'), 'page', $fonction="", $dummy="", $javascript="");
+               }
+               else {
+                       if (autoriser('modifier', 'page', $flux['args']['id']))
+                               $flux['data'] .= icone_horizontale(_T('pages:convertir_article'), parametre_url(parametre_url(generer_url_ecrire('article_edit'), 'id_article', $flux['args']['id']), 'modele', 'article'), 'article', $fonction="", $dummy="", $javascript="");
+               }
+       }
+       return $flux;
+}
+
+
+/**
+ * Insertion dans le pipeline affiche_hierarchie (SPIP)
+ * Pour les pages, faire pointer la racine vers la liste des pages au lieux des rubriques
+ * Pour savoir si on se trouve sur une page, on vérifie que le champ "page" existe, faute de mieux  
+ *
+ * @param array $flux 
+ *             Le contexte du pipeline
+ * @return array $flux
+ *             Le contexte modifié
+ */
+function pages_affiche_hierarchie($flux){
+
+       $objet = $flux['args']['objet'];
+       $id_article = $flux['args']['id_objet'];
+       if (
+               $objet == 'article'
+               and sql_getfetsel('page', 'spip_articles', 'id_article='.intval($id_article))
+       ){
+               $cherche = "<a href=\"". generer_url_ecrire('rubriques') . "\">" . _T('info_racine_site') . "</a>";
+               $remplace = "<a href=\"". generer_url_ecrire('pages') . "\">" . _T('pages:pages_uniques') . "</a>";
+               $flux['data'] = str_replace($cherche,$remplace,$flux['data']);
+       }
+       return $flux;
+}
+
+
+/**
+ * Insertion dans le pipeline pre_boucle (SPIP)
+ * Pour les listes d'articles purement éditoriaux, il faut exclure les pages uniques afin d'éviter la confusion des genres
+ * ainsi que les liens vers des pages parfois inaccessibles en fonction de l'autorisation de l'auteur.
+ *
+ * @param array $flux
+ *             Le contexte du pipeline
+ * @return array $flux
+ *             Le contexte modifié
+ */
+function pages_pre_boucle($boucle){
+
+       // On ne s'intéresse qu'à la boucle ARTICLES
+       if ($boucle->type_requete == 'articles' and !$boucle->modificateur['tout']) {
+               // On n'insère le filtre {id_rubriques>0} pour exclure les pages uniques que si aucune des conditions
+               // suivantes n'est vérifiée:
+               // - pas de critère page autre que {page=''}
+               // - pas de critère explicite {id_rubrique=-1} ou {id_rubrique<0}
+               // - pas de critère {id_rubrique?} pour lequel l'environnement renvoie -1 pour l'id de la rubrique
+               $boucle_articles = true;
+               $critere_page = false;
+
+               // On cherche les critères id_rubrique, id_article ou page
+               foreach($boucle->criteres as $_critere){
+                       if ($_critere->op == 'page') { // {page} ou {page?}
+                               // On considère qu'on cherche toujours des pages uniques donc on force le filtre id_rubrique=-1
+                               $boucle_articles = false;
+                               $critere_page = true;
+                               break;
+                       }
+                       elseif ($_critere->param[0][0]->texte == 'page') { // {page=x}
+                               if (($_critere->op == '=')
+                               AND ($_critere->param[1][0]->texte == '')) {
+                                       // On veut exclure explicitement les pages
+                                       break;
+                               }
+                               else {
+                                       // on désigne bien des pages par leur champ 'page'
+                                       $boucle_articles = false;
+                                       $critere_page = true;
+                                       break;
+                               }
+                       }
+                       elseif (($_critere->op == 'id_article') // {id_article} ou {id_article?}
+                               OR ($_critere->param[0][0]->texte == 'id_article')) { // {id_article=x}
+                               // On pointe sur un article précis, il est donc inutile de rajouter un test sur la rubrique
+                               // Pour le critère {id_article?} on considère que si l'on veut sélectionner des pages uniques
+                               // ou des articles éditoriaux on doit préciser le critère {id_rubrique}
+                               $boucle_articles = false;
+                       }
+                       elseif ((($_critere->param[0][0]->texte == 'id_rubrique') // {id_rubrique=-1}
+                                       AND ($_critere->op == '=')
+                                       AND ($_critere->param[1][0]->texte == '-1'))
+                               OR (($_critere->param[0][0]->texte == 'id_rubrique') // {id_rubrique<0}
+                                       AND ($_critere->op == '<')
+                                       AND ($_critere->param[1][0]->texte == '0'))) {
+                               // On cherche explicitement des pages uniques
+                               $boucle_articles = false;
+                               break;
+                       }
+                       elseif (($_critere->op == 'id_rubrique')) {
+                               // On connait pas à ce stade la valeur de id_rubrique qui est passé dans le env.
+                               // Aussi, on créer une condition where qui se compile différemment suivant la valeur de l'id_rubrique.
+                               // En fait, il suffit de tester si l'id_rubrique est null. Dans ce cas il faut bien rajouter id_rubrique>0
+                               // pour éliminer les pages uniques.
+                               $boucle_articles = false;
+                               $env_id = "\$Pile[0]['id_rubrique']";
+                               $boucle->where[] =
+                                       array("'?'", "(is_array($env_id)?count($env_id):strlen($env_id))", "''", "'articles.id_rubrique>0'");
+                               break;
+                       }
+               }
+
+               // Si on est en présence d'une boucle article purement éditoriale, on ajoute le filtre id_rubrique>0
+               if ($boucle_articles) {
+                       $boucle->where[] = array("'>'", "'articles.id_rubrique'", "'\"0\"'");
+               }
+
+               // Si on est en présence d'un critère {page} quelconque, on force le filtre id_rubrique=-1
+               if ($critere_page) {
+                       $boucle->where[] = array("'='", "'articles.id_rubrique'", "'\"-1\"'");
+               }
+       }
+
+       return $boucle;
+}
+
+?>
diff --git a/www/plugins/pages/paquet.xml b/www/plugins/pages/paquet.xml
new file mode 100644 (file)
index 0000000..2a6b98e
--- /dev/null
@@ -0,0 +1,36 @@
+<paquet
+       prefix="pages"
+       categorie="divers"
+       version="1.2.4"
+       etat="stable"
+       compatibilite="[3.0.0;3.1.*]"
+       logo="prive/themes/spip/images/page-32.png"
+       documentation="http://contrib.spip.net/Pages-uniques"
+       schema="1.0.1"
+>
+
+       <nom>Pages</nom>
+       <!-- Gestion des pages sans rubrique -->
+
+       <auteur>RastaPopoulos</auteur>
+       <auteur lien="http://www.ldd.fr">Les Développements Durables</auteur>
+
+       <licence lien="http://www.gnu.org/licenses/gpl-3.0.html">GPL v3</licence>
+
+       <pipeline nom="declarer_tables_objets_sql" inclure="base/pages_tables.php" />
+       <pipeline nom="affiche_milieu" action="affiche_milieu_ajouter_page" inclure="pages_pipelines.php" />
+       <pipeline nom="affiche_milieu" action="affiche_milieu_identifiant" inclure="pages_pipelines.php" />
+       <pipeline nom="boite_infos" inclure="pages_pipelines.php" />
+       <pipeline nom="affiche_hierarchie" inclure="pages_pipelines.php" />
+       <pipeline nom="formulaire_charger" inclure="pages_pipelines.php" />
+       <pipeline nom="formulaire_verifier" inclure="pages_pipelines.php" />
+       <pipeline nom="editer_contenu_objet" inclure="pages_pipelines.php" />
+       <pipeline nom="pre_edition" action="pre_edition_ajouter_page" inclure="pages_pipelines.php" />
+       <pipeline nom="autoriser" inclure="pages_autorisations.php" />
+       <pipeline nom="pre_boucle" inclure="pages_pipelines.php" />
+
+       <utilise nom="polyhier" compatibilite="[2.0.1;]" /> 
+
+       <menu nom="pages" titre="pages:pages_uniques" parent="menu_edition" icone="images/page-16.png" />
+       <menu nom="page_creer" titre="pages:creer_page" parent="outils_rapides" icone="images/page-new-16.png" action="article_edit" parametres="new=oui&amp;modele=page&amp;id_rubrique=-1" />
+</paquet>
diff --git a/www/plugins/pages/prive/objets/editer/identifiant_page.html b/www/plugins/pages/prive/objets/editer/identifiant_page.html
new file mode 100644 (file)
index 0000000..7b912a4
--- /dev/null
@@ -0,0 +1,3 @@
+<div class="ajax">
+       #FORMULAIRE_EDITER_IDENTIFIANT_PAGE{#ENV{id_article}}
+</div>
diff --git a/www/plugins/pages/prive/squelettes/contenu/pages.html b/www/plugins/pages/prive/squelettes/contenu/pages.html
new file mode 100644 (file)
index 0000000..ba9a365
--- /dev/null
@@ -0,0 +1,24 @@
+[(#AUTORISER{voir,_pages}|sinon_interdire_acces)]
+<h1 class="grostitre"><:pages:toutes_les_pages:></h1>
+
+#SET{statuts,#SESSION{statut}|statuts_articles_visibles}
+[(#ENV{id_auteur,''}|=={#SESSION{id_auteur}}|oui)
+       #SET{statuts,#GET{statuts}|array_merge{#LISTE{prepa}}}
+]
+<INCLURE{fond=prive/objets/liste/articles,
+       id_rubrique=-1,
+       statut=#GET{statuts},
+       id_auteur=#ENV{id_auteur,''},
+       nb=30,
+       titre=<:pages:toutes_les_pages:>,
+       sinon=<:pages:aucune_page:>,
+       env,
+       ajax}>
+
+[(#AUTORISER{creer,page})
+       [(#URL_ECRIRE{article_edit}
+                       |parametre_url{modele,page}
+                       |parametre_url{new,oui}
+                       |parametre_url{id_rubrique,-1}
+                       |icone_verticale{<:pages:creer_page:>,page,new,right})]
+]
\ No newline at end of file
diff --git a/www/plugins/pages/prive/themes/spip/images/page-16.png b/www/plugins/pages/prive/themes/spip/images/page-16.png
new file mode 100644 (file)
index 0000000..be09778
Binary files /dev/null and b/www/plugins/pages/prive/themes/spip/images/page-16.png differ
diff --git a/www/plugins/pages/prive/themes/spip/images/page-24.png b/www/plugins/pages/prive/themes/spip/images/page-24.png
new file mode 100644 (file)
index 0000000..b6e0d39
Binary files /dev/null and b/www/plugins/pages/prive/themes/spip/images/page-24.png differ
diff --git a/www/plugins/pages/prive/themes/spip/images/page-32.png b/www/plugins/pages/prive/themes/spip/images/page-32.png
new file mode 100644 (file)
index 0000000..f6882aa
Binary files /dev/null and b/www/plugins/pages/prive/themes/spip/images/page-32.png differ
diff --git a/www/plugins/pages/prive/themes/spip/images/page-new-16.png b/www/plugins/pages/prive/themes/spip/images/page-new-16.png
new file mode 100644 (file)
index 0000000..90d4250
Binary files /dev/null and b/www/plugins/pages/prive/themes/spip/images/page-new-16.png differ
diff --git a/www/plugins/pages/saisies-vues/pages_uniques.html b/www/plugins/pages/saisies-vues/pages_uniques.html
new file mode 100644 (file)
index 0000000..8b7b020
--- /dev/null
@@ -0,0 +1,5 @@
+<BOUCLE_article(ARTICLES){page=#ENV{valeur}}>\r
+<p>#TITRE (#PAGE)</p>\r
+</BOUCLE_article>\r
+<p><:saisies:vue_sans_reponse:></p>\r
+<//B_article>
\ No newline at end of file
diff --git a/www/plugins/pages/saisies/pages_uniques.html b/www/plugins/pages/saisies/pages_uniques.html
new file mode 100644 (file)
index 0000000..2b4d2e3
--- /dev/null
@@ -0,0 +1,19 @@
+[(#REM) \r
+  Saisie permettant de sélectionner une page unique par son nom de page\r
+  \r
+  Parametres :\r
+  - option_intro : chaine de langue de la premiere ligne vide ? (defaut:"")\r
+  - cacher_option_intro : pas de premier option vide  (defaut:"")\r
+  - class : classe(s) css ajoutes au select\r
+  \r
+  Exemple d'appel :\r
+       [(#SAISIE{pages_uniques,nom,\r
+               label=<:plugin:label:>,\r
+       })] \r
+]\r
+<select name="#ENV{nom}" id="champ_#ENV{nom}"[ class="(#ENV{class})"][ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"]>\r
+[(#ENV{cacher_option_intro}|non)<option value="">[(#ENV{option_intro})]</option>]\r
+<BOUCLE_selection(ARTICLES){par titre}{id_rubrique<0}>\r
+       <option value="#PAGE" [(#PAGE|=={#ENV{valeur,#ENV{defaut}}}|oui) selected="selected"]>#TITRE (#PAGE)</option>\r
+</BOUCLE_selection>\r
+</select>
\ No newline at end of file
diff --git a/www/plugins/pages/svn.revision b/www/plugins/pages/svn.revision
new file mode 100644 (file)
index 0000000..cc45d84
--- /dev/null
@@ -0,0 +1,10 @@
+<svn_revision>
+<text_version>
+Origine: file:///home/svn/repository/spip-zone/_plugins_/pages/trunk
+Revision: 86912
+Dernier commit: 2014-12-29 15:00:04 +0100 
+</text_version>
+<origine>file:///home/svn/repository/spip-zone/_plugins_/pages/trunk</origine>
+<revision>86912</revision>
+<commit>2014-12-29 15:00:04 +0100 </commit>
+</svn_revision>
\ No newline at end of file
diff --git a/www/plugins/ressource/inc/ressource.php b/www/plugins/ressource/inc/ressource.php
new file mode 100644 (file)
index 0000000..7207238
--- /dev/null
@@ -0,0 +1,707 @@
+<?php
+
+/*
+ * transforme un raccourci de ressource en un joli html a embed
+ * 
+ *
+ */
+
+define('_EXTRAIRE_RESSOURCES', ',' . '<"?(https?://|[\w -]+\.[\w -]+).*>'.',UimsS');
+define('_RESSOURCE_VIGNETTE_LARGEUR_DEFAUT','small');
+define('_RESSOURCE_IMAGE_LARGEUR_DEFAUT', 'large');
+
+/* pipeline pour typo */
+function ressource_post_typo($t) {
+       if (strpos($t, '<') !== false) {
+               $t = preg_replace_callback(_EXTRAIRE_RESSOURCES, 'traiter_ressources', $t);
+       }
+       return $t;
+}
+
+/* pipeline pour propre */
+function ressource_pre_liens($t) {
+       if (strpos($t, '<') !== false) {
+               $t = preg_replace_callback(_EXTRAIRE_RESSOURCES, 'traiter_ressources', $t);
+
+               // echapper les autoliens eventuellement inseres (en une seule fois)
+               if (strpos($t,"<html>")!==false)
+                       $t = echappe_html($t);
+       }
+       return $t;
+}
+
+function traiter_ressources($r) {
+       if ($ressource = charger_fonction('ressource', 'inc', true))
+               $html = $ressource($r[0]);
+       else
+               $html = htmlspecialchars($r[0]);
+
+       return '<html>'.$html.'</html>';
+}
+
+function inc_ressource_dist($r) {
+       // $r contient tout le texte définissant la ressource :
+       // <fichier.rtf option1 option2...>
+
+       // 1. phraser le raccourci
+       $attrs = phraser_tag('<res src='.substr($r,1));
+
+       # debug :)
+       $attrs['debug'] = $r;
+
+       // 2. keywords : right => align=right, etc
+       foreach(array(
+               'right' => 'align',
+               'left' => 'align',
+               'center' => 'align',
+       ) as $k => $v) {
+               if ($attrs[$k] == $k) {
+                       $attrs[$v] = $k;
+                       unset($attrs[$k]);
+               }
+       }
+
+       // 2. constituer les meta-donnees associees a $res[src]
+       $meta = ressource_meta($attrs);
+
+       // 4. traiter les parametres d'image / logo / vignette / resize
+       // supprimera le href si necessaire
+       $image = ressource_image($attrs, $meta);
+
+       $final = array_merge($meta, $attrs);
+
+       // renvoyer le html final
+       $final = array_merge($final, $image);
+
+       $html = embed_ressource($final);
+       return $html;
+}
+
+function ressource_meta($res) {
+       $meta = $res;
+
+       // on va beaucoup travailler avec l'attribut src
+       $src = $res['src'];
+
+       // identifier la ressource
+       // s'agit-il d'un fichier decrit dans la mediathèque,
+       // d'un fichier local, d'un oembed, d'un doc distant connu, etc ?
+
+       // ressource fichier.rtf => rtf/fichier.rtf
+       if (preg_match(',^[^/]+\.([^.]+)$,', $src, $r))
+               $fichier = $r[1].'/'.$r[0];
+       else
+               $fichier = $src;
+
+       // determiner temporairement l'extension de la ressource (ca pourra changer
+       // si on en fait une copie locale et qu'elle indique un autre type mime)
+       if (preg_match(',\.(\w+)([?#].*)?$,S', $src, $r)) {
+               $meta['extension'] = strtolower($r[1]);
+
+               if ($meta['extension'] == 'jpeg')
+                       $meta['extension'] = 'jpg';
+       }
+
+       # d'abord fouiller la mediatheque
+       include_spip('base/abstract_sql');
+       if ($s = sql_fetsel('*', 'spip_documents', 'fichier='.sql_quote($fichier))) {
+               $meta = $s;
+               $meta['href'] = get_spip_doc($s['fichier']);
+               $meta['local'] = copie_locale($meta['href'], 'test');
+       }
+       else
+       if (preg_match(',^https?://,', $src)) {
+               $meta['href'] = $src;
+
+               /* pipeline ! */
+               /* exemple : traitement par autoembed */
+               include_spip('autoembed/autoembed');
+               if (function_exists('embed_url')
+               AND $u = embed_url($src)) {
+                       $meta['embed'] = $u;
+               }
+
+               /* autre exemple de traitement avec oembed */
+               include_spip('oembed_fonctions');
+               if (function_exists('oembed')
+                       AND $u = oembed($src)
+                       AND $u != $src) 
+               {
+                       $meta['embed'] = $u;
+               }
+
+               /* recuperer un album flickr */
+               if (preg_match(',^https?://(www\.)?flickr\.com/.*/sets/(\d+),', $src, $r)) {
+                       $meta['album'] = $r[2];
+                       if ($html = recuperer_fond('modeles/album_flickr', $meta)) {
+                               $meta['embed'] = $html;
+                       }
+               }
+
+               $meta = pipeline('ressource_meta',
+                       array(
+                               'args' => $res,
+                               'data' => $meta
+                       )
+               );
+               
+               /* chargement distant */
+               if (!isset($meta['html'])) {
+                       include_spip('inc/distant');
+                       if (!$local = copie_locale($src, 'test')
+                       AND !in_array($meta['extension'], array('mp3'))
+                       ) {
+                               include_spip('inc/queue');
+                               queue_add_job('copie_locale', 'copier', array($src), $file = 'inc/distant', $no_duplicate = true, $time=0, $priority=0);
+                       }
+                       if ($local = copie_locale($src, 'test')) {
+                               $meta['local'] = $local;
+                       }
+               }
+
+       }
+       // fichier dans IMG/ ?
+       else if (preg_match(',^[^/]+\.([^.]+)$,', $src, $r)
+       AND $h = _DIR_IMG.$r[1].'/'.$r[0]
+       AND @file_exists($h)
+       ) {
+               $meta['local'] = $h;
+               $meta['href'] = $h;
+       }
+
+       ##### tests pour le plugin OPUS :
+       ##### <image.jpg> correspond à opus/[article]/image.jpg
+       else if ($r
+       AND isset($GLOBALS['contexte'])
+       AND isset($GLOBALS['contexte']['id_article'])
+       AND $q = sql_fetsel('url_site', 'spip_articles', 'id_article='.sql_quote($GLOBALS['contexte']['id_article']))
+       AND $opus = $q['url_site']
+       AND $h = _DIR_RACINE.$opus.'/'.$r[0]
+       AND file_exists($h)) {
+               $meta['local'] = $h;
+               $meta['href'] = $h;
+               $meta['logodocument'] = $h;
+       }
+       ####
+
+       // si on l'a, renseigner ce qu'on peut dire du fichier
+       if (isset($meta['local'])
+       AND @file_exists($meta['local'])) {
+               $meta['extension'] = strtolower(preg_replace(',^.*\.,', '', $meta['local']));
+               $meta['taille'] = @filesize($meta['local']);
+               if ($r = getimagesize($meta['local'])) {
+                       // donnees brutes du fichier
+                       $meta['width'] = $r[0];
+                       $meta['height'] = $r[1];
+                       $meta['largeur'] = $meta['width'];
+                       $meta['hauteur'] = $meta['height'];
+               }
+
+               if ($meta['extension'] == 'html') {
+                       // choper ce qu'on peut du html
+                       ressource_html($meta);
+               }
+
+               // extraire ses donnees !
+               if (!isset($meta['extract'])
+               AND $u = ressource_extract($meta)) {
+                       $meta['fullextract'] = $u;
+                       $meta['extract'] = propre(couper($u, 500));
+               }
+       }
+
+       // recupere le type mime de la ressource
+       if (isset($meta['extension']))
+               $meta['type_document'] = ressource_mime($meta['extension']);
+
+       return $meta;
+}
+
+// choper les trucs du genre meta opengraph ; meta description etc
+function ressource_html(&$meta) {
+
+       include_spip('fonctionsale');
+       if (function_exists('sale')) {
+               $u = sale(spip_file_get_contents($meta['local']));
+
+               $meta['fullextract'] = $u;
+               $meta['extract'] = propre(couper($u, 500));
+       }
+}
+
+function ressource_mime($e) {
+       global $tables_images, $tables_sequences, $tables_documents, $tables_mime, $mime_alias;
+       include_spip('base/typedoc');
+
+
+       $mime = $tables_mime[$e];
+       if (!$t = $tables_documents[$e]
+       AND !$t = $tables_images[$e]
+       AND !$t = $tables_sequences[$e])
+               $t = $e;
+
+       return $t;
+
+}
+
+/*
+ * recoit une chaine
+ * renvoie un array
+ * les valeurs par defaut sont mappees
+ * inspire de http://w-shadow.com/blog/2009/10/20/how-to-extract-html-tags-and-their-attributes-with-php/
+ */
+function phraser_tag($rr) {
+       $attribute_pattern =
+       '@
+       (
+       (?P<name>\w+)                    # attribute name
+       \s*=\s*
+       (
+           (?P<quote>[\"\'])(?P<value_quoted>.*?)(?P=quote)    # a quoted value
+           |                      # or
+           (?P<value_unquoted>[^\s"\']+?)(?:\s+)          # an unquoted value
+       )
+       |(?P<auto>\w+)
+       )
+       @xsiS';
+
+       // d'abord eliminer le type du tag et l'evntuelle fermeture auto
+       $res = array();
+       $rr = preg_replace(',^<\w+\s+,S', '', $rr);
+       $rr = preg_replace(',\s*/?'.'>$,S', ' ', $rr);
+
+       // ensuite parser le reste des attributs
+       preg_match_all($attribute_pattern, $rr, $z, PREG_SET_ORDER);
+
+       foreach($z as $t) {
+               if (isset($t['auto'])) {
+                       if (is_numeric($t['auto'])) # 200
+                               $res['width'] = $t['auto'];
+                       elseif (preg_match(',^\d+x\d+$,', $t['auto'])) # 200x300
+                               $res['geometry'] = $t['auto'];
+                       else
+                               $res[$t['auto']] = $t['auto'];
+               }
+               elseif (isset($t['value_unquoted'])) {
+                       $res[$t['name']] = $t['value_unquoted'];
+               }
+               elseif (isset($t['value_quoted'])) {
+                       $res[$t['name']] = $t['value_quoted'];
+               }
+       }
+
+       return $res;
+}
+
+function embed_ressource($res) {
+       // si la ressource a un embed, charger le modele ressource_embed
+       // qui l'encapsule dans un <figure>, ajoute eventuellement une légende etc.
+       if (isset($res['embed'])) {
+               if (!isset($res['width'])
+               AND $w = extraire_attribut($res['embed'], 'width'))
+                       $res['width'] = $w;
+               if (!isset($res['height'])
+               AND $h = extraire_attribut($res['embed'], 'height'))
+                       $res['height'] = $h;
+               return recuperer_fond('modeles/ressource_embed', $res);
+       }
+
+       // si la ressource est un document, renvoyer <doc1>
+       if (isset($res['id_document'])) {
+#              return recuperer_fond('modeles/doc', $res);
+       }
+
+       if ($res['type_document'] == 'PDF') {
+               return
+                       recuperer_fond('modeles/application', $res);
+       }
+
+       return
+#              "<pre>".var_export($res,true)."</pre>" .
+               recuperer_fond('modeles/ressource', $res);
+}
+
+/* ici c'est flou… */
+function ressource_image($attrs, $meta) {
+       $image = array();
+
+       // creer une vignette pour le doc ; si une largeur est exigee,
+       // adapter la taille.
+       if ($attrs['largeur'] OR $attrs['hauteur']) {
+               $resize = true;
+       }
+       // size
+       else {
+               if (!$attrs['size']) {
+                       if ($attrs['image']) # ???? c'est quoi ? le mode ?
+                               $attrs['size'] = _RESSOURCE_VIGNETTE_LARGEUR_DEFAUT;
+                       else
+                               $attrs['size'] = _RESSOURCE_IMAGE_LARGEUR_DEFAUT;
+               }
+
+               if (in_array($meta['extension'], array('gif', 'png', 'jpg'))
+               AND strlen($b = image_stdsize($meta, $attrs))) {
+                       $a = $b;
+                       $resize = true;
+               }
+       }
+
+       // Verifier d'abord si le parametre 'icon' force l'icon
+# todo : icone => icon
+       if ($attrs['icon']) {
+               $f = charger_fonction('vignette','inc');
+               $img = $f($meta['extension'], false);
+               if ($resize)
+                       $a = image_reduire($img, $attrs['largeur'] ? $attrs['largeur'] : -1, $attrs['hauteur'] ? $attrs['hauteur'] : -1);
+               else
+                       $a = '<img src="'.$img.'" />';
+
+       }
+       // methode normale : reduire l'image si possible, sinon icon
+       else {
+               if (!$a) {
+                       $w = sinon($attrs['largeur'],500);
+                       $h = sinon($attrs['hauteur'],700);
+                       $a = vignette_automatique($meta['id_vignette'], $meta,
+                               '' /*url*/, $w, $h, null /* align */);
+               }
+       }
+
+       if ($a) $image['logodocument'] = $a;
+
+       // experimental : DEST
+       // TODO: parametre à mieux nommer ?
+       // parametre |dest=800 pour reduire l'image LIEE a 800px max
+       if ($attrs['dest']) {
+               $tmp = image_reduire($meta['local'], $attrs['dest']);
+               if ($tmp = extraire_attribut($tmp, 'src'))
+                       $image['href'] = $tmp;
+       }
+
+       return $image;
+}
+
+
+
+
+# s t m d z b o
+function image_stdsize($meta, $attrs) {
+       include_spip('inc/filtres_images');
+
+       $s = $attrs['size'];
+       if (isset($meta['local']))
+               $img = $meta['local'];
+       else
+               $img = $attrs['src'];
+
+       # intercepter les URLs flickr pour choper les jolies reductions
+       if (preg_match(',^(http://farm.*.staticflickr.com/(\d+/[0-9a-z_]+?))(_[zbo])?\.jpg$,', $img, $r)) {
+               if (in_array($s, array('s', 't', 'm', 'z', 'b') )){
+                       $img = $r[1].'_'.$s.'.jpg';
+                       return '<img src="'.$img.'" />';
+               }
+               if (in_array($s, array('d'))) {
+                       $img = $r[1].'.jpg';
+                       return '<img src="'.$img.'" />';
+               }
+       }
+       elseif (preg_match(',^(http://farm.*.staticflickr.com/(\d+/[0-9a-z_]+?))(_[k])?\.jpg$,', $img, $r)) {
+               if (in_array($s, array('k') )){
+                       $img = $r[1].'_'.$s.'.jpg';
+                       return '<img src="'.$img.'" />';
+               }
+               if (in_array($s, array('d'))) {
+                       $img = $r[1].'.jpg';
+                       return '<img src="'.$img.'" />';
+               }
+       }
+       elseif (preg_match(',^(http://farm.*.staticflickr.com/(\d+/[0-9a-z_]+?))(_[h])?\.jpg$,', $img, $r)) {
+               if (in_array($s, array('h') )){
+                       $img = $r[1].'_'.$s.'.jpg';
+                       return '<img src="'.$img.'" />';
+               }
+               if (in_array($s, array('d'))) {
+                       $img = $r[1].'.jpg';
+                       return '<img src="'.$img.'" />';
+               }
+       }
+
+       // IMG/jpg/truc.jpg depuis l'espace prive
+       if (_DIR_RACINE
+       AND substr(_DIR_RACINE.$img, 0, strlen(_DIR_IMG)) == _DIR_IMG)
+               $img = _DIR_RACINE.$img;
+
+       if (!is_numeric($s)) {
+       switch($s) {
+               case 'sq':
+               case 'square':
+                       # la c'est dur
+                       $d = 75;
+                       $img = image_passe_partout($img, $d, $d);
+                       $img = image_recadre($img, $d, $d);
+                       break;
+               case 't':
+               case 'thumb':
+               case 'thumbnail':
+                       $a = 100;
+                       break;
+               case 'm':
+               case 's':
+               case 'small':
+                       $a = 240;
+                       break;
+               case 'z':
+               case 'medium640':
+                       $a = 640;
+                       break;
+               case 'b':
+               case 'large':
+                       $a = 1024;
+                       break;
+               // xl = 'k' chez flickr (2048px)
+               case 'k':
+               case 'xl':
+               case 'extra':
+               case 'extralarge':
+                       $a = 2048;
+                       break;
+               case 'o':
+               case 'original':
+                       $a = 10000000;
+                       break;
+               case '-':
+               case '':
+               case 'd':
+               case 'default':
+               default:
+                       $a = 500;
+                       break;
+       }
+       }
+
+       if ($a)
+               $img = image_reduire($img, $a);
+       else if (is_numeric($s))
+               $img = image_reduire($img, $s);
+
+       return $img;
+}
+
+
+
+function ressource_extract($meta) {
+       /*
+
+       global $extracteur;
+
+       $extension = $meta['extension'];
+
+       include_spip('extract/'.$extension);
+       if (function_exists($lire = $extracteur[$extension])) {
+               $charset = 'iso-8859-1';
+               $contenu = $lire($meta['local'], $charset);
+               var_dump($lire, $contenu);
+       }
+       */
+
+       switch($meta['extension']) {
+               case 'html':
+               case 'doc':
+               case 'docx':
+               case 'rtf':
+               case 'odt':
+                       $conv = converthtml($meta['local'], $err);
+                       include_spip('fonctionsale');
+                       if (function_exists('sale')) {
+                               $conv = sale($conv);
+                       }
+                       break;
+               default:
+                       break;
+       }
+
+       return $conv;
+}
+
+/**
+* Multiple Curl Handlers
+* @author Jorge Hebrard ( jorge.hebrard@gmail.com )
+**/
+class curlNode{
+    static private $listenerList;
+    private $callback;
+    public function __construct($url){
+        $new =& self::$listenerList[];
+        $new['url'] = $url;
+        $this->callback =& $new;
+    }
+    /**
+    * Callbacks needs 3 parameters: $url, $html (data of the url), and $lag (execution time)
+    **/
+    public function addListener($callback){
+        $this->callback['callback'] = $callback;
+    }
+    /**
+    * curl_setopt() wrapper. Enjoy!
+    **/
+    public function setOpt($key,$value){
+        $this->callback['opt'][$key] = $value;
+    }
+    /**
+    * Request all the created curlNode objects, and invoke associated callbacks.
+    **/
+    static public function request(){
+    
+        //create the multiple cURL handle
+        $mh = curl_multi_init();
+        
+        $running=null;
+        
+        # Setup all curl handles
+        # Loop through each created curlNode object.
+        foreach(self::$listenerList as &$listener){
+            $url = $listener['url'];
+            $current =& $ch[];
+            
+            # Init curl and set default options.
+            # This can be improved by creating
+            $current = curl_init();
+
+            curl_setopt($current, CURLOPT_URL, $url);
+            # Since we don't want to display multiple pages in a single php file, do we?
+            curl_setopt($current, CURLOPT_HEADER, 0);
+            curl_setopt($current, CURLOPT_RETURNTRANSFER, 1);
+            
+            # Set defined options, set through curlNode->setOpt();
+            if (isset($listener['opt'])){
+                foreach($listener['opt'] as $key => $value){
+                    curl_setopt($current, $key, $value);
+                }
+            }
+            
+            curl_multi_add_handle($mh,$current);
+            
+            $listener['handle'] = $current;
+            $listener['start'] = microtime(1);
+        } unset($listener);
+
+        # Main loop execution
+        do {
+            # Exec until there's no more data in this iteration.
+            # This function has a bug, it
+            while(($execrun = curl_multi_exec($mh, $running)) == CURLM_CALL_MULTI_PERFORM);
+            if($execrun != CURLM_OK) break; # This should never happen. Optional line.
+            
+            # Get information about the handle that just finished the work.
+            while($done = curl_multi_info_read($mh)) {
+                # Call the associated listener
+                foreach(self::$listenerList as $listener){
+                    # Strict compare handles.
+                    if ($listener['handle'] === $done['handle']) {
+                        # Get content
+                        $html = curl_multi_getcontent($done['handle']);
+                        # Call the callback.
+                        call_user_func($listener['callback'],
+                        $listener['url'],
+                        $html,(microtime(1)-$listener['start']));
+                        # Remove unnecesary handle (optional, script works without it).
+                        curl_multi_remove_handle($mh, $done['handle']);
+                    }
+                }
+                
+            }
+            # Required, or else we would end up with a endless loop.
+            # Without it, even when the connections are over, this script keeps running.
+            if (!$running) break;
+            
+            # I don't know what these lines do, but they are required for the script to work.
+            while (($res = curl_multi_select($mh)) === 0);
+            if ($res === false) break; # Select error, should never happen.
+        } while (true);
+
+        # Finish out our script ;)
+        curl_multi_close($mh);
+    
+    }
+}
+
+function converthtml($f, $err) {
+       define('_CONVERT_URL', 'http://office.rezo.net/office/v1/?email=fil@rezo.net&key=1223649b375bb98e1b57141f96643cd47a3029c3');
+
+       $signature = md5_file($f);
+
+       // 1. a-t-on le fichier en local
+       $html = sous_repertoire(_DIR_TMP,'converthtml').$signature.'.html';
+       if (file_exists($html))
+               return spip_file_get_contents($html);
+
+       // 2. sinon le chercher sur le serveur office.rezo
+       if (!defined('_CONVERT_URL'))
+               return false;
+
+       $url = parametre_url(_CONVERT_URL, 'signature', $signature, '&');
+
+       include_spip('inc/queue');
+       queue_add_job('convert_html_fetch', 'convert_html_fetch('.$f.')', array($url, $f, $html), $file = 'inc/ressource', $no_duplicate = true, $time=0, $priority=0);
+       #convert_html_fetch($url, $f);
+
+       return '';
+}
+
+function convert_html_fetch($url, $f, $html=null) {
+       if (!$html) return;
+       include_spip('inc/distant');
+       if ($rep = recuperer_page($url)
+       AND $rep = json_decode($rep)
+       AND isset($rep->content)) {
+               ecrire_fichier($html, $rep->content);
+               return;
+       }
+       
+
+       // 3. si 404, l'envoyer au serveur
+       #include_spip('inc/queue');
+       #queue_add_job('convert_html_send', 'convert_html_send('.$f.')', array($url, $f), $file = 'inc/ressource', $no_duplicate = true, $time=0, $priority=0);
+       convert_html_send($url, $f);
+
+}
+
+function convert_html_send($url, $f) {
+       include_spip('inc/filtres');
+       spip_log('curl @'.$f.' '.$url.' ('.taille_en_octets(filesize($f)).')');
+       $data = array('file' => '@'.$f);
+       $ch = curl_init();
+       curl_setopt($ch, CURLOPT_VERBOSE, 1);
+       curl_setopt($ch, CURLOPT_URL, $url);
+       curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+       curl_setopt($ch, CURLOPT_POST, true);
+       curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
+       $n = curl_exec($ch);
+       spip_log($n);
+       curl_close($ch);
+}
+
+
+function xconverthtml($f, &$err) {
+
+
+       $k = escapeshellarg($f);
+
+       exec("/usr/bin/textutil -convert html -stdout -noload -nostore $k", $ret, $err);
+
+       if ($err) {
+               spip_log($err);
+       } else {
+               $ret = join($ret, "\n");
+               // les notes de bas de page word sont parfois transformees en truc chelou
+               $ret = str_replace('<span class="Apple-converted-space"> </span>', '~', $ret);
+               return nettoyer_utf8($ret);
+       }
+}
+
+function nettoyer_utf8($t) {
+       if (!preg_match('!\S!u', $t))
+               $t = preg_replace_callback(',&#x([0-9a-f]+);,i', 'utf8_do', utf8_encode(utf8_decode($t)));
+       return $t;
+}
+
diff --git a/www/plugins/ressource/modeles/album_flickr.html b/www/plugins/ressource/modeles/album_flickr.html
new file mode 100644 (file)
index 0000000..c3918b3
--- /dev/null
@@ -0,0 +1,52 @@
+#SET{apikey,8e9d40e9fe62ab49c1ed33f91c027d19}
+#SET{api,https://api.flickr.com/services/rest/?method=flickr.photosets.getPhotos&api_key=#GET{apikey}&photoset_id=#ALBUM&extras=url_s%2C+url_q%2C+url_sq%2C+url_m%2C+url_l%2C+description&format=json&nojsoncallback=1}
+
+<BOUCLE_meta(DATA)
+       {source json,#GET{api}}>
+#SET{album_#CLE,#VALEUR**}
+</BOUCLE_meta>
+
+<B_album>
+<style>
+.album ul {
+  margin: 0;
+  list-style: none;
+}
+.album li {
+  float: left;
+  margin: 0;
+  margin: 0 10px 10px 0;
+}
+.album .thumb {
+  margin: 0;
+}
+</style>
+<figure class="album">
+
+[(#REM) title=" " pour ne pas avoir de titre ]
+[<figcaption>(#ENV{title}|sinon{#GET{album_photoset}|table_valeur{title}}|trim)</figcaption>]
+
+<ul class="clearfix">
+<BOUCLE_album(DATA)
+       {source json,#GET{api}}{datapath photoset/photo}>
+<li>
+
+ <a[ href="(#VALEUR{url_l}|sinon{#VALEUR{url_m}})"] type="image/jpeg"
+  data-destw="#VALEUR{width_m}"
+  data-desth="#VALEUR{height_m}"
+  rel="album#ALBUM"
+  title="[(#VALEUR{title}|entites_html|concat{[ &lt;br&gt;&lt;small&gt; (#VALEUR{description/_content}|print|entites_html)&lt;/small&gt;]})]"><img
+  src="#VALEUR{url_q}"
+  width="#VALEUR{width_q}" height="#VALEUR{height_q}"
+  class="thumb"
+ /></a>
+
+</li>
+
+</BOUCLE_album>
+</ul>
+</figure>
+</B_album>
+
+       <a href="flickr">Album introuvable</a>
+<//B_album>
\ No newline at end of file
diff --git a/www/plugins/ressource/modeles/application.html b/www/plugins/ressource/modeles/application.html
new file mode 100644 (file)
index 0000000..1ce59f0
--- /dev/null
@@ -0,0 +1,18 @@
+[(#EXTENSION|=={pdf}|oui)
+<iframe src="[(#VAL{https://docs.google.com/viewer?embedded=true}
+ |parametre_url{url,#HREF|url_absolue})]"
+ width='100%' height='100%'
+ class='ressource-pdf google-doc-viewer'
+ style='max-width:100%; border:0'>
+</iframe>
+<noscript><a href="[(#HREF|url_absolue)]"><:bouton_download:></a></noscript>
+<script type="text/javascript">
+ $(function() {
+  $('iframe.ressource-pdf')
+  .each(function() {
+    var a = $(this).data('ratio') || 1.2;
+    $(this).height(Math.min($(this).width()*a, $(window).height()));
+  });
+ });
+</script>
+]
diff --git a/www/plugins/ressource/modeles/ressource.html b/www/plugins/ressource/modeles/ressource.html
new file mode 100644 (file)
index 0000000..3302aa0
--- /dev/null
@@ -0,0 +1,34 @@
+[(#REM)
+
+       Modele pour <doc> en <figure>
+
+       Dans le cas d'une simple image -mode=image-, on affiche
+       le document lui-meme, sans lien de telechargement
+]
+
+[(#SET{logo,#LOGODOCUMENT})]
+[(#SET{fichier,[(#GET{logo}||extraire_attribut{src})]})]
+[(#SET{width,[(#GET{logo}||extraire_attribut{width})]})]
+[(#SET{height,[(#GET{logo}||extraire_attribut{height})]})]
+#SET{url,#ENV{lien,#HREF}}
+
+[(#SET{captionspip,[
+       <div class='#EDIT_TITRE spip_doc_titre'[ style='width:(#GET{width}|min{350}|max{120})px;']><strong>(#TITRE|sinon{[(#DESCRIPTIF*|oui)]})</strong></div>[
+       <div class='#EDIT_DESCRIPTIF spip_doc_descriptif quiet'[ style='width:(#GET{width}|min{350}|max{120})px;']>(#DESCRIPTIF|PtoBR)[(#NOTES|PtoBR)]</div>
+       ]
+]
+})]
+
+[(#SET{caption,[(#CAPTION
+       |sinon{#LEGEND}
+       |sinon{#LEGENDE}
+       |propre|PtoBR
+       |sinon{#GET{captionspip}}
+)]})]
+
+<figure class='[spip_document_(#ID_DOCUMENT) ]spip_documents[ spip_documents_(#ENV{align})][ (#ENV{class})] spip_lien_ok'>
+[<span class="(#IMGCLASS)">][<a href="(#GET{NONfichier})"[ class="(#ENV{lien_class}|concat{" display_box"})"][ (#ENV{lien}|?{'',type="image/jpeg"})]>]<img src='#GET{fichier}' width='#GET{width}' height='#GET{height}'[
+alt='(#ENV{alt,#TYPE_DOCUMENT[ - (#TAILLE|taille_en_octets)]}|texte_backend)']
+style="max-width:100%;height:auto;"[ title='(#TITLE|texte_backend)'] />[(#GET{url}|?{</a>})][(#IMGCLASS|?{</span>})][
+<figcaption>(#GET{caption})</figcaption>]
+</figure>
diff --git a/www/plugins/ressource/modeles/ressource_embed.html b/www/plugins/ressource/modeles/ressource_embed.html
new file mode 100644 (file)
index 0000000..c61a87c
--- /dev/null
@@ -0,0 +1,32 @@
+[(#REM)
+
+       Modele pour <doc> en <figure>
+
+       Dans le cas d'une simple image -mode=image-, on affiche
+       le document lui-meme, sans lien de telechargement
+]
+
+[(#SET{logo,#LOGODOCUMENT})]
+[(#SET{fichier,[(#GET{logo}||extraire_attribut{src})]})]
+[(#SET{width,#ENV*{width}})]
+[(#SET{height,#ENV*{height}})]
+#SET{url,#ENV{lien,#HREF}}
+
+[(#SET{captionspip,[
+       <div class='#EDIT_TITRE spip_doc_titre'[ style='width:(#GET{width}|min{350}|max{120})px;']><strong>(#TITRE|sinon{[(#DESCRIPTIF*|oui)]})</strong></div>[
+       <div class='#EDIT_DESCRIPTIF spip_doc_descriptif quiet'[ style='width:(#GET{width}|min{350}|max{120})px;']>(#DESCRIPTIF|PtoBR)[(#NOTES|PtoBR)]</div>
+       ]
+]
+})]
+
+[(#SET{caption,[(#CAPTION
+       |sinon{#LEGEND}
+       |sinon{#LEGENDE}
+       |propre|PtoBR
+       |sinon{#GET{captionspip}}
+)]})]
+
+<figure class='[spip_document_(#ID_DOCUMENT) ]spip_documents[ spip_documents_(#ENV{align})][ (#ENV{class})] spip_lien_ok'>
+[<span class="(#IMGCLASS)">][(#ENV*{embed})][(#IMGCLASS|?{</span>})][
+<figcaption>(#GET{caption})</figcaption>]
+</figure>
diff --git a/www/plugins/ressource/paquet.xml b/www/plugins/ressource/paquet.xml
new file mode 100644 (file)
index 0000000..a8d2acf
--- /dev/null
@@ -0,0 +1,22 @@
+<paquet\r
+       prefix="ressource"\r
+       categorie="edition"\r
+       version="0.3.0"\r
+       etat="dev"\r
+       compatibilite="[2.1.14;3.1.*]"\r
+       logo="images/ressource-32.png"\r
+       documentation="http://contrib.spip.net/Ressource"\r
+>\r
+       <nom>Ressource</nom>\r
+       \r
+       <auteur>Fil</auteur>\r
+       \r
+       <licence>GNU/GPL</licence>\r
+\r
+       <!-- traduire gestionnaire="salvatore" module="ressource" reference="fr" / -->\r
+       <pipeline nom="ressource_meta" action="" />\r
+       <pipeline nom="post_typo" action="post_typo" inclure="inc/ressource.php" />\r
+       <pipeline nom="pre_liens" action="pre_liens" inclure="inc/ressource.php" />\r
+       <utilise nom="tw" compatibilite="[0.8.11;1.0.0]" />\r
+       <necessite nom="queue" />\r
+</paquet>
\ No newline at end of file
diff --git a/www/plugins/ressource/plugin.xml b/www/plugins/ressource/plugin.xml
new file mode 100644 (file)
index 0000000..b039b64
--- /dev/null
@@ -0,0 +1,32 @@
+<plugin>\r
+       <nom>Raccourci &lt;ressource&gt;</nom>\r
+       <auteur>\r
+       collectif\r
+       </auteur>\r
+       <licence>\r
+               &#169; 2012 - GNU/GPL\r
+       </licence>\r
+       <version>\r
+       0.3.0\r
+       </version>\r
+       <etat>\r
+       dev\r
+       </etat>\r
+       <description>\r
+       Un raccourci permettant d'intégrer une ressource externe en indiquant entre chevrons son URL ou son nom de fichier.\r
+       </description>\r
+       <lien>http://contrib.spip.net/Ressource</lien>\r
+       <prefix>\r
+       ressource\r
+       </prefix>\r
+       <!-- icon>\r
+       images/ressource-32.png\r
+       </icon -->\r
+       <chemin dir='' />\r
+       <categorie>edition</categorie>\r
+       <pipeline><nom>ressource_meta</nom><action></action></pipeline> \r
+       <necessite id="SPIP" version="[2.1.14;3.1.99]" />\r
+       <necessite id="tw" version="[0.8.3;1.0.0]" />\r
+       <necessite id="queue" />\r
+       <!-- traduire gestionnaire="salvatore" module="crayons" reference="fr" / -->\r
+</plugin>
\ No newline at end of file
diff --git a/www/plugins/ressource/svn.revision b/www/plugins/ressource/svn.revision
new file mode 100644 (file)
index 0000000..6315099
--- /dev/null
@@ -0,0 +1,10 @@
+<svn_revision>
+<text_version>
+Origine: file:///home/svn/repository/spip-zone/_plugins_/ressource
+Revision: 84614
+Dernier commit: 2014-09-14 23:33:31 +0200 
+</text_version>
+<origine>file:///home/svn/repository/spip-zone/_plugins_/ressource</origine>
+<revision>84614</revision>
+<commit>2014-09-14 23:33:31 +0200 </commit>
+</svn_revision>
\ No newline at end of file
diff --git a/www/plugins/rss_article_3_0/base/rssarticle.php b/www/plugins/rss_article_3_0/base/rssarticle.php
new file mode 100644 (file)
index 0000000..c74ba2b
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * Ajouter la table spip_articles_syndic
+ * @param array $tables_auxiliaires
+ * @return array
+ */
+function rssarticle_declarer_tables_auxiliaires($tables_auxiliaires){
+
+       $spip_rssarticle = array(
+                       "id_article"  => "bigint(21) NOT NULL",
+                       "id_syndic"     => "bigint(21) NOT NULL");
+         
+       $spip_rssarticle_key = array(
+                       "INDEX"         => "id_article");
+       
+       $tables_auxiliaires['spip_articles_syndic'] = array(
+                       'field' => &$spip_rssarticle,
+                       'key' => &$spip_rssarticle_key);
+               
+       return $tables_auxiliaires;
+}
+
+/**
+ * Declarer la table spip_articles_syndic dans les jointures
+ * @param array $interface
+ * @return array
+ */
+function rssarticle_declarer_tables_interfaces($interface){
+       
+       $interface['table_des_tables']['articles_syndic']='articles_syndic';
+       
+       // permet au compilateur de determiner explicitement les jointures possibles
+       // lorsqu\92une boucle sur une table demande un champ inconnu
+       $interface['tables_jointures']['spip_articles'][] = 'articles_syndic';
+
+       return $interface;
+}
+
+
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+/**
+ * Ajouter des champs a la table syndic
+ * @param array $tables_principales
+ * @return array
+ */
+function rssarticle_declarer_tables_principales($tables_principales){
+       // Extension de la table syndic
+       $tables_principales['spip_syndic']['field']['rssarticle'] = "varchar(3) DEFAULT 'non' NOT NULL"; 
+               
+       return $tables_principales;
+}
+
+
+?>
diff --git a/www/plugins/rss_article_3_0/exec/rss_article.php b/www/plugins/rss_article_3_0/exec/rss_article.php
new file mode 100644 (file)
index 0000000..12d5536
--- /dev/null
@@ -0,0 +1,47 @@
+<?php\r
+/*    plugin rss en article\r
+*\r
+*     page cachée pour les gens préssés ne pouvant pas attendre le genie\r
+*     permet de relancer manuellement la recopie  du flux en article\r
+*/\r
+\r
+\r
+if (!defined("_ECRIRE_INC_VERSION")) return;\r
+include_spip("inc/presentation");\r
+\r
+function exec_rss_article_dist()\r
+{\r
+  // si pas autorise : message d'erreur  ... admin ... a affiner\r
+  if (!autoriser('editer', 'article')) {\r
+        include_spip('inc/minipres');\r
+        echo minipres();\r
+        exit;\r
+  }\r
+  \r
+  include_spip("genie/rssarticle_copie");\r
+    \r
+       //\r
+       // affichages\r
+       // \r
+\r
+       $commencer_page = charger_fonction('commencer_page', 'inc');\r
+       echo $commencer_page(_T('rssarticle:activer_recopie_intro'), 'editer', 'editer');\r
+  // titre\r
+  echo "<br /><br /><br />\n"; // outch ! aie aie aie ! au secours !\r
+  echo gros_titre(_T('rssarticle:activer_recopie_intro'),'', false);\r
+\r
+       // colonne gauche\r
+       echo debut_gauche('', true);\r
+  echo debut_droite('', true);\r
+       \r
+       // centre de la page    \r
+       genie_rssarticle_copie_dist("manuel");\r
+  echo '<div><small>'.date('Y/m/d H:i:s').'</small><br />'._T('rssarticle:maj_manuelle').'</div>';\r
+  echo '<div style="margin:2em 0;"><a href="?exec=rss_article" style="border:1px solid;padding:0.5em;background:#fff;">'._T('rssarticle:maj_recharge').'</a></div>';\r
+  \r
+\r
+       // pied\r
+       echo fin_gauche() . fin_page();\r
+}\r
+\r
+?>\r
diff --git a/www/plugins/rss_article_3_0/formulaires/configurer_rssarticle.html b/www/plugins/rss_article_3_0/formulaires/configurer_rssarticle.html
new file mode 100644 (file)
index 0000000..7bd3197
--- /dev/null
@@ -0,0 +1,74 @@
+<div class="ajax formulaire_spip formulaire_configurer formulaire_#FORM formulaire_#FORM-#ENV{id,nouveau}">\r
+       <h3 class="titrem"><:rssarticle:activer_recopie_intro:></h3>\r
+       [<p class="reponse_formulaire reponse_formulaire_ok">(#ENV**{message_ok})</p>]\r
+       [<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV**{message_erreur})</p>]\r
+       [(#ENV{editable})\r
+       <form method='post' action='#ENV{action}'><div>\r
+               [(#REM) declarer les hidden qui declencheront le service du formulaire\r
+               parametre : url d'action ]\r
+               #ACTION_FORMULAIRE{#ENV{action}}\r
+\r
+               <ul>\r
+            <li class="editer">\r
+              <h3 class="legend"><:rssarticle:statut_article_importe:></h3>\r
+                        <ul>\r
+                            <select name="import_statut">\r
+                                <option value="prop"[(#ENV{import_statut}|=={prop}|?{' selected="selected"'})]><:sites:info_statut_site_3:></option>\r
+                                <option value="publie"[(#ENV{import_statut}|=={publie}|?{' selected="selected"'})]><:sites:info_statut_site_2:></option>\r
+                            </select>\r
+                        </ul>\r
+                \r
+            </li>\r
+            <li class="editer">\r
+                    <h3 class="legend"><:rssarticle:mode:></h3>\r
+                        <ul>\r
+                            <div class="choix">\r
+                                <input class="radio" type="radio" name="mode" value="auto" [(#ENV{mode}|=={auto}?{checked='checked',''})] id="mode_auto"/>\r
+                                <label for="mode_auto"><:rssarticle:mode_auto:></label>\r
+                            </div>\r
+                            <div class="choix">\r
+                                <input type="radio" name="mode" value="manuel" [(#ENV{mode}|=={manuel}?{checked='checked',''})] id="mode_manuel"/>\r
+                                <label for="mode_manuel"><:rssarticle:mode_manuel:></label>\r
+                            </div>\r
+                        </ul>                \r
+            </li>\r
+            <li class="editer">\r
+                    <h3 class="legend"><:rssarticle:cron_interval:></h3>\r
+                    <input type="text" name="cron_interval_value" value="[(#ENV{cron_interval_value}|!={''}|?{#ENV{cron_interval_value},600})]" size="6" class="fondl" id="cron_interval_value"/>\r
+                    <label for="cron_interval_value"><:rssarticle:cron_interval_timer:></label>\r
+                \r
+            </li>\r
+            <li class="editer">\r
+                    <h3 class="legend"><:rssarticle:suivi_syndic:></h3>\r
+                    <ul>\r
+                        <li class="editer_email_alerte">\r
+                            <input type="checkbox" name="email_alerte"[ checked="(#ENV{email_alerte}|!={''}|?{'checked'})"] class="fondl" id="email_alerte"/>\r
+                            <label for="email_alerte"><:rssarticle:email_alerte:></label>\r
+                        </li>\r
+                        <li class="editer_email_suivi">\r
+                            <input type="text" name="email_suivi" value="[(#ENV{email_suivi})]" size="20" class="fondl" id="email_suivi"/>\r
+                            <label for="email_suivi"><:rssarticle:email_alerte_email:></label>\r
+                        </li>\r
+                    </ul>              \r
+            </li>\r
+            <li class="editer">\r
+                 <h3 class="legend"><:rssarticle:copie_logo:></h3>\r
+                 <input type="checkbox" name="copie_logo"[ checked="(#ENV{copie_logo}|!={''}|?{'checked'})"] class="fondl" id="copie_logo" />\r
+                 <label for="copie_logo"><:rssarticle:copie_logo:></label>\r
+                \r
+            </li>\r
+            <li class="editer">\r
+                 <h3 class="legend"><:rssarticle:html2spip:></h3>\r
+                 <input type="checkbox" name="html2spip"[ checked="(#ENV{html2spip}|!={''}|?{'checked'})"] id="html2spip" />\r
+                 <label for="html2spip"><:rssarticle:html2spip:></label>\r
+                \r
+            </li>\r
+               </ul>\r
+\r
+         [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ]\r
+         <!--extra-->\r
+         <p class='boutons'><span class='image_loading'>&nbsp;</span>\r
+                       <input type='submit' class='submit' value='<:bouton_enregistrer:>' /></p>\r
+       </div></form>\r
+       ]\r
+</div>
\ No newline at end of file
diff --git a/www/plugins/rss_article_3_0/formulaires/editer_rssarticle.html b/www/plugins/rss_article_3_0/formulaires/editer_rssarticle.html
new file mode 100644 (file)
index 0000000..d523609
--- /dev/null
@@ -0,0 +1,22 @@
+
+<div class="formulaire_spip formulaire_editer formulaire_#FORM formulaire_#FORM-#ENV{id_syndic,nouveau}">      
+  
+  [<p class="reponse_formulaire reponse_formulaire_ok">(#ENV*{message_ok})</p>]
+       [<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV*{message_erreur})</p>]
+       
+  <form method='post' action='#ENV{action}' enctype='multipart/form-data'><div>
+               #ACTION_FORMULAIRE{#ENV{action}}
+               <ul>
+                       #SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}}#SET{obli,'obligatoire'}
+                  <li class="editer_rssarticle[ (#ENV**{erreurs}|table_valeur{rssarticle}|oui)erreur]">
+                    <div class="choix" style="margin-left:-130px">
+                      [(#CHEMIN_IMAGE{rssarticle-32.png}|balise_img{RSS})]
+                      <input type='checkbox' class="checkbox" name='rssarticle' value='oui' id='rssarticle'[ (#ENV{rssarticle}|=={oui}|?{'checked="checked"'})] />                                                     
+                      <label for="rssarticle"><:rssarticle:activer_recopie:></label>
+                    </div>
+                        [<span class='rssarticle'>(#ENV*{erreurs}|table_valeur{rssarticle})</span>]
+                  </li>
+               </ul>
+         [(#ENV{editable}|oui) <p class='boutons'><input type='submit' name="save" class='submit' value='<:bouton_enregistrer:>' /></p>]
+       </div></form>
+</div>
\ No newline at end of file
diff --git a/www/plugins/rss_article_3_0/formulaires/editer_rssarticle.php b/www/plugins/rss_article_3_0/formulaires/editer_rssarticle.php
new file mode 100644 (file)
index 0000000..5206ab7
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+include_spip('inc/autoriser');
+
+/**
+* CVT: charger
+*
+*/
+function formulaires_editer_rssarticle_charger_dist($id_syndic='new', $retour=''){
+
+       $rssarticle = sql_getfetsel('rssarticle','spip_syndic','id_syndic='.intval($id_syndic));
+       $valeurs['rssarticle'] = $rssarticle;
+       $valeurs['id_syndic'] = $id_syndic;
+       $valeurs['editable'] = true;
+       
+       if (!autoriser('modifier', 'syndic', $id_syndic))
+               $valeurs['editable'] = false;
+
+       return $valeurs;
+}
+
+/**
+* CVT: verifer
+*
+*/
+function formulaires_editer_rssarticle_verifier_dist($id_syndic='new', $retour=''){
+       $erreurs = array();
+       return $erreurs;
+}
+
+/**
+* CVT: traiter
+*
+*/
+function formulaires_editer_rssarticle_traiter_dist($id_syndic='new', $retour=''){
+       
+
+  if (_request('rssarticle')=='oui') {
+       sql_updateq('spip_syndic',array(
+        'rssarticle'=> 'oui',
+        'resume'=>'non',
+        'oubli'=>'oui'
+       ),'id_syndic='.intval($id_syndic)); 
+       
+       // on force le site en mode oubli et pas resume 
+       // on rensynchronise la syndic pour passer les anciens articles (qui etaient ss doute en mode resume) en mode complet HTML
+       //sql_delete("spip_syndic_articles", "id_syndic=".sql_quote($id_syndic)); // alternative ;)
+       include_spip('genie/syndic');        
+            $t = syndic_a_jour($id_syndic); 
+  } else {
+       sql_updateq('spip_syndic',array('rssarticle'=> 'non'),'id_syndic='.intval($id_syndic)); 
+  }
+       
+  
+  $message = array('editable'=>true, 'message_ok'=>_T("rssarticle:site_maj"));
+
+       return $message;
+       
+}
+
+?>
\ No newline at end of file
diff --git a/www/plugins/rss_article_3_0/genie/rssarticle_copie.php b/www/plugins/rss_article_3_0/genie/rssarticle_copie.php
new file mode 100644 (file)
index 0000000..5f5a1e8
--- /dev/null
@@ -0,0 +1,268 @@
+<?php\r
+/**\r
+ * Plugin RSS article pour Spip 3.0\r
+ * Licence GPL\r
+ * \r
+ *\r
+ */\r
+\r
+// TODO\r
+// - gerer les mots-clés hors enclosure ?\r
+include_spip("inc/mail");\r
+include_spip('inc/filtres'); \r
+include_spip('inc/distant');\r
+include_spip('inc/chercher_logo');\r
+include_spip('inc/rubriques');\r
+include_spip('inc/config');\r
+\r
+function genie_rssarticle_copie_dist($t){  \r
+\r
+  // configuration (ou valeurs par defaut)    \r
+  if (lire_config('rssarticle/import_statut')=="publie")       $import_statut="publie"; else  $import_statut="prop";     \r
+  if (lire_config('rssarticle/mode')=="auto")       $mode_auto=true; else  $mode_auto=false;  \r
+  if (lire_config('rssarticle/email_alerte')=="on") $email_alerte=true; else  $email_alerte=false;\r
+  if (lire_config('rssarticle/copie_logo')=="on")   $copie_logo=true; else  $copie_logo=false; \r
+  if (lire_config('rssarticle/html2spip')=="on")   $html2spip=true; else  $html2spip=false; \r
+  $email_suivi = lire_config('rssarticle/email_suivi'); \r
+  \r
+  // autres valeurs\r
+  $accepter_forum =    substr($GLOBALS['meta']['forums_publics'],0,3);\r
+  \r
+  // principe de pile:\r
+  // on boucle sur les derniers articles syndiques pour les retirer ensuite\r
+  // bourrin voir les requetes avec jointure du Miroir ou du site Rezo \r
+  $log = "";\r
+  $log_c = 0;\r
+  \r
+  // boucle sur les sites publies \r
+  if ($mode_auto) $u = sql_select("id_syndic,id_rubrique,id_secteur","spip_syndic","statut='publie'");   // tous \r
+          else    $u = sql_select("id_syndic,id_rubrique,id_secteur","spip_syndic","statut='publie' AND rssarticle='oui'");\r
+  \r
+  while ($b = sql_fetch($u)) {\r
+       $id_syndic = (int) $b['id_syndic'];\r
+       $id_rubrique = (int) $b['id_rubrique'];\r
+       $id_secteur = (int) $b['id_secteur'];\r
+  \r
+       // sur chaque site copie les derniers syndication\r
+       $s = sql_select("*", "spip_syndic_articles", "statut='publie' AND id_syndic='$id_syndic'","","maj DESC","10");  // par flot de 10 articles / site pour limiter la charge\r
+       while ($a = sql_fetch($s)) {\r
+                       $titre =  $a['titre'];\r
+                       $url =  $a['url'];\r
+          $id_syndic_article = $a['id_syndic_article']; \r
+                    \r
+          // article avec mm titre existe ? (test doublons sur l'url plutot que sr le titre)\r
+               if (!$row = sql_fetsel("id_article","spip_articles","url_site=".sql_quote($url))) {        \r
+            \r
+            $texte = $a['descriptif'];\r
+            //traitement pour syntaxe SPIP\r
+            if($html2spip)\r
+                  $texte = html2spip($texte);\r
+            $lang  = $a['lang'];\r
+            $url   = $a['url'];\r
+            $tags =  $a['tags'];\r
+            $lsDate = $a['date'];\r
+            \r
+          \r
+            if ($lang=="")     \r
+                $lang = $GLOBALS['spip_lang'];  \r
+                \r
+            // cas particulier: \r
+            // site multilingue avec la configuration: 1 lang par rubrique  \r
+            // on force l'article a avoir la langue de la rubrique ds lequel il est importee(pour omaidi)            \r
+            if ($GLOBALS['meta']['multi_rubriques']=='oui') {\r
+                  $s_lang = sql_select("lang", "spip_rubriques", "id_rubrique=$id_rubrique");\r
+                  while ($a_lang = sql_fetch($s_lang)) \r
+                      $lang = $a_lang['lang'];                   \r
+            }\r
+            \r
+            //$lsDate = date('Y-m-d H:i:s');            \r
+            // creation de l'article\r
+            $id_article = sql_insertq( 'spip_articles', array(\r
+                                'titre'=>$titre, 'id_rubrique'=>$id_rubrique,\r
+                                'texte'=>$texte, 'statut'=>$import_statut, 'id_secteur'=>$id_secteur,\r
+                                'date'=> $lsDate, 'accepter_forum'=>$accepter_forum, 'lang'=>$lang, 'url_site'=>$url));\r
+                                \r
+            // lier article et site\r
+            sql_insertq( 'spip_articles_syndic', array('id_article'=>$id_article, 'id_syndic'=>$id_syndic));\r
+                                \r
+            // gestion auteur            \r
+            $auteurs= explode(", ",$a['lesauteurs']);            \r
+            foreach ($auteurs as $k => $auteur) {                       \r
+                 if ($current_id_auteur = rssarticle_get_id_auteur($auteur))\r
+                      sql_insertq( 'spip_auteurs_liens', array('id_auteur'=>$current_id_auteur, 'id_objet'=>$id_article, 'objet'=>'article'));                \r
+            }\r
+            \r
+            // tags a convertir en documents distants \r
+            $doc_distants = extraire_enclosures($tags);\r
+                       foreach ($doc_distants as $k=>$doc_distant) {\r
+                $infos = recuperer_infos_distantes($doc_distant);\r
+                if ($infos['extension']) {\r
+                    $ext    = $infos['extension'];\r
+                    $taille = $infos['tailles']; \r
+                    $row = sql_fetsel("inclus", "spip_types_documents", "extension=" . sql_quote($ext) . " AND upload='oui'");  // extension autorisee ?\r
+                    if ($row) {\r
+                          $id_document = sql_insertq( 'spip_documents', array(\r
+                                'extension'=>$ext, \r
+                                'date'=> $lsDate,\r
+                                'fichier'=> $doc_distant,\r
+                                'taille'=> $taille,\r
+                                'mode' => 'document',\r
+                                'distant' => 'oui'));\r
+                          \r
+                         sql_insertq( 'spip_documents_liens', array(\r
+                                'id_document' =>$id_document, \r
+                                'id_objet'=> $id_article,\r
+                                'objet'=> 'article',\r
+                                'vu'=> 'non'));                                        \r
+                    }\r
+                }\r
+                \r
+            }\r
+            \r
+            // logo\r
+            if ($copie_logo) {             \r
+               if ($logo_site = inc_chercher_logo_dist($id_syndic,"id_syndic")) {\r
+                  $logo_article = "arton$id_article.".$logo_site[3];\r
+                  @copy($logo_site[0],_DIR_LOGOS."$logo_article");\r
+               }                 \r
+            }\r
+                                                                       \r
+                       $log_c++;\r
+                       $log .= "\n - $titre";  \r
+            \r
+             // on "depublie" l'article syndique qui vient d'etre copie\r
+            sql_update("spip_syndic_articles", array('statut' => '"refuse"'), "id_syndic_article=$id_syndic_article");\r
+\r
+            // Mise à jour des dates de rubriques après création d'un article dedans\r
+           if ($id_article) {\r
+               if (function_exists('calculer_rubriques'))\r
+                   calculer_rubriques();\r
+               if (function_exists('calculer_langues_rubriques'))\r
+                   calculer_langues_rubriques();\r
+               if (function_exists('propager_les_secteurs'))\r
+                   propager_les_secteurs();\r
+           }\r
+                  \r
+          }  // test doublons\r
+       }  \r
+  } // FIN PILE\r
+    \r
+       \r
+       // log et alerte email\r
+  $log .= "\n\n---------\nPlugin Copie RSS en Articles: $log_c articles copies\n";\r
+  spip_log($log);\r
+  $log .= $GLOBALS['meta']['adresse_site']."/ecrire/?exec=accueil";\r
+       \r
+  if ($email_alerte && $email_suivi !="" && $log_c > 0)                 \r
+                  envoyer_mail($email_suivi,"Copie RSS en Articles", $log);\r
+              \r
+       // maintenance generale\r
+  // mode auto: on efface les syndic_articles de plus de 2 mois pour soulager le systeme (cf genie/syndic) \r
+  // attention: on efface sur l'ensemble des sites syndiques ss tenir compte de l'option               \r
+       if ($mode_auto) sql_delete('spip_syndic_articles', "maj < DATE_SUB(NOW(), INTERVAL 2 MONTH) AND date < DATE_SUB(NOW(), INTERVAL 2 MONTH)");\r
\r
+       return 1;\r
+}\r
+\r
+\r
+//\r
+// recupere id d'un auteur selon son nom sinon le creer\r
+function rssarticle_get_id_auteur($nom) {  \r
+   if (trim($nom)=="") \r
+        return false;\r
+   \r
+   if ($row = sql_fetsel(array("id_auteur"),"spip_auteurs","nom=".sql_quote($nom)))  \r
+        return $row['id_auteur']; \r
+\r
+    // auteur inconnu, on le cree ... \r
+    return sql_insertq('spip_auteurs',array('nom'=>$nom,'statut'=>'1comite'));\r
+}\r
+\r
+//\r
+// extraire les documents taggues enclosure \r
+// voir http://doc.spip.org/@afficher_enclosures\r
+function extraire_enclosures($tags) {\r
+       $s = array();\r
+       foreach (extraire_balises($tags, 'a') as $tag) {\r
+               if (extraire_attribut($tag, 'rel') == 'enclosure'\r
+               AND $t = extraire_attribut($tag, 'href')) {\r
+                       $s[] = $t;\r
+               }\r
+       }\r
+       return $s;\r
+}\r
+\r
+/**\r
+ * \r
+ * Nettoyer l'utf-8 et ses accents \r
+ *\r
+**/\r
+function clean_utf8($t) {\r
+       if (!preg_match('!\S!u', $t))\r
+               $t = preg_replace_callback(',&#x([0-9a-f]+);,i', 'utf8_do', utf8_encode(utf8_decode($t)));\r
+       return $t;\r
+}\r
+\r
+\r
+//passe le html en SPIP\r
+//repris de memo.php, merci\r
+\r
+function html2spip($lapage){\r
+       $lapage=clean_utf8($lapage);\r
+       \r
+       // remettre les double quotes casé par texte_backend\r
+       $lapage = str_replace('&#034;','"',$lapage);\r
+       \r
+       // PRETRAITEMENTS\r
+       $lapage = str_replace("\n\r", "\r", $lapage); // echapper au greedyness de preg_replace\r
+       $lapage = str_replace("\n", "\r", $lapage);\r
+\r
+       // itals\r
+       $lapage = preg_replace(",<(i|em)( [^>\r]*)?".">(.+)</\\1>,Uims", "{\\3}", $lapage);\r
+       \r
+       // gras (pas de {{ pour eviter tout conflit avec {)\r
+       $lapage = preg_replace(",<(b|h[4-6])( [^>]*)?".">(.+)</\\1>,Uims", "@@b@@\\3@@/b@@", $lapage);\r
+       $lapage = preg_replace(",<strong( [^>]*)?".">(.+)</strong>,Uims", "@@b@@\\2@@/b@@", $lapage);\r
+       \r
+       // entites\r
+       include_spip('inc/charsets');\r
+       $lapage = html2unicode($lapage, true); //secure?\r
+               \r
+       // liens avec possibilités de non fermeture du tag\r
+       $lapage = preg_replace(",<a[ \t\n\r][^<>]*href=[^<>]*(http[^<>]*)[^<>]>(.*?)<,uims", "[\\2->\\1] <", $lapage);\r
+\r
+       // images (cf ressource)\r
+       $lapage = preg_replace(",<img[ \t\n\r][^<>]*src=[^<>]*(http[^<>'\"]*)[^<>]*>,uims","[img]\\1[//img]", $lapage);\r
+\r
+               \r
+       // intertitres\r
+       $lapage = preg_replace(",<(h[1-3])( [^>]*)?".">(.+)</\\1>,Uims", "\r{{{ \\3 }}}\r", $lapage);\r
+       // tableaux\r
+       $lapage = preg_replace(",<tr( [^>]*)?".">,Uims", "<br />\r", $lapage);\r
+       $lapage = preg_replace(",<t[hd]( [^>]*)?".">,Uims", " | ", $lapage);\r
+\r
+       // POST TRAITEMENT\r
+       $lapage = str_replace("\r", "\n", $lapage);\r
+\r
+       // SUPPRIME LES TAGS\r
+       if (eregi("<title.*>(.*)</title>", $lapage, $regs))\r
+       $titre = textebrut($regs[1]);\r
+       $lapage = textebrut($lapage);\r
+       \r
+       // Suite tableaux\r
+       $lapage = preg_replace(",\n[| ]+\n,", "", $lapage);\r
+       $lapage = preg_replace(",\n[|].+?[|].+?[|].+,", "\\0|\r", $lapage);\r
+       \r
+       // retablir les gras\r
+       $lapage = preg_replace(",@@b@@(.*)@@/b@@,Uims","{{\\1}}",$lapage);\r
+       \r
+       //retablir les images pour les lire avec le plugin ressource\r
+       $lapage = preg_replace('#\[img\](.*)\[\//img\]#Umis', "<$1>", $lapage);\r
+       \r
+       //nettoyer les "] qui dépassent parfois\r
+       $lapage = preg_replace(",\"\],uims", "]", $lapage);\r
+       \r
+       return $lapage;\r
+}\r
+\r
+?>
\ No newline at end of file
diff --git a/www/plugins/rss_article_3_0/lang/paquet-rssarticle_fr.php b/www/plugins/rss_article_3_0/lang/paquet-rssarticle_fr.php
new file mode 100644 (file)
index 0000000..b6c94da
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // R
+       'rssarticle_description' => 'Ce plugin recopie les flux RSS (articles syndiqués) en articles\r
+\r
+-* reprise du contenu du flux;\r
+-* créé l\'auteur s\'il est mentionné dans le flux;\r
+-* ajoute les documents distants présents dans le flux;\r
+-* dans le champs URL de l\'article, on indique l\'adresse de l\'article d\'origine.\r
+\r
+Pour éviter les doublons et les imports successifs, une fois l\'article créé, l\'article syndiqué est rejeté (ce qui permet de suivre où en sont les recopiés).',
+       'rssarticle_nom' => 'Flux RSS en articles',
+       'rssarticle_slogan' => 'Recopie les flux RSS en articles',
+);
+
+?>
\ No newline at end of file
diff --git a/www/plugins/rss_article_3_0/lang/rssarticle_ar.php b/www/plugins/rss_article_3_0/lang/rssarticle_ar.php
new file mode 100644 (file)
index 0000000..726711c
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+  // A
+  'article_origine' => '&#1607;&#1584;&#1575; &#1575;&#1604;&#1605;&#1602;&#1575;&#1604; &#1605;&#1606;&#1602;&#1608;&#1604; &#1593;&#1606; &#1605;&#1608;&#1602;&#1593;',
+  'activer_recopie_intro' => '&#1578;&#1583;&#1601;&#1602; RSS &#1575;&#1604;&#1609; &#1575;&#1604;&#1605;&#1602;&#1575;&#1604;&#1575;&#1578;',
+  'activer_recopie' => '&#1606;&#1587;&#1582; &#1575;&#1604;&#1605;&#1602;&#1575;&#1604;&#1575;&#1578; &#1575;&#1604;&#1606;&#1575;&#1578;&#1581;&#1577; &#1593;&#1606; &#1578;&#1583;&#1601;&#1602; RSS &#1575;&#1604;&#1609; &#1605;&#1602;&#1575;&#1604;&#1575;&#1578; SPIP',
+
+  // C
+  'citer_source' => '&#1603;&#1585; &#1575;&#1604;&#1605;&#1589;&#1583;&#1585;',
+  'citer_source_oui' => '&#1584;&#1603;&#1585; &#1593;&#1606;&#1608;&#1575;&#1606; &#1575;&#1604;&#1605;&#1602;&#1575;&#1604; &#1575;&#1604;&#1605;&#1589;&#1583;&#1585; &#1601;&#1610; &#1575;&#1604;&#1605;&#1602;&#1575;&#1604; &#1575;&#1604;&#1605;&#1587;&#1578;&#1608;&#1585;&#1583;',
+  'copie_logo' => '&#1606;&#1587;&#1582; &#1588;&#1593;&#1575;&#1585; &#1575;&#1604;&#1605;&#1608;&#1602;&#1593; &#1608;&#1580;&#1593;&#1604;&#1607; &#1587;&#1593;&#1575;&#1585; &#1575;&#1604;&#1605;&#1602;&#1575;&#1604;',
+
+  // S
+  'statut_article_importe' => '&#1608;&#1590;&#1593;&#1610;&#1577; &#1575;&#1604;&#1605;&#1602;&#1575;&#1604;&#1575;&#1578; &#1575;&#1604;&#1605;&#1587;&#1578;&#1608;&#1585;&#1583;&#1577;',
+  'suivi_syndic' => '&#1605;&#1578;&#1575;&#1576;&#1593;&#1577; &#1575;&#1604;&#1578;&#1585;&#1582;&#1610;&#1589;',
+
+  // E
+  'email_alerte' => '&#1573;&#1606;&#1584;&#1575;&#1585; &#1576;&#1575;&#1604;&#1576;&#1585;&#1610;&#1583; &#1575;&#1604;&#1573;&#1604;&#1603;&#1578;&#1585;&#1608;&#1606;&#1610; &#1593;&#1606;&#1583; &#1603;&#1604; &#1578;&#1585;&#1582;&#1610;&#1589; &#1601;&#1610; &#1605;&#1602;&#1575;&#1604;&#1567;',
+  'email_alerte_email' => '&#1601;&#1610; &#1581;&#1575;&#1604; &#1575;&#1604;&#1605;&#1608;&#1575;&#1601;&#1602;&#1577;&#1548; &#1593;&#1604;&#1609; &#1571;&#1610; &#1593;&#1606;&#1608;&#1575;&#1606; &#1610;&#1578;&#1605; &#1573;&#1585;&#1587;&#1575;&#1604; &#1575;&#1604;&#1573;&#1606;&#1584;&#1575;&#1585;&#1567;',
+
+  // I
+  'install_rssarticle' => '&#1573;&#1606;&#1588;&#1575;&#1569; &#1580;&#1583;&#1608;&#1604; spip_articles_syndic',
+
+  // M
+  'mode' => '&#1608;&#1590;&#1593;&#1610;&#1577; &#1575;&#1604;&#1578;&#1588;&#1594;&#1610;&#1604;',
+  'mode_auto' => '&#1575;&#1604;&#1608;&#1590;&#1593;&#1610;&#1577; &#1575;&#1604;&#1570;&#1604;&#1610;&#1577;: &#1606;&#1587;&#1582; &#1603;&#1604; &#1575;&#1604;&#1605;&#1608;&#1575;&#1602;&#1593; &#1575;&#1604;&#1605;&#1576;&#1608;&#1576;&#1577; &#1603;&#1605;&#1602;&#1575;&#1604;&#1575;&#1578;',
+  'mode_manuel' => '&#1575;&#1604;&#1608;&#1590;&#1593;&#1610;&#1577; &#1575;&#1604;&#1610;&#1583;&#1608;&#1610;&#1577;: &#1578;&#1581;&#1583;&#1583; &#1610;&#1583;&#1608;&#1610;&#1575;&#1611; &#1575;&#1604;&#1605;&#1608;&#1575;&#1602;&#1593; &#1575;&#1604;&#1605;&#1576;&#1608;&#1576;&#1577; &#1575;&#1604;&#1578;&#1610; &#1578;&#1585;&#1610;&#1583; &#1606;&#1587;&#1582;&#1607;&#1575; &#1603;&#1605;&#1602;&#1575;&#1604;&#1575;&#1578;'
+
+);
+
+
+?>
diff --git a/www/plugins/rss_article_3_0/lang/rssarticle_fr.php b/www/plugins/rss_article_3_0/lang/rssarticle_fr.php
new file mode 100644 (file)
index 0000000..c8686b1
--- /dev/null
@@ -0,0 +1,49 @@
+<?php\r
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP\r
+if (!defined("_ECRIRE_INC_VERSION")) return;\r
+\r
+\r
+$GLOBALS[$GLOBALS['idx_lang']] = array(\r
+\r
+  // A\r
+  'article_origine' => 'Cet article est repris du site', \r
+  'activer_recopie_intro' => 'Flux RSS en Articles',\r
+  'activer_recopie' => 'Copier les articles issus de ce flux RSS en articles SPIP',\r
+\r
+  // C\r
+  'citer_source' => 'Citer la source',\r
+  'citer_source_oui' => 'Citer l\'URL de l\'article d\'origine dans l\'article import&eacute;',\r
+  'configuration_rssarticle' => 'Flux RSS en articles',\r
+  'copie_logo' => 'Recopier le logo du site comme logo d\'article',\r
+  'cron_interval' => 'Fréquence de la copie des flux RSS en articles',\r
+  'cron_interval_timer' => 'Intervalle en seconde ',\r
+  \r
+  //R\r
+  'html2spip' => 'Passer le HTML en syntaxe SPIP. Utilisez le plugin "ressource" pour afficher ensuite les images.',\r
+\r
+  // S\r
+  'statut_article_importe' => 'Statut des articles import&eacute;s',\r
+  'suivi_syndic' => 'Suivi de la syndication',\r
+  'site_maj' => 'Option enregistrée',\r
+  \r
+  // E\r
+  'email_alerte' => 'Pr&eacute;venir par email &agrave; chaque nouvelle syndication en articles ?',\r
+  'email_alerte_email' => 'Si oui, sur quel email ? ',\r
+  \r
+  // I\r
+  'install_rssarticle' => 'Cr&eacute;ation de la table spip_articles_syndic',\r
+  \r
+  // M\r
+  'maj_manuelle' => 'La copie manuelle des derniers flux RSS en articles a été effectuée',\r
+  'maj_recharge' => 'Relancer la copie manuelle',\r
+  'mode' => 'Mode de fonctionnement',\r
+  'mode_auto' => 'Mode automatique: tous les sites r&eacute;f&eacute;renc&eacute;s sont recopi&eacute;s en articles',\r
+  'mode_manuel' => 'Mode manuel: vous selectionnez manuellement les sites r&eacute;f&eacute;renc&eacute;s qui doivent être recopi&eacute;s en articles',\r
+  \r
+  // T\r
+  'titre_page_configurer_rssarticle' => 'Copie RSS en articles'\r
+  \r
+);\r
+\r
+\r
+?>\r
diff --git a/www/plugins/rss_article_3_0/paquet.xml b/www/plugins/rss_article_3_0/paquet.xml
new file mode 100644 (file)
index 0000000..e238c7c
--- /dev/null
@@ -0,0 +1,21 @@
+<paquet\r
+       prefix="rssarticle"\r
+       categorie="edition"\r
+       version="1.1.3"\r
+       etat="stable"\r
+       compatibilite="[3.0.2;3.1.*]"\r
+       logo="prive/themes/spip/images/rssarticle-64.png"\r
+       documentation="http://contrib.spip.net/Flux-RSS-en-articles"\r
+       schema="1.0.0" \r
+>\r
+       <nom>Flux RSS en articles</nom>\r
+       <auteur lien='http://www.erational.org'>erational</auteur>\r
+  <auteur>LudoRA</auteur>\r
+       <licence>GNU/GPL v3</licence>\r
+        <pipeline nom="declarer_tables_principales" inclure="base/rssarticle.php"/>\r
+        <pipeline nom="declarer_tables_interfaces" inclure="base/rssarticle.php"/>\r
+        <pipeline nom="declarer_tables_auxiliaires" inclure="base/rssarticle.php"/>\r
+        <pipeline nom="taches_generales_cron" inclure="rssarticle_pipelines.php"/>\r
+        <pipeline nom="affiche_milieu" inclure="rssarticle_pipelines.php"/>\r
+       <menu nom="configurer_rssarticle" titre="rssarticle:configuration_rssarticle" parent="menu_configuration" icone="images/rssarticle-16.png" />\r
+</paquet>\r
diff --git a/www/plugins/rss_article_3_0/prive/contenu/rssarticle.html b/www/plugins/rss_article_3_0/prive/contenu/rssarticle.html
new file mode 100644 (file)
index 0000000..6de5685
--- /dev/null
@@ -0,0 +1,3 @@
+<div class="ajax">
+  #FORMULAIRE_EDITER_RSSARTICLE{#ID_SYNDIC}
+</div>
\ No newline at end of file
diff --git a/www/plugins/rss_article_3_0/prive/squelettes/contenu/configurer_rssarticle.html b/www/plugins/rss_article_3_0/prive/squelettes/contenu/configurer_rssarticle.html
new file mode 100644 (file)
index 0000000..970c186
--- /dev/null
@@ -0,0 +1,7 @@
+[(#AUTORISER{configurer,_rssarticle}|sinon_interdire_acces)]
+
+<h1 class="grostitre"><:rssarticle:titre_page_configurer_rssarticle:></h1>
+
+<div class="ajax">
+       #FORMULAIRE_CONFIGURER_RSSARTICLE
+</div>
\ No newline at end of file
diff --git a/www/plugins/rss_article_3_0/prive/themes/spip/images/rssarticle-128.png b/www/plugins/rss_article_3_0/prive/themes/spip/images/rssarticle-128.png
new file mode 100644 (file)
index 0000000..edf8166
Binary files /dev/null and b/www/plugins/rss_article_3_0/prive/themes/spip/images/rssarticle-128.png differ
diff --git a/www/plugins/rss_article_3_0/prive/themes/spip/images/rssarticle-16.png b/www/plugins/rss_article_3_0/prive/themes/spip/images/rssarticle-16.png
new file mode 100644 (file)
index 0000000..ce22674
Binary files /dev/null and b/www/plugins/rss_article_3_0/prive/themes/spip/images/rssarticle-16.png differ
diff --git a/www/plugins/rss_article_3_0/prive/themes/spip/images/rssarticle-32.png b/www/plugins/rss_article_3_0/prive/themes/spip/images/rssarticle-32.png
new file mode 100644 (file)
index 0000000..391ac79
Binary files /dev/null and b/www/plugins/rss_article_3_0/prive/themes/spip/images/rssarticle-32.png differ
diff --git a/www/plugins/rss_article_3_0/prive/themes/spip/images/rssarticle-64.png b/www/plugins/rss_article_3_0/prive/themes/spip/images/rssarticle-64.png
new file mode 100644 (file)
index 0000000..edf8166
Binary files /dev/null and b/www/plugins/rss_article_3_0/prive/themes/spip/images/rssarticle-64.png differ
diff --git a/www/plugins/rss_article_3_0/rssarticle_administrations.php b/www/plugins/rss_article_3_0/rssarticle_administrations.php
new file mode 100644 (file)
index 0000000..5548c82
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+$GLOBALS['rssarticle_base_version'] = 0.3;
+    
+function rssarticle_upgrade(){
+
+               $version_base = $GLOBALS['rssarticle_base_version'];
+               $current_version = 0.0;
+               if ((!isset($GLOBALS['meta']['rssarticle_base_version']) )
+                               || (($current_version = $GLOBALS['meta']['rssarticle_base_version'])!=$version_base)){
+                       include_spip('base/rssarticle');
+                       if ($current_version==0.0){
+                               include_spip('base/create');
+                               include_spip('base/abstract_sql');
+                               creer_base();   
+                               maj_tables('spip_syndic');
+        echo "<p>"._T('rssarticle:install_rssarticle')."</p>"; 
+                         ecrire_meta('rssarticle_base_version',$current_version=$version_base,'non');                          
+                       }
+                       
+                       if (version_compare($current_version,"0.3","<")){
+                         include_spip('base/create');
+                         maj_tables('spip_syndic');
+                         echo "<p>"._T('rssarticle:mise_a_jour_v03')."</p>"; 
+                         ecrire_meta('rssarticle_base_version',$current_version="0.3",'non');
+                 }
+               
+                       ecrire_metas();
+               }
+}
+
+
+       
+function rssarticle_install($action){
+               $version_base = $GLOBALS['rssarticle_base_version'];
+               switch ($action){
+                       case 'test':
+                               return (isset($GLOBALS['meta']['rssarticle_base_version']) AND ($GLOBALS['meta']['rssarticle_base_version']>=$version_base));
+                               break;
+                       case 'install':
+                               rssarticle_upgrade();
+                               break;
+                       case 'uninstall':
+                               rssarticle_vider_tables();                                                              
+                               break;
+               }
+}
+
+/**
+ * Desinstallation du plugin
+ *
+ * @param string $nom_meta_base_version
+ */
+       
+function rssarticle_vider_tables() {
+    sql_alter("TABLE spip_syndic DROP rssarticle");
+               spip_query("DROP TABLE spip_articles_syndic");
+               effacer_meta('rssarticle_base_version');
+               ecrire_metas();
+}
+?>
diff --git a/www/plugins/rss_article_3_0/rssarticle_pipelines.php b/www/plugins/rss_article_3_0/rssarticle_pipelines.php
new file mode 100644 (file)
index 0000000..491da0c
--- /dev/null
@@ -0,0 +1,51 @@
+<?php\r
+/**\r
+ * genie / cron\r
+ *\r
+ */\r
+function rssarticle_taches_generales_cron($taches_generales){\r
+  $delai =  60*10; // valeur defaut: ts les 10 min \r
+  \r
+  // si cfg dispo, on charge les valeurs\r
+  if (function_exists(lire_config))  {\r
+        if (lire_config('rssarticle/cron_interval_value')!="") {    // verifier si champs CFG a ete renseigne sur ce plugin (retro-compat)       \r
+            $delai = intval(lire_config('rssarticle/cron_interval_value')); \r
+            if ($delai<10)    \r
+                      $delai=10;        // securite pour les valeurs absurdes             \r
+        }      \r
+  } \r
+       $taches_generales['rssarticle_copie'] = $delai;\r
+  \r
+       return $taches_generales;\r
+}\r
+\r
+/**\r
+ * Insertion au centre des pages d'articles dans le privé\r
+ * Affiche un formulaire d'édition de la licence de l'article\r
+ *\r
+ * @param array $flux Le contexte du pipeline\r
+ */\r
+function rssarticle_affiche_milieu($flux) {\r
+    if ($flux['args']['exec'] == 'site'){\r
+        include_spip('inc/config');\r
+        if (lire_config('rssarticle/mode')=="auto") $mode_auto=true; else  $mode_auto=false;\r
+        \r
+        if (!$mode_auto) {\r
+            $contexte['id_syndic'] = $flux["args"]["id_syndic"];\r
+            //$out = debut_cadre_relief(_DIR_PLUGIN_RSSARTICLE."prive/themes/spip/images/rssarticle-32.png", true, '',_T("rssarticle:activer_recopie_intro"));\r
+            $out .= "\n<div id='bloc_rssarticle'>";\r
+            $out .= "\n". recuperer_fond('prive/contenu/rssarticle',$contexte,array('ajax'=>false));\r
+            $out .= "\n</div>";\r
+            //$out .= "\n". fin_cadre_relief(true);\r
+            if ($p=strpos($flux['data'],'<!--affiche_milieu-->'))\r
+                $flux['data'] = substr_replace($flux['data'],$out,$p,0);\r
+                \r
+               \r
+        }\r
+    }\r
+    return $flux;\r
+}\r
+\r
+\r
+\r
+?>\r
diff --git a/www/plugins/rss_article_3_0/svn.revision b/www/plugins/rss_article_3_0/svn.revision
new file mode 100644 (file)
index 0000000..72eac51
--- /dev/null
@@ -0,0 +1,10 @@
+<svn_revision>
+<text_version>
+Origine: file:///home/svn/repository/spip-zone/_plugins_/rss_article/trunk
+Revision: 86047
+Dernier commit: 2014-11-13 16:19:17 +0100 
+</text_version>
+<origine>file:///home/svn/repository/spip-zone/_plugins_/rss_article/trunk</origine>
+<revision>86047</revision>
+<commit>2014-11-13 16:19:17 +0100 </commit>
+</svn_revision>
\ No newline at end of file
diff --git a/www/plugins/saisies/action/deplacer_saisie.php b/www/plugins/saisies/action/deplacer_saisie.php
new file mode 100644 (file)
index 0000000..a28e532
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+function action_deplacer_saisie_dist() {
+       include_spip('inc/session');
+       
+       $session         = _request('session');
+       $identifiant = _request('saisie');
+       $ou          = _request('ou');
+
+       // On récupère le formulaire à son état actuel
+       $formulaire_actuel = session_get($session);
+
+       if (!$formulaire_actuel) {
+               return "";
+       }
+
+       include_spip('inc/saisies');
+       
+       $saisies_actuelles = saisies_lister_par_identifiant($formulaire_actuel);
+       if (!isset($saisies_actuelles[$identifiant])) {
+               return "";
+       }
+
+       // tester @id et [@id] (fieldset)
+       if ($ou and !isset($saisies_actuelles[$ou]) and !isset($saisies_actuelles[ substr($ou,1,-1) ])) {
+               return "";
+       }
+
+       // on deplace ou c'est demande...
+       $formulaire_actuel = saisies_deplacer($formulaire_actuel, $identifiant, $ou);
+
+       // On sauve tout ca
+       $formulaire_actuel = session_set($session, $formulaire_actuel);
+}
+
+?>
diff --git a/www/plugins/saisies/aide/saisies.html b/www/plugins/saisies/aide/saisies.html
new file mode 100644 (file)
index 0000000..9b07f38
--- /dev/null
@@ -0,0 +1,5 @@
+<h1>Références complètes des saisies</h1>
+
+[(#ENV{format}|=={brut}|oui)<textarea style="width:100%; height:100%;">]
+[(#VAL|saisies_generer_aide)]
+[(#ENV{format}|=={brut}|oui)</textarea>]
diff --git a/www/plugins/saisies/balise/configurer_saisie.php b/www/plugins/saisies/balise/configurer_saisie.php
new file mode 100644 (file)
index 0000000..33902c1
--- /dev/null
@@ -0,0 +1,28 @@
+<?php 
+
+// Sécurité
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+function balise_CONFIGURER_SAISIE_dist($p){
+
+       // On recupere le premier argument : le nom de la saisie
+       $saisie = Pile::recuperer_et_supprimer_argument_balise(1, $p);
+       
+       // On ajoute le squelette a inclure dans les parametres
+       $p = Pile::creer_et_ajouter_argument_balise($p, 'fond', 'inclure/configurer_saisie');
+       
+       // On ajoute l'environnement
+       $p = Pile::creer_et_ajouter_argument_balise($p, 'env');
+       
+       // On ajoute le nom recupere
+       $p = Pile::creer_et_ajouter_argument_balise($p, 'saisie', $saisie);
+       
+       // On redirige vers la balise INCLURE
+       if(function_exists('balise_INCLURE'))
+               return balise_INCLURE($p);
+       else
+               return balise_INCLURE_dist($p); 
+
+}
+
+?>
diff --git a/www/plugins/saisies/balise/generer_saisies.php b/www/plugins/saisies/balise/generer_saisies.php
new file mode 100644 (file)
index 0000000..83e4401
--- /dev/null
@@ -0,0 +1,49 @@
+<?php 
+
+/**
+ * Gestion de la balise GENERER_SAISIES
+ *
+ * @package SPIP\Saisies\Balises
+ */
+
+// Sécurité
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+/**
+ * Compile la balise GENERER_SAISIES
+ *
+ * La balise accepte 1 paramètre qui est une liste de descriptions de saisies
+ * dont on veut générer le HTML affichant les champs du formulaires
+ *
+ * Cette balise est un raccourcis :
+ * - #GENERER_SAISIES{#TABLEAU_DE_SAISIES} est équivalent à
+ * - #INCLURE{fond=generer_saisies,env,saisies=#TABLEAU_DE_SAISIES}
+ *
+ * @param Champ $p
+ *     Pile au niveau de la balise
+ * @return Champ
+ *     Pile complété du code à générer
+**/
+function balise_GENERER_SAISIES_dist($p){
+
+       // On recupere le premier (et seul) argument : le tableau decrivant ce qu'on veut generer
+       $config = Pile::recuperer_et_supprimer_argument_balise(1, $p);
+       
+       // On ajoute le squelette a inclure dans les parametres
+       $p = Pile::creer_et_ajouter_argument_balise($p, 'fond', 'inclure/generer_saisies');
+       
+       // On ajoute l'environnement
+       $p = Pile::creer_et_ajouter_argument_balise($p, 'env');
+       
+       // On ajoute le tableau recupere
+       $p = Pile::creer_et_ajouter_argument_balise($p, 'saisies', $config);
+       
+       // On redirige vers la balise INCLURE
+       if(function_exists('balise_INCLURE'))
+               return balise_INCLURE($p);
+       else
+               return balise_INCLURE_dist($p); 
+
+}
+
+?>
diff --git a/www/plugins/saisies/balise/saisie.php b/www/plugins/saisies/balise/saisie.php
new file mode 100644 (file)
index 0000000..c891e55
--- /dev/null
@@ -0,0 +1,180 @@
+<?php
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+// pour ne pas interferer avec d'eventuelles futures fonctions du core
+// on met le tout dans un namespace ; les fonctions sont autonomes.
+
+class Pile {
+
+
+       // les arguments sont dans l'entree 0 du tableau param.
+       // param[0][0] vaut toujours '' (ou presque ?)
+       static function recuperer_argument_balise($pos, $p) {
+               if (!isset($p->param[0])) {
+                       return null;
+               }
+               if (!isset($p->param[0][$pos])) {
+                       return null;
+               }       
+               return $p->param[0][$pos];
+       }
+       
+       
+       
+       // les arguments sont dans l'entree 0 du tableau param.
+       // param[0][0] vaut toujours '' (ou presque ?)
+       static function supprimer_argument_balise($pos, $p) {
+               if (!isset($p->param[0])) {
+                       return null;
+               }
+               if (!isset($p->param[0][$pos])) {
+                       return null;
+               }       
+               if ($pos == 0) {
+                       array_shift($p->param[0]);
+               } else {
+                       $debut = array_slice($p->param[0], 0, $pos);
+                       $fin   = array_slice($p->param[0], $pos+1);
+                       $p->param[0] = array_merge($debut, $fin);
+               }               
+               return $p;
+       }       
+       
+       
+       
+       static function recuperer_et_supprimer_argument_balise($pos, &$p) {
+               $arg = Pile::recuperer_argument_balise($pos, $p);
+               $p   = Pile::supprimer_argument_balise($pos, $p);
+               return $arg;
+       }
+       
+       
+       
+       
+       // les arguments sont dans l'entree 0 du tableau param.
+       // param[0][0] vaut toujours '' (ou presque ?)
+       static function ajouter_argument_balise($element, $p) {
+               if (isset($p->param[0][0])) {
+                       $zero = array_shift($p->param[0]);
+                       array_unshift($p->param[0], $element);
+                       array_unshift($p->param[0], $zero);
+               } else {
+                       if (!is_array($p->param[0])) {
+                               $p->param[0] = array();
+                       }
+                       array_unshift($p->param[0], $element);
+               }
+               return $p;
+       }
+       
+       
+       
+       // creer_argument_balise(nom) = {nom}
+       // creer_argument_balise(nom, 'coucou') = {nom=coucou}
+       // creer_argument_balise(nom, $balise) = {nom=#BALISE}
+       static function creer_argument_balise($nom, $valeur = null) {
+               include_spip('public/interfaces');
+               $s = new Texte;
+               $s->texte = $nom;
+               $s->ligne=0;
+               
+               // si #BALISE cree avec Pile::creer_balise(), le mettre en array, comme les autres
+               if (is_object($valeur)) {
+                       $valeur = array($valeur);
+               }
+               
+               $res = null;
+               
+               // {nom}
+               if (is_null($valeur)) {
+                       $res = array($s);
+               } 
+               // {nom=coucou}
+               elseif (is_string($valeur)) {                   
+                       $s->texte .= "=$valeur";
+                       $res = array($s);
+               }
+               // {nom=#BALISE}
+               elseif (is_array($valeur)) {
+                       $s->texte .= "="; // /!\ sans cette toute petite chose, ça ne fait pas d'egalite :)
+                       $res = array_merge(array($s), $valeur);
+               }
+
+               return $res;
+       }
+       
+       
+       
+       static function creer_et_ajouter_argument_balise($p, $nom, $valeur = null) {
+               $new = Pile::creer_argument_balise($nom, $valeur); 
+               return Pile::ajouter_argument_balise($new, $p);
+       }
+
+
+
+       // creer une balise
+       static function creer_balise($nom, $opt) {
+               include_spip('public/interfaces');
+               $b = new Champ;
+               $b->nom_champ = strtoupper($nom);
+               $vars = get_class_vars('Champ'); // property_exists($b, $o); est en php 5
+               foreach ($opt as $o=>$val) {
+                       #if (property_exists($b,$o)) {
+                       if (array_key_exists($o, $vars)) {
+                               if ($o == 'param') {
+                                       array_unshift($val, '');
+                                       $b->$o = array($val);
+                               } else {
+                                       $b->$o = $val;
+                               }
+                       }
+               }
+               return $b;
+       }
+}
+
+
+
+/* 
+ * #saisie{type,nom} : champs obligatoires
+ * 
+ * collecte des arguments en fonctions du parametre "nom"
+ * ajoute des arguments
+ * appelle #INCLURE avec les arguments collectes en plus
+ * 
+ * il faudrait en faire une balise dynamique (?)
+ * pour avoir un code plus propre
+ * mais je n'ai pas reussi a trouver comment recuperer "valeur=#ENV{$nom}"
+ * 
+ */
+function balise_SAISIE_dist ($p) {
+
+       // on recupere les parametres sans les traduire en code d'execution php
+       $type_saisie = Pile::recuperer_et_supprimer_argument_balise(1, $p); // $type
+       $titre       = Pile::recuperer_et_supprimer_argument_balise(1, $p); // $titre
+
+       // creer #ENV*{$titre} (* pour les cas de tableau serialises par exemple, que l'on veut reutiliser)
+       $env_titre   = Pile::creer_balise('ENV', array('param' => array($titre), 'etoile' => '*')); // #ENV*{titre}
+
+       // on modifie $p pour ajouter des arguments
+       // {nom=$titre, valeur=#ENV{$titre}, erreurs, type_saisie=$type, fond=saisies/_base}
+       $p = Pile::creer_et_ajouter_argument_balise($p, 'nom', $titre);
+       $p = Pile::creer_et_ajouter_argument_balise($p, 'valeur', $env_titre);
+       $p = Pile::creer_et_ajouter_argument_balise($p, 'type_saisie', $type_saisie);
+       $p = Pile::creer_et_ajouter_argument_balise($p, 'erreurs');
+       $p = Pile::creer_et_ajouter_argument_balise($p, 'fond', 'saisies/_base');
+
+       // on appelle la balise #INCLURE
+       // avec les arguments ajoutes
+       if(function_exists('balise_INCLURE'))
+               return balise_INCLURE($p);
+       else
+               return balise_INCLURE_dist($p); 
+               
+}
+
+
+
+
+?>
diff --git a/www/plugins/saisies/balise/voir_saisie.php b/www/plugins/saisies/balise/voir_saisie.php
new file mode 100644 (file)
index 0000000..3498901
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+
+// Sécurité
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+/* 
+ * #VOIR_SAISIE{type,nom} : champs obligatoires
+ * 
+ * collecte des arguments en fonctions du parametre "nom"
+ * ajoute des arguments
+ * appelle #INCLURE avec les arguments collectes en plus
+ * 
+ */
+function balise_VOIR_SAISIE_dist ($p) {
+
+       // on recupere les parametres sans les traduire en code d'execution php
+       $type_saisie = Pile::recuperer_et_supprimer_argument_balise(1, $p);
+       $nom       = Pile::recuperer_et_supprimer_argument_balise(1, $p);
+
+       // creer #ENV*{$titre} (* pour les cas de tableau serialises par exemple, que l'on veut reutiliser)
+       $env_nom   = Pile::creer_balise('ENV', array('param' => array($nom), 'etoile' => '*')); // #ENV*{nom}
+
+       // on modifie $p pour ajouter des arguments
+       // {nom=$nom, valeur=#ENV{$nom}, type_saisie=$type, fond=saisies/_base}
+       $p = Pile::creer_et_ajouter_argument_balise($p, 'nom', $nom);
+       $p = Pile::creer_et_ajouter_argument_balise($p, 'valeur', $env_nom);
+       $p = Pile::creer_et_ajouter_argument_balise($p, 'type_saisie', $type_saisie);
+       $p = Pile::creer_et_ajouter_argument_balise($p, 'fond', 'saisies-vues/_base');
+
+       // on appelle la balise #INCLURE
+       // avec les arguments ajoutes
+       if(function_exists('balise_INCLURE'))
+               return balise_INCLURE($p);
+       else
+               return balise_INCLURE_dist($p); 
+               
+}
+
+?>
diff --git a/www/plugins/saisies/balise/voir_saisies.php b/www/plugins/saisies/balise/voir_saisies.php
new file mode 100644 (file)
index 0000000..609742d
--- /dev/null
@@ -0,0 +1,30 @@
+<?php 
+
+// Sécurité
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+function balise_VOIR_SAISIES_dist($p){
+
+       // On recupere les arguments : les tableaux decrivant ce qu'on veut generer + les reponses
+       $saisies = Pile::recuperer_et_supprimer_argument_balise(1, $p);
+       $valeurs = Pile::recuperer_et_supprimer_argument_balise(1, $p);
+       
+       // On ajoute le squelette a inclure dans les parametres
+       $p = Pile::creer_et_ajouter_argument_balise($p, 'fond', 'inclure/voir_saisies');
+       
+       // On ajoute l'environnement
+       $p = Pile::creer_et_ajouter_argument_balise($p, 'env');
+       
+       // On ajoute les tableaux recuperes
+       $p = Pile::creer_et_ajouter_argument_balise($p, 'saisies', $saisies);
+       $p = Pile::creer_et_ajouter_argument_balise($p, 'valeurs', $valeurs);
+       
+       // On redirige vers la balise INCLURE
+       if(function_exists('balise_INCLURE'))
+               return balise_INCLURE($p);
+       else
+               return balise_INCLURE_dist($p); 
+
+}
+
+?>
diff --git a/www/plugins/saisies/contenu/page-saisies_cvt.html b/www/plugins/saisies/contenu/page-saisies_cvt.html
new file mode 100644 (file)
index 0000000..0d7a487
--- /dev/null
@@ -0,0 +1,4 @@
+<h1>CVT automatique avec Saisies</h1>
+<p>Démonstration d'un formulaire CVT généré uniquement à partir d'une déclaration de Saisies</p>
+
+#FORMULAIRE_SAISIES_CVT
diff --git a/www/plugins/saisies/css/formulaires_constructeur.css b/www/plugins/saisies/css/formulaires_constructeur.css
new file mode 100644 (file)
index 0000000..94415fa
--- /dev/null
@@ -0,0 +1,181 @@
+
+.formulaire_spip .formulaire_spip{
+       border:none;
+       background:transparent;
+}
+#deplacable .ui-state-highlight { height: 5em; line-height: 1.2em; }
+#deplacable .ui-sortable {min-height:3em;}
+/*
+.formulaire_construire_formulaire{
+       padding:1em;
+}
+.formulaire_construire_formulaire ul li label{
+       display:block;
+       margin:0;
+       width:auto;
+       float:none;
+       clear:both;
+       line-height:1.7em;
+}
+.formulaire_construire_formulaire input.text, .formulaire_construire_formulaire input.password, .formulaire_construire_formulaire textarea, .formulaire_construire_formulaire select{
+       width:auto;
+}
+*/
+.formulaire_construire_formulaire li.actions_formulaire{
+       margin:0;
+       padding:1em;
+       text-align:center;
+       border:0;
+}
+.formulaire_construire_formulaire li.actions_formulaire img{
+       vertical-align:middle;
+}
+/*
+.formulaire_construire_formulaire li.configurable{
+       position:relative;
+       padding:1em;
+       margin:0.5em 0;
+       background:transparent;
+       border:1px dashed transparent;
+       border-radius:5px;
+       -moz-border-radius:5px;
+       -webkit-border-radius:5px;
+}
+.formulaire_construire_formulaire li.configurable.hover{
+       border-color:#999;
+}
+*/
+.formulaire_construire_formulaire li.en_configuration{
+       border:5px solid #999;
+       border-radius:5px;
+       margin:.5em;
+}
+.formulaire_construire_formulaire li.fieldset.configurable>fieldset>ul {margin-left:30px;}
+/*
+.formulaire_construire_formulaire li.fieldset.configurable{
+       padding:0;
+}
+.formulaire_construire_formulaire li.fieldset h3.legend{
+       margin:0;
+}
+.formulaire_construire_formulaire li.fieldset.configurable > fieldset{
+       border:1px solid #ddd;
+       padding:0;
+}
+.formulaire_construire_formulaire li.fieldset.configurable > fieldset > ul{
+       padding:0 1em;
+}
+*/
+.formulaire_construire_formulaire .formulaire_configurer{
+       border-top:3px dashed #999;
+       margin: 1em -8px 0 -138px;
+    padding: 1em .5em .5em .5em;
+    background:white;
+}
+.formulaire_construire_formulaire .fieldset > .formulaire_configurer{
+       margin: 1em -8px 0;
+}
+
+/*
+.formulaire_construire_formulaire li.fieldset.configurable > .formulaire_configurer{
+       margin:1em 0 0 0;
+}
+.formulaire_construire_formulaire .formulaire_configurer fieldset{
+       border:0;
+       padding:0;
+}
+.formulaire_construire_formulaire .formulaire_configurer-contenus li.editer{
+       border:0;
+       margin:0.5em 0;
+       padding:0.5em 1em;
+}
+.formulaire_construire_formulaire .formulaire_configurer li.formulaire_configurer-contenu{
+       background:white;
+       border-top:1px solid #ddd;
+       border-radius:0 0 5px 5px;
+       -moz-border-radius:0 0 5px 5px;
+       -webkit-border-radius:0 0 5px 5px;
+}
+*/
+.formulaire_construire_formulaire .formulaire_configurer .formulaire_configurer-onglets {
+       overflow:auto;
+}
+.formulaire_construire_formulaire .formulaire_configurer .formulaire_configurer-onglets li{
+       float:left;
+       width:auto;
+       clear:none;
+       padding:0;
+       background:#eee;
+       border:1px solid #ddd;
+       margin-right:1px;
+       -moz-border-radius:5px 5px 0 0;
+       -webkit-border-radius:5px 5px 0 0;
+       -o-border-radius:5px 5px 0 0;
+       border-radius:5px 5px 0 0;
+}
+.formulaire_construire_formulaire .formulaire_configurer .formulaire_configurer-onglets li.actif{
+       background:white;
+       border-bottom:1px solid white;
+}
+.formulaire_construire_formulaire .formulaire_configurer .formulaire_configurer-onglets li.erreur a{
+       color:#CC3300;
+}
+.formulaire_construire_formulaire .formulaire_configurer .formulaire_configurer-onglets li a{
+       display:block;
+       padding:.5em;
+}
+.formulaire_construire_formulaire .formulaire_configurer .boutons { margin-bottom: -20px; }
+
+.formulaire_configurer-contenus > .fieldset > fieldset:first-child {border-top:0;}
+
+.formulaire_construire_formulaire li.editer,
+.formulaire_construire_formulaire li.explication,
+.formulaire_construire_formulaire li.fieldset {padding-top:30px; position:relative;}
+.formulaire_construire_formulaire li.explication { padding-left:140px; background:transparent; }
+.formulaire_construire_formulaire li.explication > p { margin-left:-130px; }
+.formulaire_construire_formulaire .formulaire_configurer li.fieldset {padding-top:0px;}
+.formulaire_construire_formulaire .formulaire_configurer fieldset fieldset>ul>li.editer:first-child {padding-top:0px;}
+
+.formulaire_construire_formulaire li.hover {background-color:transparent;}
+
+.formulaire_construire_formulaire .actions{
+       position:absolute;
+       right:5px;
+       top:5px;
+}
+
+.formulaire_construire_formulaire .actions button{
+       cursor:pointer;
+       background:none;
+       border:none;
+       opacity:0.7;
+}
+.formulaire_construire_formulaire .actions button:hover{
+       opacity:1;
+}
+.formulaire_construire_formulaire .actions .move {
+       cursor:move;
+}
+
+
+
+.formulaire_construire_formulaire li.saisies_disponibles {
+       /*padding:1em;*/
+}
+
+.formulaire_construire_formulaire .ajouter_saisie{
+       width:45%;
+       margin:5px;
+       padding:0.5em 8px 0.5em 36px;
+       font-size:1em;
+       text-align:left;
+       color:black;
+       cursor:pointer;
+       background:white url() 8px center no-repeat;
+       border:1px solid #ccc;
+}
+
+.formulaire_construire_formulaire .ajouter_saisie img{
+       vertical-align:middle;
+}
+
diff --git a/www/plugins/saisies/extra-vues/pays.html b/www/plugins/saisies/extra-vues/pays.html
new file mode 100644 (file)
index 0000000..f45fbd1
--- /dev/null
@@ -0,0 +1,8 @@
+<B_pays>
+<div class="#ENV{champ_extra}">
+       <strong>#ENV{label_extra}</strong>
+       <BOUCLE_pays(GEO_PAYS){id_pays=#ENV*{valeur_extra}}>
+               #NOM
+       </BOUCLE_pays>
+</div>
+</B_pays>
\ No newline at end of file
diff --git a/www/plugins/saisies/formulaires/construire_formulaire.html b/www/plugins/saisies/formulaires/construire_formulaire.html
new file mode 100644 (file)
index 0000000..df8b08f
--- /dev/null
@@ -0,0 +1,219 @@
+[(#ENV{erreurs/positionner}|oui)
+       <a name="ajax_ancre" href="[(#ENV{erreurs/positionner})]"></a>
+]
+<div class="formulaire_spip formulaire_editer formulaire_#ENV{form}[ (#ENV{formulaire_modifie}|oui) modifie]">
+       [<p class="reponse_formulaire reponse_formulaire_ok">(#ENV*{message_ok})</p>]
+       <p id="message_attention" class="message_reinitialiser reponse_formulaire reponse_formulaire_ok">#ENV*{_message_attention}</p>
+       [<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV*{message_erreur})</p>]
+       
+       <BOUCLE_editable(CONDITION){si #ENV{editable}|oui}>
+       <form method='post' action='#ENV{action}' enctype='multipart/form-data'><div>
+               [(#REM) declarer les hidden qui declencheront le service du formulaire 
+               parametre : url d'action ]
+               #ACTION_FORMULAIRE{#ENV{action}}
+               <input style="display:none;" type="submit" class="submit" name="enregistrer" value="<:bouton_enregistrer:>" />
+
+               <ul id="deplacable">
+                       
+                       <li id="reinitialiser" class="actions_formulaire">
+                               <button type="submit" class="submit" name="reinitialiser" value="oui" onclick="return confirm('<:saisies:construire_reinitialiser_confirmer:>')">
+                                       <img src="#CHEMIN{images/formulaire-reinitialiser-24.png}" alt="" />
+                                       <:saisies:construire_reinitialiser:>
+                               </button>
+                       </li>
+
+                       <BOUCLE_contenu(DATA){source tableau, #ENV{_contenu}}>
+                       [(#VAL{saisie}|array_key_exists{#VALEUR}|oui)
+                               [(#VALEUR**|formidable_generer_saisie_configurable{#ENV**|unserialize})]
+                       ]
+                       </BOUCLE_contenu>
+                       <li class="aucun"><em class="attention"><:saisies:construire_aucun_champs:></em></li>
+                       <//B_contenu>
+                       
+                       <B_saisies_disponibles>
+                       <li class="editer haut saisies_disponibles" id="attrapable">
+                               <label><:saisies:construire_ajouter_champ:></label>
+                               <BOUCLE_saisies_disponibles(DATA){source tableau, #ENV{_saisies_disponibles}}{par cle}>
+                               <button type="submit" name="ajouter_saisie" value="#CLE" class="submit ajouter_saisie"[ title="(#DESCRIPTION)"] [style="background-image:url((#ICONE|sinon{#CHEMIN{images/formulaire-saisie-defaut.png}}))"]>
+                                       <span>#TITRE</span>
+                               </button>
+                               </BOUCLE_saisies_disponibles>
+                       </li>
+                       </B_saisies_disponibles>
+               </ul>
+
+               
+               [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ]
+               <!--extra-->
+               
+               <span class='image_loading'></span>
+       </div></form>
+
+       <style>
+               .message_reinitialiser,#reinitialiser {display: none}
+               .modifie .message_reinitialiser,.modifie #reinitialiser {display: block}
+       </style>
+       <script type="text/javascript">
+               jQuery(function(){
+                       jQuery('.formulaire_construire_formulaire li.configurable')
+                               .hover(
+                                       function(){
+                                               jQuery(this)
+                                                       .addClass('hover')
+                                                       .find('> .actions')
+                                                               .show()
+                                                       .end()
+                                                       .parents('li.configurable:not(.en_configuration)')
+                                                               .mouseout();
+                                       },
+                                       function(){
+                                               if (!jQuery(this).is('.en_configuration'))
+                                                       jQuery(this)
+                                                               .removeClass('hover')
+                                                               .find('> .actions')
+                                                                       .hide()
+                                                               .end()
+                                                               .parents('li.configurable').eq('0')
+                                                                       .mouseover();
+                                       }
+                               )
+                               .filter(':not(.en_configuration)')
+                                       .find('> .actions')
+                                               .hide()
+                                       .end()
+                               .end();
+                       
+                       // On lance la création des onglets
+                       formulaire_configurer_onglets();
+                       
+                       // Gérer la liste des vérifications
+                       jQuery('.liste_verifications')
+                               .each(function(){
+                                       var options = jQuery(this).siblings('.options_verifier').hide();
+                                       var select = jQuery(this).find('select');
+                                       
+                                       select
+                                               .change(function(){
+                                                       var montrer = jQuery(this).val() ? jQuery(this).val() : 'soigfeg';
+                                                       options.hide().filter('.'+montrer).show();
+                                               })
+                                               .change();
+                               });
+                       
+                       // On déplie toujours les fieldsets plés par défaut
+                       jQuery('li.fieldset.plie')
+                               .each(function(){
+                                       jQuery(this)
+                                               .removeClass('plie')
+                                               .find('> fieldset > ul').show();
+                               });
+                               
+                       [(#ENV{erreurs}|non|et{#ENV{_chemin_ui}})
+                               $.getScript("#CHEMIN{#ENV{_chemin_ui}core.js}", function(){
+                               $.getScript("#CHEMIN{#ENV{_chemin_ui}widget.js}", function(){
+                               $.getScript("#CHEMIN{#ENV{_chemin_ui}mouse.js}", function(){
+                                       $.getScript("#CHEMIN{#ENV{_chemin_ui}sortable.js}", function(){
+                                               if ($.fn.sortable) {
+                                                       jQuery( "#deplacable, #deplacable ul" ).sortable({
+                                                               revert: true,
+                                                               containment: '#deplacable',
+                                                               connectWith: "#deplacable, #deplacable ul",
+                                                               placeholder: "ui-state-highlight",
+                                                               handle: '>.actions .deplacer_saisie',
+                                                               start: function(event, ui) {
+                                                                       jQuery('.ui-state-highlight')
+                                                                               .css('height', ui.item.css('height'))
+                                                                               .css('height', '+=20px');
+                                                               },
+                                                               update: function(event, ui) {
+                                                                       id = ui.item.data('id');
+                                                                       ou = ui.item.next().data('id');
+                                                                       // avant le suivant
+                                                                       if (!ou) {
+                                                                               // sinon dans le parent
+                                                                               ou = ui.item.closest('.fieldset').data('id');
+                                                                               if (ou) {
+                                                                                       ou = '\[' + ou + '\]';
+                                                                               }
+                                                                       }
+                                                                       url = "#URL_ECRIRE";
+                                                                       $.get(url, {
+                                                                               session: '#ENV{_identifiant_session}',
+                                                                               action: 'deplacer_saisie',
+                                                                               saisie: id,
+                                                                               ou: ou
+                                                                       }, function() {
+                                                                               //jQuery('input.vide').submit();
+                                                                               jQuery('.formulaire_#ENV{form}').addClass('modifie').trigger('modifsaisies');
+                                                                       });
+                                                               }
+                                                       });
+                                               }
+                                       });
+                                                       /*
+                                       $.getScript("#CHEMIN{#ENV{_chemin_ui}jquery.ui.draggable.js}", function(){
+                                               if ($.fn.draggable) {
+                                                       jQuery( "#attrapable" ).draggable({
+                                                               connectToSortable: "#deplacable, #deplacable ul",
+                                                               helper: "clone"
+                                                       });
+                                               }
+                                       });
+                                                       */
+                               });});});
+                       ]
+               });
+               
+               function formulaire_configurer_onglets(){
+                       var formulaire_configurer = jQuery('.formulaire_configurer');
+                       var onglets = jQuery('<ul class="formulaire_configurer-onglets"></ul>');
+                       var contenus = formulaire_configurer.find('> ul > li.fieldset');
+                       
+                       // On ajoute le conteneur des onglets
+                       formulaire_configurer
+                               .prepend(onglets);
+                       
+                       // On parcourt les contenus pour générer les onglets
+                       contenus
+                               .each(function(i){
+                                       // On ajoute un identifiant et une classe
+                                       jQuery(this)
+                                               .attr('id', 'formulaire_configurer-contenu-'+i)
+                                               .addClass('formulaire_configurer-contenu');
+                                       // On récupère le titre (en le cachant au passage)
+                                       var titre = jQuery(this).find('h3').eq(0).hide().text();
+                                       // On crée un onglet
+                                       var onglet = jQuery('<li><a href="#formulaire_configurer-contenu-'+i+'">'+titre+'</a></li>');
+                                       onglet
+                                               .click(function(){
+                                                       contenus.hide();
+                                                       jQuery(
+                                                               jQuery(this)
+                                                                       .siblings()
+                                                                               .removeClass('actif')
+                                                                       .end()
+                                                                       .addClass('actif')
+                                                                       .find('a')
+                                                                               .attr('href')
+                                                       ).show();
+                                                       return false;
+                                               });
+                                       
+                                       // On active le premier onglet au démarrage
+                                       if (i == 0) onglet.addClass('actif');
+                                       
+                                       // S'il y a des erreurs dans cette partie du contenu, on met une classe "erreurs" à l'onglet aussi
+                                       if (jQuery(this).find('li.erreur').length > 0)
+                                               onglet.addClass('erreur');
+                                       
+                                       // On ajoute l'onglet
+                                       onglets
+                                               .append(onglet);
+                               })
+                               .hide()
+                               .eq(0)
+                                       .show();
+               }
+       </script>
+       </BOUCLE_editable>
+</div>
diff --git a/www/plugins/saisies/formulaires/construire_formulaire.php b/www/plugins/saisies/formulaires/construire_formulaire.php
new file mode 100644 (file)
index 0000000..675c159
--- /dev/null
@@ -0,0 +1,535 @@
+<?php
+
+// Sécurité
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+function formulaires_construire_formulaire_charger($identifiant, $formulaire_initial=array(), $options=array()){
+       include_spip('inc/saisies');
+       $contexte = array();
+       
+       // On ajoute un préfixe devant l'identifiant, pour être sûr
+       $identifiant = 'constructeur_formulaire_'.$identifiant;
+       $contexte['_identifiant_session'] = $identifiant;
+       
+       // On vérifie ce qui a été passé en paramètre 
+       if (!is_array($formulaire_initial)) $formulaire_initial = array();
+       
+       // On initialise la session si elle est vide
+       if (is_null($formulaire_actuel = session_get($identifiant))){
+               session_set($identifiant, $formulaire_initial);
+               $formulaire_actuel = $formulaire_initial;
+       }
+       
+       // Si le formulaire actuel est différent du formulaire initial on agite un drapeau pour le dire
+       if ($formulaire_actuel != $formulaire_initial){
+               $contexte['formulaire_modifie'] = true;
+       }
+       $contexte['_message_attention'] = _T('saisies:construire_attention_modifie');
+       
+       // On passe ça pour l'affichage
+       $contexte['_contenu'] = $formulaire_actuel;
+
+       // On passe ça pour la récup plus facile des champs
+       $contexte['_saisies_par_nom'] = saisies_lister_par_nom($formulaire_actuel);
+       // Pour déclarer les champs modifiables à CVT
+       foreach(array_keys($contexte['_saisies_par_nom']) as $nom){
+               $contexte["saisie_modifiee_$nom"] = array();
+       }
+       
+       // La liste des saisies
+       $saisies_disponibles = saisies_lister_disponibles();
+       $contexte['_saisies_disponibles'] = $saisies_disponibles;
+       
+       $contexte['fond_generer'] = 'formulaires/inc-generer_saisies_configurables';
+       
+       // On cherche jquery UI pour savoir si on pourra glisser-déplacer
+       // SPIP 3.1 - jquery_ui
+       if (find_in_path('javascript/ui/sortable.js') and find_in_path('javascript/ui/draggable.js')){
+               $contexte['_chemin_ui'] = 'javascript/ui/';
+       }
+       // SPIP 3 - jquery_ui
+       elseif (find_in_path('javascript/ui/jquery.ui.sortable.js') and find_in_path('javascript/ui/jquery.ui.draggable.js')){
+               $contexte['_chemin_ui'] = 'javascript/ui/jquery.ui.';
+       }
+       else{
+               $contexte['_chemin_ui'] = false;
+       }
+
+       return $contexte;
+}
+
+function formulaires_construire_formulaire_verifier($identifiant, $formulaire_initial=array(), $options=array()){
+       include_spip('inc/saisies');
+       $erreurs = array();
+       // l'une ou l'autre sera presente
+       $configurer_saisie = $enregistrer_saisie = '';
+
+       // Pas d'erreur si l'on ne demande rien
+       if (!($nom_ou_id = $configurer_saisie  = _request('configurer_saisie')
+       OR    $nom_ou_id = $enregistrer_saisie = _request('enregistrer_saisie'))) {
+               return $erreurs;
+       }
+
+       // On ajoute un préfixe devant l'identifiant
+       $identifiant = 'constructeur_formulaire_'.$identifiant;
+       // On récupère le formulaire à son état actuel
+       $formulaire_actuel = session_get($identifiant);
+
+       // On récupère les saisies actuelles, par identifiant ou par nom
+       if ($nom_ou_id[0] == '@') {
+               $saisies_actuelles = saisies_lister_par_identifiant($formulaire_actuel);
+               $nom = $saisies_actuelles[$nom_ou_id]['options']['nom'];
+       } else {
+               $saisies_actuelles = saisies_lister_par_nom($formulaire_actuel);
+               $nom = $nom_ou_id;
+       }
+       $noms_autorises = array_keys($saisies_actuelles);
+
+       // le nom (ou identifiant) doit exister
+       if (!in_array($nom_ou_id, $noms_autorises)) {
+               return $erreurs;
+       }
+       
+       // La liste des saisies
+       $saisies_disponibles = saisies_lister_disponibles();
+       
+       $saisie = $saisies_actuelles[$nom_ou_id];
+       $formulaire_config = $saisies_disponibles[$saisie['saisie']]['options'];
+       array_walk_recursive($formulaire_config, 'formidable_transformer_nom', "saisie_modifiee_${nom}[options][@valeur@]");
+       $formulaire_config = saisie_identifier(array('saisies'=>$formulaire_config));
+       $formulaire_config = $formulaire_config['saisies'];
+       
+       // Si la saisie possede un identifiant, on l'ajoute
+       // au formulaire de configuration pour ne pas le perdre en route
+       if (isset($saisie['identifiant']) and $saisie['identifiant']) {
+               $formulaire_config = saisies_inserer(
+                       $formulaire_config,
+                       array(
+                               'saisie' => 'hidden',
+                               'options' => array(
+                                       'nom' => "saisie_modifiee_${nom}[identifiant]",
+                                       'defaut' => $saisie['identifiant']
+                               ),
+                       )
+               );
+       }
+       
+       // S'il y a l'option adéquat, on ajoute le champ pour modifier le nom
+       if (isset($options['modifier_nom']) and $options['modifier_nom']
+         and $chemin_nom = saisies_chercher($formulaire_config, "saisie_modifiee_${nom}[options][description]", true))
+       {
+               $chemin_nom[] = 'saisies';
+               $chemin_nom[] = '0';
+
+               $formulaire_config = saisies_inserer(
+                       $formulaire_config,
+                       array(
+                               'saisie' => 'input',
+                               'options' => array(
+                                       'nom' => "saisie_modifiee_${nom}[options][nom]",
+                                       'label' => _T('saisies:option_nom_label'),
+                                       'explication' => _T('saisies:option_nom_explication'),
+                                       'obligatoire' => 'oui',
+                                       'size' => 50
+                               ),
+                               'verifier' => array(
+                                       'type' => 'regex',
+                                       'options' => array(
+                                               'modele' => '/^[\w]+$/'
+                                       )
+                               )
+                       ),
+                       $chemin_nom
+               );
+       }
+
+       // liste des options de vérification
+       $verif_options = array();
+
+       // S'il y a un groupe "validation" alors on va construire le formulaire des vérifications
+       if ($chemin_validation = saisies_chercher($formulaire_config, "saisie_modifiee_${nom}[options][validation]", true)){
+               include_spip('inc/verifier');
+               $liste_verifications = verifier_lister_disponibles();
+               $chemin_validation[] = 'saisies';
+               $chemin_validation[] = 1000000; // à la fin
+               
+               // On construit la saisie à insérer et les fieldset des options
+               $saisie_liste_verif = array(
+                       'saisie' => 'selection',
+                       'options' => array(
+                               'nom' => "saisie_modifiee_${nom}[verifier][type]",
+                               'label' => _T('saisies:construire_verifications_label'),
+                               'option_intro' => _T('saisies:construire_verifications_aucune'),
+                               'li_class' => 'liste_verifications',
+                               'datas' => array()
+                       )
+               );
+
+               foreach ($liste_verifications as $type_verif => $verif){
+                       $saisie_liste_verif['options']['datas'][$type_verif] = $verif['titre'];
+                       // Si le type de vérif a des options, on ajoute un fieldset
+                       if (isset($verif['options']) and $verif['options'] and is_array($verif['options'])){
+                               $groupe = array(
+                                       'saisie' => 'fieldset',
+                                       'options' => array(
+                                               'nom' => 'options',
+                                               'label' => $verif['titre'],
+                                               'li_class' => "$type_verif options_verifier"
+                                       ),
+                                       'saisies' => $verif['options']
+                               );
+                               array_walk_recursive($groupe, 'formidable_transformer_nom', "saisie_modifiee_${nom}[verifier][$type_verif][@valeur@]");
+                               $verif_options[$type_verif] = $groupe;
+                       }
+               }
+               $verif_options = array_merge(array($saisie_liste_verif), $verif_options);
+       }
+       
+       
+       if ($enregistrer_saisie){
+               // La saisie modifié
+               $saisie_modifiee = _request("saisie_modifiee_${nom}");
+               // On cherche les erreurs de la configuration
+               $vraies_erreurs = saisies_verifier($formulaire_config);
+               // Si on autorise à modifier le nom ET qu'il doit être unique : on vérifie
+               if (isset($options['modifier_nom']) and $options['modifier_nom']
+                 and isset($options['nom_unique']) and $options['nom_unique'])
+               {
+                       $nom_modifie = $saisie_modifiee['options']['nom'];
+                       if ($nom_modifie != $enregistrer_saisie and saisies_chercher($formulaire_actuel, $nom_modifie))
+                               $vraies_erreurs["saisie_modifiee_${nom}[options][nom]"] = _T('saisies:erreur_option_nom_unique');
+               }
+               // On regarde s'il a été demandé un type de vérif
+               if (isset($saisie_modifiee['verifier']['type'])
+                 and (($type_verif = $saisie_modifiee['verifier']['type']) != '')
+                 and $verif_options[$type_verif])
+               {
+                       // On ne vérifie que les options du type demandé
+                       $vraies_erreurs = array_merge($vraies_erreurs, saisies_verifier($verif_options[$type_verif]['saisies']));
+               }
+       }
+
+       // On insère chaque saisie des options de verification
+       if ($verif_options){
+               foreach ($verif_options as $saisie_verif){
+                       $formulaire_config = saisies_inserer($formulaire_config, $saisie_verif, $chemin_validation);
+               }
+       }
+       $erreurs['configurer_'.$nom] = $formulaire_config;
+       $erreurs['positionner'] = '#configurer_'.$nom;
+
+       if ($enregistrer_saisie) {
+               if ($vraies_erreurs) {
+                       $erreurs = array_merge($erreurs, $vraies_erreurs);
+               } else {
+                       $erreurs = array();
+               }
+       } else {
+               $erreurs['message_erreur'] = ''; // on ne veut pas du message_erreur automatique
+       }
+
+       return $erreurs;
+}
+
+function formulaires_construire_formulaire_traiter($identifiant, $formulaire_initial=array(), $options=array()){
+       include_spip('inc/saisies');
+       $retours = array();
+       $saisies_disponibles = saisies_lister_disponibles();
+       
+       // On ajoute un préfixe devant l'identifiant
+       $identifiant = 'constructeur_formulaire_'.$identifiant;
+       // On récupère le formulaire à son état actuel
+       $formulaire_actuel = session_get($identifiant);
+       
+       // Si on demande à ajouter une saisie
+       if ($ajouter_saisie = _request('ajouter_saisie')){
+               $nom = saisies_generer_nom($formulaire_actuel, $ajouter_saisie);
+               $saisie = array(
+                       'saisie' => $ajouter_saisie,
+                       'options' => array(
+                               'nom' => $nom
+                       )
+               );
+               // S'il y a des valeurs par défaut pour ce type de saisie, on les ajoute
+               if (($defaut = $saisies_disponibles[$ajouter_saisie]['defaut']) and is_array($defaut)){
+                       $defaut = _T_ou_typo($defaut, 'multi');
+
+                       //Compatibilite PHP<5.3.0 
+                       //source : http://www.php.net/manual/en/function.array-replace-recursive.php#92574
+                       if (!function_exists('array_replace_recursive'))
+                       {
+                               function array_replace_recursive($array, $array1)
+                               {
+                                       function recurse($array, $array1)
+                                       {
+                                               foreach ($array1 as $key => $value)
+                                               {
+                                                       // create new key in $array, if it is empty or not an array
+                                                       if (!isset($array[$key]) || (isset($array[$key]) && !is_array($array[$key])))
+                                                       {
+                                                               $array[$key] = array();
+                                                       }
+                                                       // overwrite the value in the base array
+                                                       if (is_array($value))
+                                                       {
+                                                               $value = recurse($array[$key], $value);
+                                                       }
+                                                       $array[$key] = $value;
+                                               }
+                                               return $array;
+                                       }
+                                       // handle the arguments, merge one by one
+                                       $args = func_get_args();
+                                       $array = $args[0];
+                                       if (!is_array($array))
+                                       {
+                                               return $array;
+                                       }
+                                       for ($i = 1; $i < count($args); $i++)
+                                       {
+                                               if (is_array($args[$i]))
+                                               {
+                                                       $array = recurse($array, $args[$i]);
+                                               }
+                                       }
+                               return $array;
+                               }
+                       }
+                       $saisie = array_replace_recursive($saisie, $defaut);
+               }
+               $formulaire_actuel = saisies_inserer($formulaire_actuel, $saisie);
+       }
+
+       // Si on demande à dupliquer une saisie
+       if ($dupliquer_saisie = _request('dupliquer_saisie')) {
+               $formulaire_actuel = saisies_dupliquer($formulaire_actuel, $dupliquer_saisie);  
+       }
+       
+       // Si on demande à supprimer une saisie
+       if ($supprimer_saisie = _request('supprimer_saisie')){
+               $formulaire_actuel = saisies_supprimer($formulaire_actuel, $supprimer_saisie);
+       }
+       
+       // Si on enregistre la conf d'une saisie
+       if ($nom = _request('enregistrer_saisie')){
+               // On récupère ce qui a été modifié
+               $saisie_modifiee = _request("saisie_modifiee_$nom");
+               
+               // On regarde s'il y a une position à modifier
+               if (isset($saisie_modifiee['position'])){
+                       $position = $saisie_modifiee['position'];
+                       unset($saisie_modifiee['position']);
+                       // On ne déplace que si ce n'est pas la même chose
+                       if ($position != $nom)
+                               $formulaire_actuel = saisies_deplacer($formulaire_actuel, $nom, $position);
+               }
+               
+               // On regarde s'il y a des options de vérification à modifier
+               if (isset($saisie_modifiee['verifier']['type'])
+                 and ($type_verif = $saisie_modifiee['verifier']['type']) != '')
+               {
+                       $saisie_modifiee['verifier'] = array(
+                               'type' => $type_verif,
+                               'options' => $saisie_modifiee['verifier'][$type_verif]
+                       );
+               }
+               else {
+                       unset($saisie_modifiee['verifier']);
+               }
+
+               // On récupère les options postées en enlevant les chaines vides
+               $saisie_modifiee['options'] = array_filter($saisie_modifiee['options'], 'saisie_option_contenu_vide');
+               if (isset($saisie_modifiee['verifier']['options']) and $saisie_modifiee['verifier']['options']) {
+                       $saisie_modifiee['verifier']['options'] = array_filter($saisie_modifiee['verifier']['options'], 'saisie_option_contenu_vide');
+               }
+               
+               // On désinfecte à la main
+               if (is_array($saisie_modifiee['options']))
+                       spip_desinfecte($saisie_modifiee['options']);
+               
+               // On modifie enfin
+               $formulaire_actuel = saisies_modifier($formulaire_actuel, $nom, $saisie_modifiee);
+       }
+       
+       // Si on demande à réinitialiser
+       if (_request('reinitialiser') == 'oui'){
+               $formulaire_actuel = $formulaire_initial;
+       }
+       
+       // On enregistre en session la nouvelle version du formulaire
+       session_set($identifiant, $formulaire_actuel);
+       
+       // Le formulaire reste éditable
+       $retours['editable'] = true;
+       
+       return $retours;
+}
+
+// À utiliser avec un array_walk_recursive()
+// Applique une transformation à la @valeur@ de tous les champs "nom" d'un formulaire, y compris loin dans l'arbo
+function formidable_transformer_nom(&$valeur, $cle, $transformation){
+       if ($cle == 'nom' and is_string($valeur)){
+               $valeur = str_replace('@valeur@', $valeur, $transformation);
+       }
+}
+
+// Préparer une saisie pour la transformer en truc configurable
+function formidable_generer_saisie_configurable($saisie, $env){
+       // On récupère le nom
+       $nom = $saisie['options']['nom'];
+       $identifiant = $saisie['identifiant'];
+       // On cherche si ya un formulaire de config
+       $formulaire_config = isset($env['erreurs']['configurer_'.$nom]) ? $env['erreurs']['configurer_'.$nom] : "";
+       // On ajoute une classe
+       if (!isset($saisie['options']['li_class'])) {
+               $saisie['options']['li_class'] = ''; // initialisation
+       }
+       $saisie['options']['li_class'] .= ' configurable';
+       // On ajoute l'option "tout_afficher"
+       $saisie['options']['tout_afficher'] = 'oui';
+       
+       // On ajoute les boutons d'actions, mais seulement s'il n'y a pas de configuration de lancée
+       if (!$env['erreurs']) {
+               $saisie = saisies_inserer_html(
+                       $saisie,
+                       recuperer_fond(
+                               'formulaires/inc-construire_formulaire-actions',
+                               array(
+                                       'nom' => $nom,
+                                       'identifiant' => $identifiant, 
+                                       'formulaire_config' => $formulaire_config,
+                                       'deplacable' => $env['_chemin_ui']
+                               )
+                       ),
+                       'debut'
+               );
+       }
+       
+       // On ajoute une ancre pour s'y déplacer
+       $saisie = saisies_inserer_html(
+               $saisie,
+               "\n<a id=\"configurer_$nom\"></a>\n",
+               'debut'
+       );
+       
+       // Si ya un form de config on l'ajoute à la fin
+       if (is_array($formulaire_config)){
+               // On double l'environnement
+               $env2 = $env;
+               // On ajoute une classe
+               $saisie['options']['li_class'] .= ' en_configuration';
+               
+               // Si possible on met en readonly
+               $saisie['options']['readonly'] = 'oui';
+               
+               // On vire les sous-saisies s'il y en a
+               if (isset($saisie['saisies']) and $saisie['saisies'] and is_array($saisie['saisies'])){
+                       $nb_champs_masques = count(saisies_lister_champs($saisie['saisies']));
+                       $saisie['saisies'] = array(
+                               array(
+                                       'saisie' => 'explication',
+                                       'options' => array(
+                                               'nom' => 'truc',
+                                               'texte' => _T('saisies:construire_info_nb_champs_masques', array('nb'=>$nb_champs_masques)),
+                                       )
+                               )
+                       );
+               }
+               
+               // On va ajouter le champ pour la position
+               if (!($chemin_description = saisies_chercher($formulaire_config, "saisie_modifiee_${nom}[options][description]", true))){
+                       $chemin_description = array(0);
+                       $formulaire_config = saisies_inserer(
+                               $formulaire_config,
+                               array(
+                                       'saisie' => 'fieldset',
+                                       'options' => array(
+                                               'nom' => "saisie_modifiee_${nom}[options][description]",
+                                               'label' => _T('saisies:option_groupe_description')
+                                       ),
+                                       'saisies' => array()
+                               ),
+                               0
+                       );
+               }
+               $chemin_description[] = 'saisies';
+               $chemin_description[] = '0'; // tout au début
+               $formulaire_config = saisies_inserer(
+                       $formulaire_config,
+                       array(
+                               'saisie' => 'position_construire_formulaire',
+                               'options' => array(
+                                       'nom' => "saisie_modifiee_${nom}[position]",
+                                       'label' => _T('saisies:construire_position_label'),
+                                       'explication' => _T('saisies:construire_position_explication'),
+                                       'formulaire' => $env['_contenu'],
+                                       'saisie_a_positionner' => $nom
+                               )
+                       ),
+                       $chemin_description
+               );
+               
+               $env2['saisies'] = $formulaire_config;
+               
+               // Un test pour savoir si on prend le _request ou bien
+               $erreurs_test = $env['erreurs'];
+               unset($erreurs_test['configurer_'.$nom]);
+               unset($erreurs_test['positionner']);
+               unset($erreurs_test['message_erreur']);
+
+               if ($erreurs_test){
+                       // Là aussi on désinfecte à la main
+                       if (isset($env2["saisie_modifiee_$nom"]['options']) and is_array($env2["saisie_modifiee_$nom"]['options'])) {
+                               spip_desinfecte($env2["saisie_modifiee_$nom"]['options']);
+                       }
+               }
+               else{
+                       $env2["saisie_modifiee_$nom"] = $env2['_saisies_par_nom'][$nom];
+                       // il n'y a pas toujours de verification...
+                       if (isset($env2["saisie_modifiee_$nom"]['verifier'])) {
+                               $env2["saisie_modifiee_$nom"]['verifier'][ $env2["saisie_modifiee_$nom"]['verifier']['type'] ]
+                                       = $env2["saisie_modifiee_$nom"]['verifier']['options'];
+                       }
+               }
+               
+               $env2['fond_generer'] = 'inclure/generer_saisies';
+               $saisie = saisies_inserer_html(
+                       $saisie,
+                       '<div class="formulaire_configurer"><ul class="formulaire_configurer-contenus">'
+                       .recuperer_fond(
+                               'inclure/generer_saisies',
+                               $env2
+                       )
+                       .'<li class="boutons">
+                               <input type="hidden" name="enregistrer_saisie" value="'.$nom.'" />
+                               <button type="submit" class="submit link" name="enregistrer_saisie" value="">'._T('bouton_annuler').'</button>
+                               <input type="submit" class="submit" name="enregistrer" value="'._T('bouton_valider').'" />
+                       </li>'
+                       .'</ul></div>',
+                       'fin'
+               );
+       }
+       // On génère le HTML de la saisie
+       $html = saisies_generer_html($saisie, $env);
+       return $html;
+}
+
+/**
+ * Callback d'array_filter()
+ * Permet de retourner tout ce qui n'est pas un contenu vide.
+ * La valeur '0' est par contre retournée.
+ *
+ * @param $var La variable a tester
+ * @return bool L'accepte-t-on ?
+**/
+function saisie_option_contenu_vide($var) {
+       if (!$var) {
+               if (is_string($var) AND strlen($var)) {
+                       return true;
+               }
+               return false;
+       }
+       return true;
+}
+?>
diff --git a/www/plugins/saisies/formulaires/inc-construire_formulaire-actions.html b/www/plugins/saisies/formulaires/inc-construire_formulaire-actions.html
new file mode 100644 (file)
index 0000000..faaedc4
--- /dev/null
@@ -0,0 +1,27 @@
+<div class="actions">
+       #SET{nom,#ENV{identifiant,#ENV{nom}}}
+       [(#ENV{formulaire_config}|is_array|non)
+       [(#ENV{deplacable}|oui)
+       <span class="move deplacer_saisie" title="<:saisies:construire_action_deplacer:>">
+               <img src="[(#CHEMIN{images/formulaire-deplacer-16.png})]" alt="<:saisies:construire_action_deplacer:>"/>
+       </span>
+       ]
+       <button type="submit" class="submit configurer_saisie" name="configurer_saisie" value="#GET{nom}" title="<:saisies:construire_action_configurer:>">
+               <img src="[(#CHEMIN{images/formulaire-configurer-16.png})]" alt="<:saisies:construire_action_configurer:>"/>
+       </button>
+       <button type="submit" class="submit dupliquer_saisie" name="dupliquer_saisie" value="#GET{nom}" title="<:saisies:construire_action_dupliquer:>">
+               <img src="[(#CHEMIN{images/formulaire-dupliquer-16.png})]" alt="<:saisies:construire_action_dupliquer:>"/>
+       </button>
+       <button type="submit" class="submit supprimer_saisie" name="supprimer_saisie" value="#GET{nom}" title="<:saisies:construire_action_supprimer:>" onclick="javascript:return confirm('<:saisies:construire_confirmer_supprimer_champ:>');">
+               <img src="[(#CHEMIN{images/formulaire-supprimer-16.png})]" alt="<:saisies:construire_action_supprimer:>"/>
+       </button>
+       ]
+       [(#ENV{formulaire_config}|is_array|oui)
+       <button type="submit" class="submit enregistrer_saisie" name="enregistrer_saisie" value="#GET{nom}" title="<:bouton_enregistrer:>">
+               <img src="[(#CHEMIN{images/formulaire-enregistrer-16.png})]" alt="<:bouton_enregistrer:>"/>
+       </button>
+       <button type="submit" class="submit annuler" name="enregistrer_saisie" value="" title="<:saisies:construire_action_annuler:>">
+               <img src="[(#CHEMIN{images/formulaire-annuler-16.png})]" alt="<:saisies:construire_action_annuler:>"/>
+       </button>
+       ]
+</div>
diff --git a/www/plugins/saisies/formulaires/inc-generer_saisies_configurables.html b/www/plugins/saisies/formulaires/inc-generer_saisies_configurables.html
new file mode 100644 (file)
index 0000000..fbb0c00
--- /dev/null
@@ -0,0 +1,9 @@
+[(#REM) 
+  Exemple d'appel :
+    #INCLURE{fond=formulaires/inc-generer_saisies_configurables, env, fond_generer=formulaires/inc-generer_saisies_configurables, saisies=#ENV{tableau}}
+]
+<BOUCLE_contenu(POUR){tableau #ENV{saisies}}>
+[(#VAL{saisie}|array_key_exists{#VALEUR}|oui)
+       [(#VALEUR**|formidable_generer_saisie_configurable{#ENV{_env}|sinon{#ENV**|unserialize}})]
+]
+</BOUCLE_contenu>
diff --git a/www/plugins/saisies/formulaires/inc-saisies-cvt.html b/www/plugins/saisies/formulaires/inc-saisies-cvt.html
new file mode 100644 (file)
index 0000000..b87cb54
--- /dev/null
@@ -0,0 +1,24 @@
+<div class="formulaire_spip formulaire_#ENV{form}">\r
+       [<p class="reponse_formulaire reponse_formulaire_ok">(#ENV**{message_ok})</p>]\r
+       [<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV**{message_erreur})</p>]\r
+       \r
+       [(#ENV{editable}|oui)\r
+       <form method="post" action="#ENV{action}" enctype="multipart/form-data"><div>\r
+               [(#REM) declarer les hidden qui declencheront le service du formulaire \r
+               parametre : url d'action ]\r
+               #ACTION_FORMULAIRE{#ENV{action}}\r
+               \r
+               <ul>\r
+                       #GENERER_SAISIES{#ENV{_saisies}}\r
+               </ul>\r
+               \r
+               [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ]\r
+               <!--extra-->\r
+               \r
+               <p class="boutons">\r
+                       <span class="image_loading"></span>\r
+                       <input type="submit" class="submit" value="[(#ENV{saisies_texte_submit}|sinon{<:bouton_enregistrer:>})]" />\r
+               </p>\r
+       </div></form>\r
+       ]\r
+</div>\r
diff --git a/www/plugins/saisies/formulaires/saisies_cvt.html b/www/plugins/saisies/formulaires/saisies_cvt.html
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/www/plugins/saisies/formulaires/saisies_cvt.php b/www/plugins/saisies/formulaires/saisies_cvt.php
new file mode 100644 (file)
index 0000000..582fa56
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+
+// Sécurité
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+function formulaires_saisies_cvt_saisies_dist(){
+       return array(
+               array(
+                       'saisie' => 'input',
+                       'options' => array(
+                               'nom' => 'nom',
+                               'label' => 'Nom'
+                       )
+               ),
+               array(
+                       'saisie' => 'input',
+                       'options' => array(
+                               'nom' => 'email',
+                               'obligatoire' => 'oui',
+                               'label' => 'E-mail'
+                       ),
+                       'verifier' => array(
+                               'type' => 'email'
+                       )
+               ),
+               array(
+                       'saisie' => 'textarea',
+                       'options' => array(
+                               'nom' => 'message',
+                               'obligatoire' => 'oui',
+                               'label' => 'Un message'
+                       ),
+                       'verifier' => array(
+                               'type' => 'taille',
+                               'options' => array('min' => 10)
+                       )
+               )
+       );
+}
+
+?>
diff --git a/www/plugins/saisies/images/formulaire-annuler-16.png b/www/plugins/saisies/images/formulaire-annuler-16.png
new file mode 100644 (file)
index 0000000..1af591c
Binary files /dev/null and b/www/plugins/saisies/images/formulaire-annuler-16.png differ
diff --git a/www/plugins/saisies/images/formulaire-configurer-16.png b/www/plugins/saisies/images/formulaire-configurer-16.png
new file mode 100644 (file)
index 0000000..36a909b
Binary files /dev/null and b/www/plugins/saisies/images/formulaire-configurer-16.png differ
diff --git a/www/plugins/saisies/images/formulaire-deplacer-16.png b/www/plugins/saisies/images/formulaire-deplacer-16.png
new file mode 100644 (file)
index 0000000..6e13dd3
Binary files /dev/null and b/www/plugins/saisies/images/formulaire-deplacer-16.png differ
diff --git a/www/plugins/saisies/images/formulaire-dupliquer-16.png b/www/plugins/saisies/images/formulaire-dupliquer-16.png
new file mode 100644 (file)
index 0000000..f9f4e9b
Binary files /dev/null and b/www/plugins/saisies/images/formulaire-dupliquer-16.png differ
diff --git a/www/plugins/saisies/images/formulaire-enregistrer-16.png b/www/plugins/saisies/images/formulaire-enregistrer-16.png
new file mode 100644 (file)
index 0000000..06b2491
Binary files /dev/null and b/www/plugins/saisies/images/formulaire-enregistrer-16.png differ
diff --git a/www/plugins/saisies/images/formulaire-reinitialiser-24.png b/www/plugins/saisies/images/formulaire-reinitialiser-24.png
new file mode 100644 (file)
index 0000000..5dce298
Binary files /dev/null and b/www/plugins/saisies/images/formulaire-reinitialiser-24.png differ
diff --git a/www/plugins/saisies/images/formulaire-saisie-defaut.png b/www/plugins/saisies/images/formulaire-saisie-defaut.png
new file mode 100644 (file)
index 0000000..6a0e70a
Binary files /dev/null and b/www/plugins/saisies/images/formulaire-saisie-defaut.png differ
diff --git a/www/plugins/saisies/images/formulaire-supprimer-16.png b/www/plugins/saisies/images/formulaire-supprimer-16.png
new file mode 100644 (file)
index 0000000..6f4a6a6
Binary files /dev/null and b/www/plugins/saisies/images/formulaire-supprimer-16.png differ
diff --git a/www/plugins/saisies/images/logo_saisie_48.png b/www/plugins/saisies/images/logo_saisie_48.png
new file mode 100644 (file)
index 0000000..e1e6f37
Binary files /dev/null and b/www/plugins/saisies/images/logo_saisie_48.png differ
diff --git a/www/plugins/saisies/images/saisies_auteurs.png b/www/plugins/saisies/images/saisies_auteurs.png
new file mode 100644 (file)
index 0000000..6fdf70d
Binary files /dev/null and b/www/plugins/saisies/images/saisies_auteurs.png differ
diff --git a/www/plugins/saisies/images/saisies_case.png b/www/plugins/saisies/images/saisies_case.png
new file mode 100644 (file)
index 0000000..71565b0
Binary files /dev/null and b/www/plugins/saisies/images/saisies_case.png differ
diff --git a/www/plugins/saisies/images/saisies_checkbox.png b/www/plugins/saisies/images/saisies_checkbox.png
new file mode 100644 (file)
index 0000000..8f0a9bf
Binary files /dev/null and b/www/plugins/saisies/images/saisies_checkbox.png differ
diff --git a/www/plugins/saisies/images/saisies_date.png b/www/plugins/saisies/images/saisies_date.png
new file mode 100644 (file)
index 0000000..96ef9a4
Binary files /dev/null and b/www/plugins/saisies/images/saisies_date.png differ
diff --git a/www/plugins/saisies/images/saisies_explication.png b/www/plugins/saisies/images/saisies_explication.png
new file mode 100644 (file)
index 0000000..be5c134
Binary files /dev/null and b/www/plugins/saisies/images/saisies_explication.png differ
diff --git a/www/plugins/saisies/images/saisies_fieldset.png b/www/plugins/saisies/images/saisies_fieldset.png
new file mode 100644 (file)
index 0000000..f9efaca
Binary files /dev/null and b/www/plugins/saisies/images/saisies_fieldset.png differ
diff --git a/www/plugins/saisies/images/saisies_hidden.png b/www/plugins/saisies/images/saisies_hidden.png
new file mode 100644 (file)
index 0000000..cb552bf
Binary files /dev/null and b/www/plugins/saisies/images/saisies_hidden.png differ
diff --git a/www/plugins/saisies/images/saisies_input.png b/www/plugins/saisies/images/saisies_input.png
new file mode 100644 (file)
index 0000000..ccaa722
Binary files /dev/null and b/www/plugins/saisies/images/saisies_input.png differ
diff --git a/www/plugins/saisies/images/saisies_oui_non.png b/www/plugins/saisies/images/saisies_oui_non.png
new file mode 100644 (file)
index 0000000..a32b607
Binary files /dev/null and b/www/plugins/saisies/images/saisies_oui_non.png differ
diff --git a/www/plugins/saisies/images/saisies_radio.png b/www/plugins/saisies/images/saisies_radio.png
new file mode 100644 (file)
index 0000000..c85d949
Binary files /dev/null and b/www/plugins/saisies/images/saisies_radio.png differ
diff --git a/www/plugins/saisies/images/saisies_selecteur_article.png b/www/plugins/saisies/images/saisies_selecteur_article.png
new file mode 100644 (file)
index 0000000..24eece2
Binary files /dev/null and b/www/plugins/saisies/images/saisies_selecteur_article.png differ
diff --git a/www/plugins/saisies/images/saisies_selecteur_rubrique.png b/www/plugins/saisies/images/saisies_selecteur_rubrique.png
new file mode 100644 (file)
index 0000000..026a114
Binary files /dev/null and b/www/plugins/saisies/images/saisies_selecteur_rubrique.png differ
diff --git a/www/plugins/saisies/images/saisies_selecteur_rubrique_article.png b/www/plugins/saisies/images/saisies_selecteur_rubrique_article.png
new file mode 100644 (file)
index 0000000..77acfe3
Binary files /dev/null and b/www/plugins/saisies/images/saisies_selecteur_rubrique_article.png differ
diff --git a/www/plugins/saisies/images/saisies_selection.png b/www/plugins/saisies/images/saisies_selection.png
new file mode 100644 (file)
index 0000000..dab25d3
Binary files /dev/null and b/www/plugins/saisies/images/saisies_selection.png differ
diff --git a/www/plugins/saisies/images/saisies_selection_multiple.png b/www/plugins/saisies/images/saisies_selection_multiple.png
new file mode 100644 (file)
index 0000000..d682108
Binary files /dev/null and b/www/plugins/saisies/images/saisies_selection_multiple.png differ
diff --git a/www/plugins/saisies/images/saisies_textarea.png b/www/plugins/saisies/images/saisies_textarea.png
new file mode 100644 (file)
index 0000000..84277b8
Binary files /dev/null and b/www/plugins/saisies/images/saisies_textarea.png differ
diff --git a/www/plugins/saisies/inc/saisies.php b/www/plugins/saisies/inc/saisies.php
new file mode 100644 (file)
index 0000000..4a343f5
--- /dev/null
@@ -0,0 +1,494 @@
+<?php\r
+\r
+/**\r
+ * Gestion de l'affichage des saisies\r
+ *\r
+ * @return SPIP\Saisies\Saisies\r
+**/\r
+\r
+// Sécurité\r
+if (!defined('_ECRIRE_INC_VERSION')) return;\r
+\r
+/*\r
+ * Une librairie pour manipuler ou obtenir des infos sur un tableau de saisies\r
+ *\r
+ * saisies_lister_par_nom()\r
+ * saisies_lister_champs()\r
+ * saisies_lister_valeurs_defaut()\r
+ * saisies_charger_champs()\r
+ * saisies_chercher()\r
+ * saisies_supprimer()\r
+ * saisies_inserer()\r
+ * saisies_deplacer()\r
+ * saisies_modifier()\r
+ * saisies_verifier()\r
+ * saisies_comparer()\r
+ * saisies_generer_html()\r
+ * saisies_generer_vue()\r
+ * saisies_generer_nom()\r
+ * saisies_inserer_html()\r
+ * saisies_lister_disponibles()\r
+ * saisies_autonomes()\r
+ */\r
+\r
+// Différentes méthodes pour trouver les saisies\r
+include_spip('inc/saisies_lister');\r
+\r
+// Différentes méthodes pour manipuler une liste de saisies\r
+include_spip('inc/saisies_manipuler');\r
+\r
+// Les outils pour afficher les saisies et leur vue\r
+include_spip('inc/saisies_afficher');\r
+\r
+/*\r
+ * Cherche la description des saisies d'un formulaire CVT dont on donne le nom\r
+ *\r
+ * @param string $form Nom du formulaire dont on cherche les saisies\r
+ * @return array Retourne les saisies du formulaire sinon false\r
+ */\r
+function saisies_chercher_formulaire($form, $args){\r
+       if ($fonction_saisies = charger_fonction('saisies', 'formulaires/'.$form, true)\r
+               and $saisies = call_user_func_array($fonction_saisies, $args)\r
+               and is_array($saisies)\r
+               // On passe les saisies dans un pipeline normé comme pour CVT\r
+               and $saisies = pipeline(\r
+                       'formulaire_saisies',\r
+                       array(\r
+                               'args' => array('form' => $form, 'args' => $args),\r
+                               'data' => $saisies\r
+                       )\r
+               )\r
+               // Si c'est toujours un tableau après le pipeline\r
+               and is_array($saisies)\r
+       ){\r
+               return $saisies;\r
+       }\r
+       else{\r
+               return false;\r
+       }\r
+}\r
+\r
+/*\r
+ * Cherche une saisie par son id, son nom ou son chemin et renvoie soit la saisie, soit son chemin\r
+ *\r
+ * @param array $saisies Un tableau décrivant les saisies\r
+ * @param unknown_type $id_ou_nom_ou_chemin L'identifiant ou le nom de la saisie à chercher ou le chemin sous forme d'une liste de clés\r
+ * @param bool $retourner_chemin Indique si on retourne non pas la saisie mais son chemin\r
+ * @return array Retourne soit la saisie, soit son chemin, soit null\r
+ */\r
+function saisies_chercher($saisies, $id_ou_nom_ou_chemin, $retourner_chemin=false){\r
+\r
+       if (is_array($saisies) and $id_ou_nom_ou_chemin){\r
+               if (is_string($id_ou_nom_ou_chemin)){\r
+                       $nom = $id_ou_nom_ou_chemin;\r
+                       // identifiant ? premier caractere @\r
+                       $id = ($nom[0] == '@');\r
+\r
+                       foreach($saisies as $cle => $saisie){\r
+                               $chemin = array($cle);\r
+                               // notre saisie est la bonne ?\r
+                               if ($nom == ($id ? $saisie['identifiant'] : $saisie['options']['nom'])) {\r
+                                       return $retourner_chemin ? $chemin : $saisie;\r
+                               // sinon a telle des enfants ? et si c'est le cas, cherchons dedans\r
+                               } elseif (isset($saisie['saisies']) and is_array($saisie['saisies']) and $saisie['saisies']\r
+                                       and ($retour = saisies_chercher($saisie['saisies'], $nom, $retourner_chemin))) {\r
+                                               return $retourner_chemin ? array_merge($chemin, array('saisies'), $retour) : $retour;\r
+                               }\r
+\r
+                       }\r
+               }\r
+               elseif (is_array($id_ou_nom_ou_chemin)){\r
+                       $chemin = $id_ou_nom_ou_chemin;\r
+                       $saisie = $saisies;\r
+                       // On vérifie l'existence quand même\r
+                       foreach ($chemin as $cle){\r
+                               if (isset($saisie[$cle])) $saisie = $saisie[$cle];\r
+                               else return null;\r
+                       }\r
+                       // Si c'est une vraie saisie\r
+                       if ($saisie['saisie'] and $saisie['options']['nom'])\r
+                               return $retourner_chemin ? $chemin : $saisie;\r
+               }\r
+       }\r
+       \r
+       return null;\r
+}\r
+\r
+/**\r
+ * Génère un nom unique pour un champ d'un formulaire donné\r
+ *\r
+ * @param array $formulaire\r
+ *     Le formulaire à analyser \r
+ * @param string $type_saisie\r
+ *     Le type de champ dont on veut un identifiant \r
+ * @return string\r
+ *     Un nom unique par rapport aux autres champs du formulaire\r
+ */\r
+function saisies_generer_nom($formulaire, $type_saisie){\r
+       $champs = saisies_lister_champs($formulaire);\r
+       \r
+       // Tant que type_numero existe, on incrémente le compteur\r
+       $compteur = 1;\r
+       while (array_search($type_saisie.'_'.$compteur, $champs) !== false)\r
+               $compteur++;\r
+       \r
+       // On a alors un compteur unique pour ce formulaire\r
+       return $type_saisie.'_'.$compteur;\r
+}\r
+\r
+/*\r
+ * Crée un identifiant Unique\r
+ * pour toutes les saisies donnees qui n'en ont pas \r
+ *\r
+ * @param Array $saisies Tableau de saisies\r
+ * @param Bool $regenerer_id Régénère un nouvel identifiant pour toutes les saisies ?\r
+ * @return Array Tableau de saisies complété des identifiants\r
+ */\r
+function saisies_identifier($saisies, $regenerer = false) {\r
+       if (!is_array($saisies)) {\r
+               return array();\r
+       }\r
+       foreach ($saisies as $k => $saisie) {\r
+               $saisies[$k] = saisie_identifier($saisie, $regenerer);\r
+       }\r
+       return $saisies;\r
+}\r
+\r
+/**\r
+ * Crée un identifiant Unique\r
+ * pour la saisie donnee si elle n'en a pas\r
+ * (et pour ses sous saisies éventuels)\r
+ *\r
+ * @param Array $saisie Tableau d'une saisie\r
+ * @param Bool $regenerer_id Régénère un nouvel identifiant pour la saisie ?\r
+ * @return Array Tableau de la saisie complété de l'identifiant\r
+**/\r
+function saisie_identifier($saisie, $regenerer = false) {\r
+       if (!isset($saisie['identifiant']) OR !$saisie['identifiant']) {\r
+               $saisie['identifiant'] = uniqid('@');\r
+       } elseif ($regenerer) {\r
+               $saisie['identifiant'] = uniqid('@');\r
+       }\r
+       if (isset($saisie['saisies']) AND is_array($saisie['saisies'])) {\r
+               $saisie['saisies'] = saisies_identifier($saisie['saisies'], $regenerer);\r
+       }\r
+       return $saisie;\r
+}\r
+\r
+/*\r
+ * Vérifier tout un formulaire tel que décrit avec les Saisies\r
+ *\r
+ * @param array $formulaire Le contenu d'un formulaire décrit dans un tableau de Saisies\r
+ * @param bool $saisies_masquees_nulles Si TRUE, les saisies masquées selon afficher_si ne seront pas verifiées, leur valeur étant forcée a NULL. Cette valeur NULL est transmise à traiter (via set_request).\r
+ * @return array Retourne un tableau d'erreurs\r
+ */\r
+function saisies_verifier($formulaire, $saisies_masquees_nulles=true){\r
+       include_spip('inc/verifier');\r
+       $erreurs = array();\r
+       $verif_fonction = charger_fonction('verifier','inc',true);\r
+\r
+       if ($saisies_masquees_nulles)\r
+               $formulaire = saisies_verifier_afficher_si($formulaire);\r
+       \r
+       $saisies = saisies_lister_par_nom($formulaire);\r
+       foreach ($saisies as $saisie){\r
+               $obligatoire = isset($saisie['options']['obligatoire']) ? $saisie['options']['obligatoire'] : '';\r
+               $champ = $saisie['options']['nom'];\r
+               $file = ($saisie['saisie'] == 'input' and isset($saisie['options']['type']) and $saisie['options']['type'] == 'file');\r
+               $verifier = isset($saisie['verifier']) ? $saisie['verifier'] : false;\r
+\r
+               // Si le nom du champ est un tableau indexé, il faut parser !\r
+               if (preg_match('/([\w]+)((\[[\w]+\])+)/', $champ, $separe)){\r
+                       $valeur = _request($separe[1]);\r
+                       preg_match_all('/\[([\w]+)\]/', $separe[2], $index);\r
+                       // On va chercher au fond du tableau\r
+                       foreach($index[1] as $cle){\r
+                               $valeur = isset($valeur[$cle]) ? $valeur[$cle] : null;\r
+                       }\r
+               }\r
+               // Sinon la valeur est juste celle du nom\r
+               else\r
+                       $valeur = _request($champ);\r
+               \r
+               // Pour la saisie "destinataires" il faut filtrer si jamais on a mis un premier choix vide\r
+               if ($saisie['saisie'] == 'destinataires') {\r
+                       $valeur = array_filter($valeur);\r
+               }\r
+               \r
+               // On regarde d'abord si le champ est obligatoire\r
+               if ($obligatoire\r
+                       and $obligatoire != 'non'\r
+                       and (\r
+                               ($file and !$_FILES[$champ]['name'])\r
+                               or (!$file and (\r
+                                       is_null($valeur)\r
+                                       or (is_string($valeur) and trim($valeur) == '')\r
+                                       or (is_array($valeur) and count($valeur) == 0)\r
+                               ))\r
+                       )\r
+               ) {\r
+                       $erreurs[$champ] =\r
+                               (isset($saisie['options']['erreur_obligatoire']) and $saisie['options']['erreur_obligatoire'])\r
+                               ? $saisie['options']['erreur_obligatoire']\r
+                               : _T('info_obligatoire');\r
+               }\r
+\r
+               // On continue seulement si ya pas d'erreur d'obligation et qu'il y a une demande de verif\r
+               if ((!isset($erreurs[$champ]) or !$erreurs[$champ]) and is_array($verifier) and $verif_fonction){\r
+                       $normaliser = null;\r
+                       // Si le champ n'est pas valide par rapport au test demandé, on ajoute l'erreur\r
+                       $options = isset($verifier['options']) ? $verifier['options'] : array();\r
+                       if ($erreur_eventuelle = $verif_fonction($valeur, $verifier['type'], $options, $normaliser)) {\r
+                               $erreurs[$champ] = $erreur_eventuelle;\r
+                       // S'il n'y a pas d'erreur et que la variable de normalisation a été remplie, on l'injecte dans le POST\r
+                       } elseif (!is_null($normaliser)) {\r
+                               set_request($champ, $normaliser);\r
+                       }\r
+               }\r
+       }\r
+       \r
+       return $erreurs;\r
+}\r
+\r
+/**\r
+ * Applatie une description tabulaire\r
+ * @param string $tab, le tableau à aplatir\r
+ * @return $nouveau_tab\r
+ */\r
+function saisies_aplatir_tableau($tab){\r
+       $nouveau_tab = array();\r
+       foreach($tab as $entree=>$contenu){\r
+               if (is_array($contenu)) {\r
+                       foreach ($contenu as $cle => $valeur) {\r
+                               $nouveau_tab[$cle] = $valeur;\r
+                       }\r
+               } else {\r
+                       $nouveau_tab[$entree] = $contenu;\r
+               }\r
+       }\r
+       return $nouveau_tab;\r
+}\r
+\r
+/**\r
+ * Applatie une description chaînée, en supprimant les sous-groupes.\r
+ * @param string $chaine, la chaîne à aplatir\r
+ * @return $chaine\r
+ */\r
+function saisies_aplatir_chaine($chaine){\r
+       return trim(preg_replace("#(?:^|\n)(\*(?:.*)|/\*)\n#i","\n",$chaine));\r
+}\r
+\r
+/**\r
+ * Transforme une chaine en tableau avec comme principe :\r
+ * \r
+ * - une ligne devient une case\r
+ * - si la ligne est de la forme truc|bidule alors truc est la clé et bidule la valeur\r
+ * - si la ligne commence par * alors on commence un sous-tableau\r
+ * - si la ligne est égale à /*, alors on fini le sous-tableau\r
+ * \r
+ * @param string $chaine Une chaine à transformer\r
+ * @return array Retourne un tableau PHP\r
+ */\r
+function saisies_chaine2tableau($chaine, $separateur="\n"){\r
+       if ($chaine and is_string($chaine)){\r
+               $tableau = array();\r
+               $soustab = False;\r
+               // On découpe d'abord en lignes\r
+               $lignes = explode($separateur, $chaine);\r
+               foreach ($lignes as $i=>$ligne){\r
+                       $ligne = trim(trim($ligne), '|');\r
+                       // Si ce n'est pas une ligne sans rien\r
+                       if ($ligne !== ''){\r
+                               // si ca commence par * c'est qu'on va faire un sous tableau\r
+                               if (strpos($ligne,"*")===0) {\r
+                                       $soustab=True;\r
+                                       $soustab_cle    = _T_ou_typo(substr($ligne,1), 'multi');\r
+                                       if (!isset($tableau[$soustab_cle])){\r
+                                               $tableau[$soustab_cle] = array();\r
+                                       }\r
+                               }\r
+                               elseif ($ligne=="/*") {//si on finit sous tableau\r
+                                       $soustab=False;\r
+                               }\r
+                               else{//sinon c'est une entrée normale\r
+                               // Si on trouve un découpage dans la ligne on fait cle|valeur\r
+                                       if (strpos($ligne, '|') !== false) {\r
+                                               list($cle,$valeur) = explode('|', $ligne, 2);\r
+                                               // permettre les traductions de valeurs au passage\r
+                                               if ($soustab == True){\r
+                                                       $tableau[$soustab_cle][$cle] = _T_ou_typo($valeur, 'multi');\r
+                                               } else {\r
+                                                       $tableau[$cle] = _T_ou_typo($valeur, 'multi');\r
+                                               }\r
+                                       }\r
+                               // Sinon on génère la clé\r
+                                       else{\r
+                                               if ($soustab == True) {\r
+                                                       $tableau[$soustab_cle][$i] = _T_ou_typo($ligne,'multi');\r
+                                               } else {\r
+                                                       $tableau[$i] = _T_ou_typo($ligne,'multi');\r
+                                               }\r
+                                       }\r
+                               }\r
+                       }\r
+               }\r
+               return $tableau;\r
+       }\r
+       // Si c'est déjà un tableau on lui applique _T_ou_typo (qui fonctionne de manière récursive avant de le renvoyer\r
+       elseif (is_array($chaine)){\r
+               return _T_ou_typo($chaine, 'multi');\r
+       } else {\r
+               return array();\r
+       }\r
+}\r
+\r
+/**\r
+ * Transforme un tableau en chaine de caractères avec comme principe :\r
+ * \r
+ * - une case de vient une ligne de la chaine\r
+ * - chaque ligne est générée avec la forme cle|valeur\r
+ * - si une entrée du tableau est elle même un tableau, on met une ligne de la forme *clef\r
+ * - pour marquer que l'on quitte un sous-tableau, on met une ligne commencant par /*, sauf si on bascule dans un autre sous-tableau.\r
+ */\r
+function saisies_tableau2chaine($tableau){\r
+       if ($tableau and is_array($tableau)){\r
+               $chaine = '';\r
+               $avant_est_tableau = False;\r
+               foreach($tableau as $cle=>$valeur){\r
+                       if (is_array($valeur)){\r
+                               $avant_est_tableau = True;\r
+                               $ligne=trim("*$cle");\r
+                               $chaine .= "$ligne\n";\r
+                               $chaine .= saisies_tableau2chaine($valeur)."\n";\r
+                               }\r
+                       else{   \r
+                               if ($avant_est_tableau == True){\r
+                                               $avant_est_tableau = False;\r
+                                               $chaine.="/*\n";\r
+                                       }\r
+                               $ligne = trim("$cle|$valeur");\r
+                               $chaine .= "$ligne\n";\r
+                       }\r
+               }\r
+               $chaine = trim($chaine);\r
+       \r
+               return $chaine;\r
+       }\r
+       // Si c'est déjà une chaine on la renvoie telle quelle\r
+       elseif (is_string($tableau)){\r
+               return $tableau;\r
+       }\r
+       else{\r
+               return '';\r
+       }\r
+}\r
+\r
+\r
+\r
+\r
+/**\r
+ * Passe une valeur en tableau d'élements si ce n'en est pas une\r
+ *\r
+ * entrée :\r
+ * cle|valeur\r
+ * cle|valeur\r
+ *\r
+ * Sinon :\r
+ * valeur,valeur\r
+ *\r
+ * @param mixed $valeur\r
+ * @return array Tableau de valeurs\r
+**/\r
+function saisies_valeur2tableau($valeur, $sinon_separateur="") {\r
+       if (is_array($valeur)) {\r
+               return $valeur;\r
+       }\r
+       \r
+       if (!strlen($valeur)) {\r
+               return array();\r
+       }\r
+       \r
+       $t = saisies_chaine2tableau($valeur);\r
+       if (count($t) > 1) {\r
+               return $t;\r
+       }\r
+\r
+       // qu'une seule valeur, c'est qu'elle a peut etre un separateur a virgule\r
+       // et a donc une cle est 0 dans ce cas la d'ailleurs\r
+       if (isset($t[0])) {\r
+               $t = saisies_chaine2tableau($t[0], ',');\r
+       }\r
+       \r
+       return $t;\r
+}\r
+\r
+\r
+\r
+\r
+/*\r
+ * Génère une page d'aide listant toutes les saisies et leurs options\r
+ */\r
+function saisies_generer_aide(){\r
+       // On a déjà la liste par saisie\r
+       $saisies = saisies_lister_disponibles();\r
+       \r
+       // On construit une liste par options\r
+       $options = array();\r
+       foreach ($saisies as $type_saisie=>$saisie){\r
+               $options_saisie = saisies_lister_par_nom($saisie['options'], false);\r
+               foreach ($options_saisie as $nom=>$option){\r
+                       // Si l'option n'existe pas encore\r
+                       if (!isset($options[$nom])){\r
+                               $options[$nom] = _T_ou_typo($option['options']);\r
+                       }\r
+                       // On ajoute toujours par qui c'est utilisé\r
+                       $options[$nom]['utilisee_par'][] = $type_saisie;\r
+               }\r
+               ksort($options_saisie);\r
+               $saisies[$type_saisie]['options'] = $options_saisie;\r
+       }\r
+       ksort($options);\r
+       \r
+       return recuperer_fond(\r
+               'inclure/saisies_aide',\r
+               array(\r
+                       'saisies' => $saisies,\r
+                       'options' => $options\r
+               )\r
+       );\r
+}\r
+\r
+/*\r
+ * Le tableau de saisies a-t-il une option afficher_si ?\r
+ *\r
+ * @param array $saisies Un tableau de saisies\r
+ * @return boolean\r
+ */\r
+\r
+function saisies_afficher_si($saisies) {\r
+       $saisies = saisies_lister_par_nom($saisies,true);\r
+       // Dès qu'il y a au moins une option afficher_si, on l'active\r
+       foreach ($saisies as $saisie) {\r
+               if (isset($saisie['options']['afficher_si']))\r
+                       return true;\r
+       }\r
+       return false;\r
+}\r
+\r
+\r
+/*\r
+ * Le tableau de saisies a-t-il une option afficher_si_remplissage ?\r
+ *\r
+ * @param array $saisies Un tableau de saisies\r
+ * @return boolean\r
+ */\r
+function saisies_afficher_si_remplissage($saisies) {\r
+       $saisies = saisies_lister_par_nom($saisies,true);\r
+       // Dès qu'il y a au moins une option afficher_si_remplissage, on l'active\r
+       foreach ($saisies as $saisie) {\r
+               if (isset($saisie['options']['afficher_si_remplissage']))\r
+                       return true;\r
+       }\r
+       return false;\r
+}\r
+?>\r
diff --git a/www/plugins/saisies/inc/saisies_afficher.php b/www/plugins/saisies/inc/saisies_afficher.php
new file mode 100644 (file)
index 0000000..b4a713c
--- /dev/null
@@ -0,0 +1,429 @@
+<?php\r
+\r
+/**\r
+ * Gestion de l'affichage des saisies\r
+ *\r
+ * @return SPIP\Saisies\Afficher\r
+**/\r
+\r
+// Sécurité\r
+if (!defined('_ECRIRE_INC_VERSION')) return;\r
+\r
+/**\r
+ * Indique si une saisie peut être affichée.\r
+ *\r
+ * On s'appuie sur l'éventuelle clé "editable" du $champ.\r
+ * Si editable vaut :\r
+ *    - absent : le champ est éditable\r
+ *    - 1, le champ est éditable\r
+ *    - 0, le champ n'est pas éditable\r
+ *    - -1, le champ est éditable s'il y a du contenu dans le champ (l'environnement)\r
+ *         ou dans un de ses enfants (fieldsets)\r
+ *\r
+ * @param array $champ\r
+ *     Tableau de description de la saisie\r
+ * @param array $env\r
+ *     Environnement transmis à la saisie, certainement l'environnement du formulaire\r
+ * @param bool $utiliser_editable\r
+ *     - false pour juste tester le cas -1\r
+ *\r
+ * @return bool\r
+ *     Retourne un booléen indiquant l'état éditable ou pas :\r
+ *     - true si la saisie est éditable (peut être affichée)\r
+ *     - false sinon\r
+ */\r
+function saisie_editable($champ, $env, $utiliser_editable=true) {\r
+       if ($utiliser_editable) {\r
+               // si le champ n'est pas éditable, on sort.\r
+               if (!isset($champ['editable'])) {\r
+                       return true;\r
+               }\r
+               $editable = $champ['editable'];\r
+\r
+               if ($editable > 0) {\r
+                       return true;\r
+               }\r
+               if ($editable == 0) {\r
+                       return false;\r
+               }\r
+       }\r
+\r
+       // cas -1\r
+       // name de la saisie\r
+       if (isset($champ['options']['nom'])) {\r
+               // si on a le name dans l'environnement, on le teste\r
+               $nom = $champ['options']['nom'];\r
+               if (isset($env[$nom])) {\r
+                       return $env[$nom] ? true : false ;\r
+               }\r
+       }\r
+       // sinon, si on a des sous saisies\r
+       if (isset($champ['saisies']) and is_array($champ['saisies'])) {\r
+               foreach($champ['saisies'] as $saisie) {\r
+                       if (saisie_editable($saisie, $env, false)) {\r
+                               return true;\r
+                       }\r
+               }\r
+       }\r
+\r
+       // aucun des paramètres demandés n'avait de contenu\r
+       return false;\r
+}\r
+\r
+/**\r
+ * Génère une saisie à partir d'un tableau la décrivant et de l'environnement\r
+ *\r
+ * @param array $champ\r
+ *     Description de la saisie.\r
+ *     Le tableau doit être de la forme suivante :\r
+ *     array(\r
+ *                     'saisie' => 'input',\r
+ *                     'options' => array(\r
+ *                             'nom' => 'le_name',\r
+ *                             'label' => 'Un titre plus joli',\r
+ *                             'obligatoire' => 'oui',\r
+ *                             'explication' => 'Remplissez ce champ en utilisant votre clavier.'\r
+ *                     )\r
+ *     )\r
+ * @param array $env\r
+ *     Environnement du formulaire\r
+ *     Permet de savoir les valeurs actuelles des contenus des saisies,\r
+ *     les erreurs eventuelles présentes...\r
+ * @return string\r
+ *     Code HTML des saisies de formulaire\r
+ */\r
+function saisies_generer_html($champ, $env=array()){\r
+       // Si le parametre n'est pas bon, on genere du vide\r
+       if (!is_array($champ))\r
+               return '';\r
+\r
+       // Si la saisie n'est pas editable, on sort aussi.\r
+       if (!saisie_editable($champ, $env)) {\r
+               return '';\r
+       }\r
+\r
+       $contexte = array();\r
+\r
+       // On sélectionne le type de saisie\r
+       $contexte['type_saisie'] = $champ['saisie'];\r
+       // Identifiant unique de saisie, si present\r
+       if (isset($champ['identifiant'])) {\r
+               $contexte['id_saisie'] = $champ['identifiant'];\r
+       }\r
+\r
+       // Peut-être des transformations à faire sur les options textuelles\r
+       $options = $champ['options'];\r
+       foreach ($options as $option => $valeur){\r
+               if ($option == 'datas') {\r
+                       // exploser une chaine datas en tableau (applique _T_ou_typo sur chaque valeur)\r
+                       $options[$option] = saisies_chaine2tableau($valeur);\r
+               } else {\r
+                       $options[$option] = _T_ou_typo($valeur, 'multi');\r
+               }\r
+       }\r
+\r
+       // On ajoute les options propres à la saisie\r
+       $contexte = array_merge($contexte, $options);\r
+\r
+       // Si env est définie dans les options ou qu'il y a des enfants, on ajoute tout l'environnement\r
+       if (isset($contexte['env']) or (isset($champ['saisies']) AND is_array($champ['saisies']))) {\r
+               unset($contexte['env']);\r
+\r
+               // on sauve l'ancien environnement\r
+               // car les sous-saisies ne doivent pas être affectees\r
+               // par les modification sur l'environnement servant à generer la saisie mère\r
+               $contexte['_env'] = $env;\r
+\r
+               // À partir du moment où on passe tout l'environnement, il faut enlever certains éléments qui ne doivent absolument provenir que des options\r
+               unset($env['inserer_debut']);\r
+               unset($env['inserer_fin']);\r
+               $saisies_disponibles = saisies_lister_disponibles();\r
+               if (isset($saisies_disponibles[$contexte['type_saisie']]) and is_array($saisies_disponibles[$contexte['type_saisie']]['options'])) {\r
+                       $options_a_supprimer = saisies_lister_champs($saisies_disponibles[$contexte['type_saisie']]['options']);\r
+                       foreach ($options_a_supprimer as $option_a_supprimer){\r
+                               unset($env[$option_a_supprimer]);\r
+                       }\r
+               }\r
+\r
+               $contexte = array_merge($env, $contexte);\r
+       }\r
+       // Sinon on ne sélectionne que quelques éléments importants\r
+       else{\r
+               // On récupère la liste des erreurs\r
+               $contexte['erreurs'] = $env['erreurs'];\r
+               // On ajoute toujours le bon self\r
+               $contexte['self'] = self();\r
+       }\r
+\r
+       // Dans tous les cas on récupère de l'environnement la valeur actuelle du champ\r
+       // Si le nom du champ est un tableau indexé, il faut parser !\r
+       if (preg_match('/([\w]+)((\[[\w]+\])+)/', $contexte['nom'], $separe)){\r
+               $contexte['valeur'] = $env[$separe[1]];\r
+               preg_match_all('/\[([\w]+)\]/', $separe[2], $index);\r
+               // On va chercher au fond du tableau\r
+               foreach($index[1] as $cle){\r
+                       $contexte['valeur'] = isset($contexte['valeur'][$cle]) ? $contexte['valeur'][$cle] : null;\r
+               }\r
+       }\r
+       // Sinon la valeur est juste celle du nom\r
+       else {\r
+               $contexte['valeur'] = (isset($env[$contexte['nom']]) ? $env[$contexte['nom']] : null);\r
+       }\r
+\r
+       // Si ya des enfants on les remonte dans le contexte\r
+       if (isset($champ['saisies']) and is_array($champ['saisies']))\r
+               $contexte['saisies'] = $champ['saisies'];\r
+\r
+       // On génère la saisie\r
+       return recuperer_fond(\r
+               'saisies/_base',\r
+               $contexte\r
+       );\r
+}\r
+\r
+/**\r
+ * Génère une vue d'une saisie à partir d'un tableau la décrivant\r
+ *\r
+ * @see saisies_generer_html()\r
+ * @param array $saisie\r
+ *     Tableau de description d'une saisie\r
+ * @param array $env\r
+ *     L'environnement, contenant normalement la réponse à la saisie\r
+ * @param array $env_obligatoire\r
+ *     ???\r
+ * @return string\r
+ *     Code HTML de la vue de la saisie\r
+ */\r
+function saisies_generer_vue($saisie, $env=array(), $env_obligatoire=array()){\r
+       // Si le paramètre n'est pas bon, on génère du vide\r
+       if (!is_array($saisie))\r
+               return '';\r
+\r
+       $contexte = array();\r
+\r
+       // On sélectionne le type de saisie\r
+       $contexte['type_saisie'] = $saisie['saisie'];\r
+\r
+       // Peut-être des transformations à faire sur les options textuelles\r
+       $options = $saisie['options'];\r
+       foreach ($options as $option => $valeur){\r
+               if ($option == 'datas') {\r
+                       // exploser une chaine datas en tableau (applique _T_ou_typo sur chaque valeur)\r
+                       $options[$option] = saisies_chaine2tableau($valeur);\r
+               } else {\r
+                       $options[$option] = _T_ou_typo($valeur, 'multi');\r
+               }\r
+       }\r
+\r
+       // On ajoute les options propres à la saisie\r
+       $contexte = array_merge($contexte, $options);\r
+\r
+       // Si env est définie dans les options ou qu'il y a des enfants, on ajoute tout l'environnement\r
+       if (isset($contexte['env']) or (isset($saisie['saisies']) AND is_array($saisie['saisies']))){\r
+               unset($contexte['env']);\r
+\r
+               // on sauve l'ancien environnement\r
+               // car les sous-saisies ne doivent pas être affectees\r
+               // par les modification sur l'environnement servant à generer la saisie mère\r
+               $contexte['_env'] = $env;\r
+\r
+               // À partir du moment où on passe tout l'environnement, il faut enlever\r
+               // certains éléments qui ne doivent absolument provenir que des options\r
+               $saisies_disponibles = saisies_lister_disponibles();\r
+               if (is_array($saisies_disponibles[$contexte['type_saisie']]['options'])){\r
+                       $options_a_supprimer = saisies_lister_champs($saisies_disponibles[$contexte['type_saisie']]['options']);\r
+                       foreach ($options_a_supprimer as $option_a_supprimer){\r
+                               unset($env[$option_a_supprimer]);\r
+                       }\r
+               }\r
+\r
+               $contexte = array_merge($env, $contexte);\r
+       }\r
+\r
+       // Dans tous les cas on récupère de l'environnement la valeur actuelle du champ\r
+\r
+       // On regarde en priorité s'il y a un tableau listant toutes les valeurs\r
+       if ($env['valeurs'] and is_array($env['valeurs']) and isset($env['valeurs'][$contexte['nom']])){\r
+               $contexte['valeur'] = $env['valeurs'][$contexte['nom']];\r
+       }\r
+       // Si le nom du champ est un tableau indexé, il faut parser !\r
+       elseif (preg_match('/([\w]+)((\[[\w]+\])+)/', $contexte['nom'], $separe)){\r
+               $contexte['valeur'] = $env[$separe[1]];\r
+               preg_match_all('/\[([\w]+)\]/', $separe[2], $index);\r
+               // On va chercher au fond du tableau\r
+               foreach($index[1] as $cle){\r
+                       $contexte['valeur'] = $contexte['valeur'][$cle];\r
+               }\r
+       }\r
+       // Sinon la valeur est juste celle du nom\r
+       else {\r
+               // certains n'ont pas de nom (fieldset)\r
+               $contexte['valeur'] = isset($env[$contexte['nom']]) ? $env[$contexte['nom']] : '';\r
+       }\r
+\r
+       // Si ya des enfants on les remonte dans le contexte\r
+       if (isset($saisie['saisies']) AND is_array($saisie['saisies']))\r
+               $contexte['saisies'] = $saisie['saisies'];\r
+\r
+       if (is_array($env_obligatoire)) {\r
+               $contexte = array_merge($contexte, $env_obligatoire);\r
+       }\r
+\r
+       // On génère la saisie\r
+       return recuperer_fond(\r
+               'saisies-vues/_base',\r
+               $contexte\r
+       );\r
+}\r
+\r
+/**\r
+ * Génère, à partir d'un tableau de saisie le code javascript ajouté à la fin de #GENERER_SAISIES\r
+ * pour produire un affichage conditionnel des saisies ayant une option afficher_si ou afficher_si_remplissage.\r
+ *\r
+ * @param array $saisies\r
+ *     Tableau de descriptions des saisies\r
+ * @param string $id_form\r
+ *     Identifiant unique pour le formulaire\r
+ * @return text\r
+ *     Code javascript\r
+ */\r
+function saisies_generer_js_afficher_si($saisies,$id_form){\r
+       $i = 0;\r
+       $saisies = saisies_lister_par_nom($saisies,true);\r
+       $code = '';\r
+       $code .= '(function($){';\r
+       $code .= '$(document).ready(function(){chargement=true;';\r
+               $code .= 'verifier_saisies_'.$id_form." = function(form){\n";\r
+                               foreach ($saisies as $saisie) {\r
+                                       // on utilise comme selecteur l'identifiant de saisie en priorite s'il est connu\r
+                                       // parce que li_class = 'tableau[nom][option]' ne fonctionne evidement pas\r
+                                       // lorsque le name est un tableau\r
+                                       if (isset($saisie['options']['afficher_si']) or isset($saisie['options']['afficher_si_remplissage'])) {\r
+                                               $i++;\r
+                                               // retrouver la classe css probable\r
+                                               switch ($saisie['saisie']) {\r
+                                                       case 'fieldset':\r
+                                                               $class_li = 'fieldset_'.$saisie['options']['nom'];\r
+                                                               break;\r
+                                                       case 'explication':\r
+                                                               $class_li = 'explication_'.$saisie['options']['nom'];\r
+                                                               break;\r
+                                                       default:\r
+                                                               $class_li = 'editer_'.$saisie['options']['nom'];\r
+                                               }\r
+                                               $afficher_si = isset($saisie['options']['afficher_si']) ? $saisie['options']['afficher_si'] : '';\r
+                                               $afficher_si_remplissage = isset($saisie['options']['afficher_si_remplissage']) ? $saisie['options']['afficher_si_remplissage'] : '';\r
+                                               $condition = join("\n", array_filter(array($afficher_si, $afficher_si_remplissage)));\r
+                                               // retrouver l'identifiant\r
+                                               $identifiant = '';\r
+                                               if (isset($saisie['identifiant']) and $saisie['identifiant']) {\r
+                                                       $identifiant = $saisie['identifiant'];\r
+                                               }\r
+                                               // On gère le cas @plugin:non_plugin@\r
+                                               preg_match_all('#@plugin:(.+)@#U', $condition, $matches);\r
+                                               foreach ($matches[1] as $plug) {\r
+                                                       if (defined('_DIR_PLUGIN_'.strtoupper($plug)))\r
+                                                               $condition = preg_replace('#@plugin:'.$plug.'@#U', 'true', $condition);\r
+                                                       else\r
+                                                               $condition = preg_replace('#@plugin:'.$plug.'@#U', 'false', $condition);\r
+                                               }\r
+                                               // On gère le cas @config:plugin:meta@ suivi d'un test\r
+                                               preg_match_all('#@config:(.+):(.+)@#U', $condition, $matches);\r
+                                               foreach ($matches[1] as $plugin) {\r
+                                                       $config = lire_config($plugin);\r
+                                                       $condition = preg_replace('#@config:'.$plugin.':'.$matches[2][0].'@#U', '"'.$config[$matches[2][0]].'"', $condition);\r
+                                               }\r
+                                               // On transforme en une condition valide\r
+                                               preg_match_all('#@(.+)@#U', $condition, $matches);\r
+                                               foreach ($matches[1] as $nom) {\r
+                                                       switch($saisies[$nom]['saisie']) {\r
+                                                               case 'radio':\r
+                                                               case 'oui_non':\r
+                                                                       $condition = preg_replace('#@'.preg_quote($nom).'@#U', '$(form).find("[name=\''.$nom.'\']:checked").val()', $condition);\r
+                                                                       break;\r
+                                                               case 'case':\r
+                                                                       $condition = preg_replace('#@'.preg_quote($nom).'@#U', '($(form).find(".checkbox[name=\''.$nom.'\']").is(":checked") ? $(form).find(".checkbox[name=\''.$nom.'\']").val() : "")', $condition);\r
+                                                                       break;\r
+                                                               case 'checkbox':\r
+                                                                       preg_match_all('#@(.+)@\s*==\s*"(.*)"$#U', $condition, $matches2);\r
+                                                                       foreach ($matches2[2] as $value) {\r
+                                                                               $condition = preg_replace('#@'.preg_quote($nom).'@#U', '($(form).find(".checkbox[name=\''.$nom.'[]\'][value='.$value.']").is(":checked") ? $(form).find(".checkbox[name=\''.$nom.'[]\'][value='.$value.']").val() : "")', $condition);\r
+                                                                               }\r
+                                                                       break;\r
+                                                               default:\r
+                                                                       $condition = preg_replace('#@'.preg_quote($nom).'@#U', '$(form).find("[name=\''.$nom.'\']").val()', $condition);\r
+                                                       }\r
+                                               }\r
+                                               if ($identifiant) {\r
+                                                       $sel = "li[data-id='$identifiant']";\r
+                                               } else {\r
+                                                       $sel = "li.$class_li";\r
+                                               }\r
+                                               $code .= "\tif (".$condition.') {$(form).find("'.$sel.'").show(400);} '."\n\t";\r
+                                               $code .= 'else {if (chargement==true) {$(form).find("'.$sel.'").hide(400).css("display","none");} else {$(form).find("'.$sel.'").hide(400);};} '."\n";\r
+                                       }\r
+                               }\r
+               $code .= "};";\r
+               $code .= '$("li#afficher_si_'.$id_form.'").parents("form").each(function(){verifier_saisies_'.$id_form.'(this);});';\r
+               $code .= '$("li#afficher_si_'.$id_form.'").parents("form").change(function(){verifier_saisies_'.$id_form.'(this);});';\r
+       $code .= 'chargement=false;})';\r
+       $code .= '})(jQuery);';\r
+       return $i>0 ? $code : '';\r
+}\r
+\r
+/**\r
+ * Lorsque l'on affiche les saisies (#VOIR_SAISIES), les saisies ayant une option afficher_si\r
+ * et dont les conditions ne sont pas remplies doivent être retirées du tableau de saisies.\r
+ *\r
+ * Cette fonction sert aussi lors de la vérification des saisies avec saisies_verifier().\r
+ * À ce moment là, les saisies non affichées sont retirées de _request\r
+ * (on passe leur valeur à NULL).\r
+ *\r
+ * @param array $saisies\r
+ *     Tableau de descriptions de saisies\r
+ * @param array|null $env\r
+ *     Tableau d'environnement transmis dans inclure/voi_saisies.html,\r
+ *     NULL si on doit rechercher dans _request (pour saisies_verifier()).\r
+ * @return array\r
+ *     Tableau de descriptions de saisies\r
+ */\r
+function saisies_verifier_afficher_si($saisies, $env=NULL) {\r
+       // eviter une erreur par maladresse d'appel :)\r
+       if (!is_array($saisies)) {\r
+               return array();\r
+       }\r
+       foreach ($saisies as $cle => $saisie) {\r
+               if (isset($saisie['options']['afficher_si'])) {\r
+                       $condition = $saisie['options']['afficher_si'];\r
+                       // On gère le cas @plugin:non_plugin@\r
+                       preg_match_all('#@plugin:(.+)@#U', $condition, $matches);\r
+                       foreach ($matches[1] as $plug) {\r
+                               if (defined('_DIR_PLUGIN_'.strtoupper($plug)))\r
+                                       $condition = preg_replace('#@plugin:'.$plug.'@#U', 'true', $condition);\r
+                               else\r
+                                       $condition = preg_replace('#@plugin:'.$plug.'@#U', 'false', $condition);\r
+                       }\r
+                       // On gère le cas @config:plugin:meta@ suivi d'un test\r
+                       preg_match_all('#@config:(.+):(.+)@#U', $condition, $matches);\r
+                       foreach ($matches[1] as $plugin) {\r
+                               $config = lire_config($plugin);\r
+                               $condition = preg_replace('#@config:'.$plugin.':'.$matches[2][0].'@#U', '"'.$config[$matches[2][0]].'"', $condition);\r
+                       }\r
+                       // On transforme en une condition valide\r
+                       if (is_null($env))\r
+                               $condition = preg_replace('#@(.+)@#U', '_request(\'$1\')', $condition);\r
+                       else\r
+                               $condition = preg_replace('#@(.+)@#U', '$env["valeurs"][\'$1\']', $condition);\r
+                       eval('$ok = '.$condition.';');\r
+                       if (!$ok) {\r
+                               unset($saisies[$cle]);\r
+                               if (is_null($env)) set_request($saisie['options']['nom'],NULL);\r
+                       }\r
+               }\r
+               if (isset($saisies[$cle]['saisies'])) // S'il s'agit d'un fieldset ou equivalent, verifier les sous-saisies\r
+                       $saisies[$cle]['saisies'] = saisies_verifier_afficher_si($saisies[$cle]['saisies'], $env);\r
+       }\r
+       return $saisies;\r
+}\r
+\r
+?>\r
diff --git a/www/plugins/saisies/inc/saisies_lister.php b/www/plugins/saisies/inc/saisies_lister.php
new file mode 100644 (file)
index 0000000..482e079
--- /dev/null
@@ -0,0 +1,324 @@
+<?php
+
+// Sécurité
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+/*
+ * Prend la description complète du contenu d'un formulaire et retourne
+ * les saisies "à plat" classées par identifiant unique.
+ *
+ * @param array $contenu Le contenu d'un formulaire
+ * @param bool $avec_conteneur Indique si on renvoie aussi les saisies ayant des enfants, comme les fieldsets
+ * @return array Un tableau avec uniquement les saisies
+ */
+function saisies_lister_par_identifiant($contenu, $avec_conteneur=true){
+       $saisies = array();
+
+       if (is_array($contenu)){
+               foreach ($contenu as $ligne){
+                       if (is_array($ligne)) {
+                               $enfants_presents = (isset($ligne['saisies']) and is_array($ligne['saisies']));
+                               if (array_key_exists('saisie', $ligne) and (!$enfants_presents or $avec_conteneur)){
+                                       $saisies[$ligne['identifiant']] = $ligne;
+                               }
+                               if ($enfants_presents) {
+                                       $saisies = array_merge($saisies, saisies_lister_par_identifiant($ligne['saisies']));
+                               }
+                       }
+               }
+       }
+       
+       return $saisies;
+}
+
+/*
+ * Prend la description complète du contenu d'un formulaire et retourne
+ * les saisies "à plat" classées par nom.
+ *
+ * @param array $contenu Le contenu d'un formulaire
+ * @param bool $avec_conteneur Indique si on renvoie aussi les saisies ayant des enfants, comme les fieldset
+ * @return array Un tableau avec uniquement les saisies
+ */
+function saisies_lister_par_nom($contenu, $avec_conteneur=true){
+       $saisies = array();
+       
+       if (is_array($contenu)){
+               foreach ($contenu as $ligne){
+                       if (is_array($ligne)){
+                               if (array_key_exists('saisie', $ligne) and (!isset($ligne['saisies']) OR !is_array($ligne['saisies']) or $avec_conteneur)){
+                                       $saisies[$ligne['options']['nom']] = $ligne;
+                               }
+                               if (isset($ligne['saisies']) AND is_array($ligne['saisies'])){
+                                       $saisies = array_merge($saisies, saisies_lister_par_nom($ligne['saisies']));
+                               }
+                       }
+               }
+       }
+       
+       return $saisies;
+}
+
+/*
+ * Liste les saisies ayant une option X
+ * # saisies_lister_avec_option('sql', $saisies);
+ *  
+ *
+ * @param String $option Nom de l'option cherchée
+ * @param Array $saisies Liste de saisies
+ * @param String $tri tri par défaut des résultats (s'ils ne sont pas deja triés) ('nom', 'identifiant')
+ * @return liste de ces saisies triees par nom ayant une option X définie
+ */
+function saisies_lister_avec_option($option, $saisies, $tri = 'nom') {
+       $saisies_option = array();
+       // tri par nom si ce n'est pas le cas
+       $s = array_keys($saisies);
+       if (is_int(array_shift($s))) {
+               $trier = 'saisies_lister_par_' . $tri;
+               $saisies = $trier($saisies);
+       }
+       foreach ($saisies as $nom_ou_id => $saisie) {
+               if (isset($saisie['options'][$option]) and $saisie['options'][$option]) {
+                       $saisies_option[$nom_ou_id] = $saisie;
+               }
+       }
+
+       return $saisies_option;
+}
+
+/*
+ * Liste les saisies ayant une definition SQL
+ *
+ * @param Array $saisies liste de saisies
+ * @param String $tri tri par défaut des résultats (s'ils ne sont pas deja triés) ('nom', 'identifiant')
+ * @return liste de ces saisies triees par nom ayant une option sql définie
+ */
+function saisies_lister_avec_sql($saisies, $tri = 'nom') {
+       return saisies_lister_avec_option('sql', $saisies, $tri);
+}
+
+/*
+ * Prend la description complète du contenu d'un formulaire et retourne
+ * les saisies "à plat" classées par type de saisie.
+ * $saisie['input']['input_1'] = $saisie
+ *
+ * @param array $contenu Le contenu d'un formulaire
+ * @return array Un tableau avec uniquement les saisies
+ */
+function saisies_lister_par_type($contenu) {
+       $saisies = array();
+       
+       if (is_array($contenu)){
+               foreach ($contenu as $ligne){
+                       if (is_array($ligne)){
+                               if (array_key_exists('saisie', $ligne) and (!is_array($ligne['saisies']))){
+                                       $saisies[ $ligne['saisie'] ][ $ligne['options']['nom'] ] = $ligne;
+                               }
+                               if (is_array($ligne['saisies'])){
+                                       $saisies = array_merge_recursive($saisies, saisies_lister_par_type($ligne['saisies']));
+                               }
+                       }
+               }
+       }
+       
+       return $saisies;
+}
+
+/*
+ * Prend la description complète du contenu d'un formulaire et retourne
+ * une liste des noms des champs du formulaire.
+ *
+ * @param array $contenu Le contenu d'un formulaire
+ * @return array Un tableau listant les noms des champs
+ */
+function saisies_lister_champs($contenu, $avec_conteneur=true){
+       $saisies = saisies_lister_par_nom($contenu, $avec_conteneur);
+       return array_keys($saisies);
+}
+
+/*
+ * A utiliser dans une fonction charger d'un formulaire CVT,
+ * cette fonction renvoie le tableau de contexte correspondant
+ * de la forme $contexte['nom_champ'] = ''
+ *
+ * @param array $contenu Le contenu d'un formulaire (un tableau de saisies)
+ * @return array Un tableau de contexte
+ */
+function saisies_charger_champs($contenu) {
+       // array_fill_keys est disponible uniquement avec PHP >= 5.2.0
+       // return array_fill_keys(saisies_lister_champs($contenu, false), '');
+       $champs = array();
+       foreach (saisies_lister_champs($contenu, false) as $champ)
+               $champs[$champ] = '';
+       return $champs;
+}
+
+/*
+ * Prend la description complète du contenu d'un formulaire et retourne
+ * une liste des valeurs par défaut des champs du formulaire.
+ *
+ * @param array $contenu Le contenu d'un formulaire
+ * @return array Un tableau renvoyant la valeur par défaut de chaque champs
+ */
+function saisies_lister_valeurs_defaut($contenu){
+       $contenu = saisies_lister_par_nom($contenu, false);
+       $defauts = array();
+       foreach ($contenu as $nom => $saisie){
+               // Si le nom du champ est un tableau indexé, il faut parser !
+               if (preg_match('/([\w]+)((\[[\w]+\])+)/', $nom, $separe)){
+                       $nom = $separe[1];
+                       // Dans ce cas on ne récupère que le nom, la valeur par défaut du tableau devra être renseigné autre part
+                       $defauts[$nom] = array();
+               }
+               else{
+                       $defauts[$nom] = isset($saisie['options']['defaut']) ? $saisie['options']['defaut'] : '';
+               }
+       }
+       return $defauts;
+}
+
+/*
+ * Compare deux tableaux de saisies pour connaitre les différences
+ * @param array $saisies_anciennes Un tableau décrivant des saisies
+ * @param array $saisies_nouvelles Un autre tableau décrivant des saisies
+ * @param bool $avec_conteneur Indique si on veut prendre en compte dans la comparaison les conteneurs comme les fieldsets
+ * @param string $tri Comparer selon quel tri ? 'nom' / 'identifiant'
+ * @return array Retourne le tableau des saisies supprimées, ajoutées et modifiées
+ */
+function saisies_comparer($saisies_anciennes, $saisies_nouvelles, $avec_conteneur=true, $tri = 'nom') {
+       $trier = "saisies_lister_par_$tri";
+       $saisies_anciennes = $trier($saisies_anciennes, $avec_conteneur);
+       $saisies_nouvelles = $trier($saisies_nouvelles, $avec_conteneur);
+       
+       // Les saisies supprimées sont celles qui restent dans les anciennes quand on a enlevé toutes les nouvelles
+       $saisies_supprimees = array_diff_key($saisies_anciennes, $saisies_nouvelles);
+       // Les saisies ajoutées, c'est le contraire
+       $saisies_ajoutees = array_diff_key($saisies_nouvelles, $saisies_anciennes);
+       // Il reste alors les saisies qui ont le même nom
+       $saisies_restantes = array_intersect_key($saisies_anciennes, $saisies_nouvelles);
+       // Dans celles-ci, celles qui sont modifiées sont celles dont la valeurs est différentes
+       $saisies_modifiees = array_udiff(array_diff_key($saisies_nouvelles, $saisies_ajoutees), $saisies_restantes, 'saisies_comparer_rappel');
+       #$saisies_modifiees = array_udiff($saisies_nouvelles, $saisies_restantes, 'saisies_comparer_rappel');
+       // Et enfin les saisies qui ont le même nom et la même valeur
+       $saisies_identiques = array_diff_key($saisies_restantes, $saisies_modifiees);
+       
+       return array(
+               'supprimees' => $saisies_supprimees,
+               'ajoutees' => $saisies_ajoutees,
+               'modifiees' => $saisies_modifiees,
+               'identiques' => $saisies_identiques
+       );
+}
+
+/*
+ * Compare deux saisies et indique si elles sont égales ou pas
+ */
+function saisies_comparer_rappel($a, $b){
+       if ($a === $b) return 0;
+       else return 1;
+}
+
+/*
+ * Compare deux tableaux de saisies pour connaitre les différences
+ * en s'appuyant sur les identifiants de saisies
+ *
+ * @see saisies_comparer()
+ * @param array $saisies_anciennes Un tableau décrivant des saisies
+ * @param array $saisies_nouvelles Un autre tableau décrivant des saisies
+ * @param bool $avec_conteneur Indique si on veut prendre en compte dans la comparaison les conteneurs comme les fieldsets
+ * @return array Retourne le tableau des saisies supprimées, ajoutées et modifiées
+ */
+function saisies_comparer_par_identifiant($saisies_anciennes, $saisies_nouvelles, $avec_conteneur=true) {
+       return saisies_comparer($saisies_anciennes, $saisies_nouvelles, $avec_conteneur, $tri = 'identifiant');
+}
+
+/*
+ * Liste toutes les saisies configurables (ayant une description)
+ *
+ * @return array Un tableau listant des saisies et leurs options
+ */
+function saisies_lister_disponibles(){
+       static $saisies = null;
+       
+       if (is_null($saisies)){
+               $saisies = array();
+               $liste = find_all_in_path('saisies/', '.+[.]yaml$');
+               
+               if (count($liste)){
+                       foreach ($liste as $fichier=>$chemin){
+                               $type_saisie = preg_replace(',[.]yaml$,i', '', $fichier);
+                               $dossier = str_replace($fichier, '', $chemin);
+                               // On ne garde que les saisies qui ont bien le HTML avec !
+                               if (file_exists("$dossier$type_saisie.html")
+                                       and (
+                                               is_array($saisie = saisies_charger_infos($type_saisie))
+                                       )
+                               ){
+                                       $saisies[$type_saisie] = $saisie;
+                               }
+                       }
+               }
+       }
+       
+       return $saisies;
+}
+
+/*
+ * Lister les saisies existantes ayant une définition SQL 
+ *
+ * @return array Un tableau listant des saisies et leurs options
+ */
+function saisies_lister_disponibles_sql() {
+       $saisies = array();
+       $saisies_disponibles = saisies_lister_disponibles();
+       foreach ($saisies_disponibles as $type=>$saisie) {
+               if (isset($saisie['defaut']['options']['sql']) and $saisie['defaut']['options']['sql']) {
+                       $saisies[$type] = $saisie;
+               }
+       }
+       return $saisies;
+}
+
+/*
+ * Charger les informations contenues dans le YAML d'une saisie
+ *
+ * @param string $type_saisie Le type de la saisie
+ * @return array Un tableau contenant le YAML décodé
+ */
+function saisies_charger_infos($type_saisie){
+       if(defined('_DIR_PLUGIN_YAML')){
+               include_spip('inc/yaml');
+               $fichier = find_in_path("saisies/$type_saisie.yaml");
+               $saisie = yaml_decode_file($fichier);
+               if (is_array($saisie)){
+                       $saisie['titre'] = (isset($saisie['titre']) AND $saisie['titre'])
+                               ? _T_ou_typo($saisie['titre']) : $type_saisie;
+                       $saisie['description'] = (isset($saisie['description']) AND $saisie['description'])
+                               ? _T_ou_typo($saisie['description']) : '';
+                       $saisie['icone'] = (isset($saisie['icone']) AND $saisie['icone'])
+                               ? find_in_path($saisie['icone']) : '';
+               }
+       }else
+               $saisie = array();
+       return $saisie;
+}
+
+/*
+ * Quelles sont les saisies qui se débrouillent toutes seules, sans le _base commun
+ *
+ * @return array Retourne un tableau contenant les types de saisies qui ne doivent pas utiliser le _base.html commun
+ */
+function saisies_autonomes(){
+       $saisies_autonomes = pipeline(
+               'saisies_autonomes',
+               array(
+                       'fieldset',
+                       'hidden',
+                       'destinataires', 
+                       'explication'
+               )
+       );
+       
+       return $saisies_autonomes;
+}
+
+?>
diff --git a/www/plugins/saisies/inc/saisies_manipuler.php b/www/plugins/saisies/inc/saisies_manipuler.php
new file mode 100644 (file)
index 0000000..9ccffda
--- /dev/null
@@ -0,0 +1,303 @@
+<?php
+
+/**
+ * Gestion de l'affichage des saisies
+ *
+ * @return SPIP\Saisies\Manipuler
+**/
+
+// Sécurité
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+/**
+ * Supprimer une saisie dont on donne l'identifiant, le nom ou le chemin
+ *
+ * @param array $saisies
+ *     Tableau des descriptions de saisies
+ * @param string|array $id_ou_nom_ou_chemin
+ *     L'identifiant unique
+ *     ou le nom de la saisie à supprimer
+ *     ou son chemin sous forme d'une liste de clés
+ * @return array
+ *     Tableau modifié décrivant les saisies
+ */
+function saisies_supprimer($saisies, $id_ou_nom_ou_chemin){
+       // Si la saisie n'existe pas, on ne fait rien
+       if ($chemin = saisies_chercher($saisies, $id_ou_nom_ou_chemin, true)){
+               // La position finale de la saisie
+               $position = array_pop($chemin);
+       
+               // On va chercher le parent par référence pour pouvoir le modifier
+               $parent =& $saisies;
+               foreach($chemin as $cle){
+                       $parent =& $parent[$cle];
+               }
+               
+               // On supprime et réordonne
+               unset($parent[$position]);
+               $parent = array_values($parent);
+       }
+       
+       return $saisies;
+}
+
+/**
+ * Insère une saisie à une position donnée
+ *
+ * @param array $saisies
+ *     Tableau des descriptions de saisies
+ * @param array $saisie
+ *     Description de la saisie à insérer
+ * @param array $chemin
+ *     Position complète où insérer la saisie.
+ *     En absence, insère la saisie à la fin.
+ * @return array
+ *     Tableau des saisies complété de la saisie insérée
+ */
+function saisies_inserer($saisies, $saisie, $chemin=array()){
+       // On vérifie quand même que ce qu'on veut insérer est correct
+       if ($saisie['saisie'] and $saisie['options']['nom']){
+               // ajouter un identifiant
+               $saisie = saisie_identifier($saisie);
+               
+               // Par défaut le parent c'est la racine
+               $parent =& $saisies;
+               // S'il n'y a pas de position, on va insérer à la fin du formulaire
+               if (!$chemin){
+                       $position = count($parent);
+               }
+               elseif (is_array($chemin)){
+                       $position = array_pop($chemin);
+                       foreach ($chemin as $cle){
+                               // Si la clé est un conteneur de saisies "saisies" et qu'elle n'existe pas encore, on la crée
+                               if ($cle == 'saisies' and !isset($parent[$cle]))
+                                       $parent[$cle] = array();
+                               $parent =& $parent[$cle];
+                       }
+                       // On vérifie maintenant que la position est cohérente avec le parent
+                       if ($position < 0) $position = 0;
+                       elseif ($position > count($parent)) $position = count($parent);
+               }
+               // Et enfin on insère
+               array_splice($parent, $position, 0, array($saisie));
+       }
+       
+       return $saisies;
+}
+
+/*
+ * Duplique une saisie (ou groupe de saisies)
+ * en placant la copie à la suite de la saisie d'origine.
+ * Modifie automatiquement les identifiants des saisies
+ *
+ * @param array $saisies Un tableau décrivant les saisies
+ * @param unknown_type $id_ou_nom_ou_chemin L'identifiant unique ou le nom ou le chemin de la saisie a dupliquer
+ * @return array Retourne le tableau modifié des saisies
+ */
+function saisies_dupliquer($saisies, $id_ou_nom_ou_chemin){
+       // On récupère le contenu de la saisie à déplacer
+       $saisie = saisies_chercher($saisies, $id_ou_nom_ou_chemin);
+       if ($saisie) {
+               list($clone) = saisies_transformer_noms_auto($saisies, array($saisie));
+               // insertion apres quoi ?
+               $chemin_validation = saisies_chercher($saisies, $id_ou_nom_ou_chemin, true);
+               // 1 de plus pour mettre APRES le champ trouve
+               $chemin_validation[count($chemin_validation)-1]++;
+               // On ajoute "copie" après le label du champs
+               $clone['options']['label'] .= ' '._T('saisies:construire_action_dupliquer_copie');
+
+               // Création de nouveau identifiants pour le clone
+               $clone = saisie_identifier($clone, true);
+               
+               $saisies = saisies_inserer($saisies, $clone, $chemin_validation);
+       }
+
+       return $saisies;
+}
+
+/*
+ * Déplace une saisie existante autre part
+ *
+ * @param array $saisies Un tableau décrivant les saisies
+ * @param unknown_type $id_ou_nom_ou_chemin L'identifiant unique ou le nom ou le chemin de la saisie à déplacer
+ * @param string $ou Le nom de la saisie devant laquelle on déplacera OU le nom d'un conteneur entre crochets [conteneur]
+ * @return array Retourne le tableau modifié des saisies
+ */
+function saisies_deplacer($saisies, $id_ou_nom_ou_chemin, $ou){
+       // On récupère le contenu de la saisie à déplacer
+       $saisie = saisies_chercher($saisies, $id_ou_nom_ou_chemin);
+
+       // Si on l'a bien trouvé
+       if ($saisie){
+               // On cherche l'endroit où la déplacer
+               // Si $ou est vide, c'est à la fin de la racine
+               if (!$ou){
+                       $saisies = saisies_supprimer($saisies, $id_ou_nom_ou_chemin);
+                       $chemin = array(count($saisies));
+               }
+               // Si l'endroit est entre crochet, c'est un conteneur
+               elseif (preg_match('/^\[(@?[\w]*)\]$/', $ou, $match)){
+                       $parent = $match[1];
+                       // Si dans les crochets il n'y a rien, on met à la fin du formulaire
+                       if (!$parent){
+                               $saisies = saisies_supprimer($saisies, $id_ou_nom_ou_chemin);
+                               $chemin = array(count($saisies));
+                       }
+                       // Sinon on vérifie que ce conteneur existe
+                       elseif (saisies_chercher($saisies, $parent, true)){
+                               // S'il existe on supprime la saisie et on recherche la nouvelle position
+                               $saisies = saisies_supprimer($saisies, $id_ou_nom_ou_chemin);
+                               $parent = saisies_chercher($saisies, $parent, true);
+                               $chemin = array_merge($parent, array('saisies', 1000000));
+                       }
+                       else
+                               $chemin = false;
+               }
+               // Sinon ça sera devant un champ
+               else{
+                       // On vérifie que le champ existe
+                       if (saisies_chercher($saisies, $ou, true)){
+                               // S'il existe on supprime la saisie
+                               $saisies = saisies_supprimer($saisies, $id_ou_nom_ou_chemin);
+                               // Et on recherche la nouvelle position qui n'est plus forcément la même maintenant qu'on a supprimé une saisie
+                               $chemin = saisies_chercher($saisies, $ou, true);
+                       }
+                       else
+                               $chemin = false;
+               }
+               
+               // Si seulement on a bien trouvé un nouvel endroit où la placer, alors on déplace
+               if ($chemin)
+                       $saisies = saisies_inserer($saisies, $saisie, $chemin);
+       }
+       
+       return $saisies;
+}
+
+/*
+ * Modifie une saisie
+ *
+ * @param array $saisies Un tableau décrivant les saisies
+ * @param unknown_type $id_ou_nom_ou_chemin L'identifiant unique ou le nom ou le chemin de la saisie à modifier
+ * @param array $modifs Le tableau des modifications à apporter à la saisie
+ * @return Retourne le tableau décrivant les saisies, mais modifié
+ */
+function saisies_modifier($saisies, $id_ou_nom_ou_chemin, $modifs){
+       $chemin = saisies_chercher($saisies, $id_ou_nom_ou_chemin, true);
+       $position = array_pop($chemin);
+       $parent =& $saisies;
+       foreach ($chemin as $cle){
+               $parent =& $parent[$cle];
+       }
+       
+       // On récupère le type tel quel
+       $modifs['saisie'] = $parent[$position]['saisie'];
+       // On récupère le nom s'il n'y est pas
+       if (!isset($modifs['options']['nom'])){
+               $modifs['options']['nom'] = $parent[$position]['options']['nom'];
+       }
+       // On récupère les enfants tels quels s'il n'y a pas des enfants dans la modif
+       if (
+               !isset($modifs['saisies'])
+               and isset($parent[$position]['saisies'])
+               and is_array($parent[$position]['saisies'])
+       ){
+               $modifs['saisies'] = $parent[$position]['saisies'];
+       }
+
+       // Si une option 'nouveau_type_saisie' est donnee, c'est que l'on souhaite
+       // peut être changer le type de saisie !
+       if (isset($modifs['options']['nouveau_type_saisie']) and $type = $modifs['options']['nouveau_type_saisie']) {
+               $modifs['saisie'] = $type;
+               unset($modifs['options']['nouveau_type_saisie']);
+       }
+                       
+       // On remplace tout
+       $parent[$position] = $modifs;
+       
+       // Cette méthode ne marche pas trop
+       //$parent[$position] = array_replace_recursive($parent[$position], $modifs);
+       
+       return $saisies;
+}
+
+/**
+ * Transforme tous les noms du formulaire avec un preg_replace
+ *
+ * @param array $saisies
+ *     Un tableau décrivant les saisies
+ * @param string $masque
+ *     Ce que l'on doit chercher dans le nom
+ * @param string $remplacement
+ *     Ce par quoi on doit remplacer
+ * @return array
+ *     Retourne le tableau modifié des saisies
+ */
+function saisies_transformer_noms($saisies, $masque, $remplacement){
+       if (is_array($saisies)){
+               foreach ($saisies as $cle => $saisie){
+                       $saisies[$cle]['options']['nom'] = preg_replace($masque, $remplacement, $saisie['options']['nom']);
+                       if (isset($saisie['saisies']) and is_array($saisie['saisies'])) {
+                               $saisies[$cle]['saisies'] = saisies_transformer_noms($saisie['saisies'], $masque, $remplacement);
+                       }
+               }
+       }
+       
+       return $saisies;
+}
+
+/**
+ * Transforme les noms d'une liste de saisies pour qu'ils soient
+ * uniques dans le formulaire donné.
+ *
+ * @param array $formulaire
+ *     Le formulaire à analyser 
+ * @param array $saisies
+ *     Un tableau décrivant les saisies.
+ * @return array
+ *     Retourne le tableau modifié des saisies
+ */
+function saisies_transformer_noms_auto($formulaire, $saisies){
+
+       if (is_array($saisies)){
+               foreach ($saisies as $cle => $saisie){
+                       $saisies[$cle]['options']['nom'] = saisies_generer_nom($formulaire, $saisie['saisie']);
+                       // il faut prendre en compte dans $formulaire les saisies modifiees
+                       // sinon on aurait potentiellement 2 champs successifs avec le meme nom.
+                       // on n'ajoute pas les saisies dont les noms ne sont pas encore calculees.
+                       $new = $saisies[$cle];
+                       unset($new['saisies']);
+                       $formulaire[] = $new;
+                       
+                       if (is_array($saisie['saisies']))
+                               $saisies[$cle]['saisies'] = saisies_transformer_noms_auto($formulaire, $saisie['saisies']);
+               }
+       }
+
+       return $saisies;
+}
+
+/*
+ * Insère du HTML au début ou à la fin d'une saisie
+ *
+ * @param array $saisie La description d'une seule saisie
+ * @param string $insertion Du code HTML à insérer dans la saisie 
+ * @param string $ou L'endroit où insérer le HTML : "debut" ou "fin"
+ * @return array Retourne la description de la saisie modifiée
+ */
+function saisies_inserer_html($saisie, $insertion, $ou='fin'){
+       if (!in_array($ou, array('debut', 'fin')))
+               $ou = 'fin';
+       
+       if ($ou == 'debut') {
+               $saisie['options']['inserer_debut'] =
+                       $insertion . (isset($saisie['options']['inserer_debut']) ? $saisie['options']['inserer_debut'] : '');
+       } elseif ($ou == 'fin') {
+               $saisie['options']['inserer_fin'] =
+                       (isset($saisie['options']['inserer_fin']) ? $saisie['options']['inserer_fin'] : '') . $insertion;
+       }
+       
+       return $saisie;
+}
+
+?>
diff --git a/www/plugins/saisies/inclure/configurer_saisie.html b/www/plugins/saisies/inclure/configurer_saisie.html
new file mode 100644 (file)
index 0000000..70991bb
--- /dev/null
@@ -0,0 +1,19 @@
+[(#REM) 
+  
+  ### /!\ necessite le plugin YAML ###
+  
+  Genere l'intérieur d'un formulaire permettant de configurer une saisie.
+  
+  Par defaut, ne permet pas de configurer le "name" de la saisie car dans la
+  plupart des cas c'est une valeur qui sera automatique. On utilise le parametre
+  "avec_nom=oui" pour forcer le champ.
+  
+  Exemples d'appels :
+    #INCLURE{fond=inclure/configurer_saisie, env, saisie=input}
+    #INCLURE{fond=inclure/configurer_saisie, env, saisie=input, avec_nom=oui}
+
+]
+
+[(#SET{config, #ENV{saisie}|construire_configuration_saisie{#ENV{avec_nom}}})]
+
+#GENERER_SAISIES{#GET{config}}
diff --git a/www/plugins/saisies/inclure/configurer_saisie_fonctions.php b/www/plugins/saisies/inclure/configurer_saisie_fonctions.php
new file mode 100644 (file)
index 0000000..5285b93
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+// Sécurité
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+function construire_configuration_saisie($saisie, $avec_nom='non'){
+       include_spip('inc/yaml');
+       $configuration_saisie = yaml_decode_file(find_in_path('saisies/'.$saisie.'.yaml'));
+       
+       if (is_array($configuration_saisie)){
+               $configuration_saisie = $configuration_saisie['options'];
+               // On ne met le premier champ permettant de configurer le "name" seulement si on le demande explicitement
+               if ($avec_nom == 'oui')
+                       array_unshift($configuration_saisie[0]['contenu'],
+                               array(
+                                       'saisie' => 'input',
+                                       'options' => array(
+                                               'nom' => 'nom',
+                                               'label' => '<:saisies:option_nom_label:>',
+                                               'explication' => '<:saisies:option_nom_explication:>',
+                                               'obligatoire' => 'oui'
+                                       ),
+                                       'verifier' => array(
+                                               'type' => 'regex',
+                                               'options' => array(
+                                                       'modele' => '/^[\w]+$/'
+                                               )
+                                       )
+                               )
+                       );
+       }
+       else
+               $configuration_saisie = array();
+       
+       return $configuration_saisie;
+}
+
+?>
diff --git a/www/plugins/saisies/inclure/generer_saisies.html b/www/plugins/saisies/inclure/generer_saisies.html
new file mode 100644 (file)
index 0000000..9acd758
--- /dev/null
@@ -0,0 +1,46 @@
+[(#REM) 
+  
+  ### /!\ boucle POUR (spip Bonux) ###
+  
+  Génère le contenu (l'intérieur) d'un formulaire, à partir d'une description dans un tableau PHP.
+  Le tableau doit être de la forme suivante :
+  
+  // Chaque ligne est elle-même un tableau
+  array(
+       // Ligne de type "explication"
+       array(
+               'explication' => 'Ceci est un bloc d'explication général.'
+       ),
+       // Ligne classique, cad un champ de formulaire
+       array(
+               'saisie' => 'input',
+               'options => array(
+                       'nom' => 'mon_champ',
+                       'label' => 'Un joli titre',
+                       'obligatoire' => 'oui'
+               )
+       ),
+       // Ligne contenant un fieldset
+       array(
+               'groupe' => 'Ceci est le titre du groupe de champs (fieldset)',
+               'css' => 'eventuelles classes css',
+               'contenu' => array(
+                       // On recommence ici suivant le même formalisme que le tableau général.
+               )
+       )
+  )
+  
+  
+  Exemples d'appels :
+    # INCLURE{fond=inclure/generer_saisies, env, saisies=#ENV{tableau}}
+
+]
+
+<BOUCLE_contenu(POUR){tableau #ENV{saisies}}>
+[(#VAL{saisie}|array_key_exists{#VALEUR}|oui)
+[(#VALEUR**|saisies_generer_html{#ENV{_env}|sinon{#ENV**|unserialize}})]
+]
+</BOUCLE_contenu>
+
+[(#REM) Ajout du script js pour les options si des saisies ]
+[(#ENV{from_fieldset}|non|et{#ENV{saisies}|saisies_afficher_si|ou{#ENV{saisies}|saisies_afficher_si_remplissage}}) #INCLURE{fond=inclure/js_afficher_si,env}]
diff --git a/www/plugins/saisies/inclure/js_afficher_si.html b/www/plugins/saisies/inclure/js_afficher_si.html
new file mode 100644 (file)
index 0000000..6866cbe
--- /dev/null
@@ -0,0 +1,7 @@
+[(#REM) Ajout d'un marqueur unique pour identifier le formulaire]\r
+[(#SET{id_unique,#EVAL{'rand();'}})]\r
+<li id="afficher_si_#GET{id_unique}" style="display:none;" >\r
+<script type="text/javascript">// <![CDATA[\r
+[(#ENV**{saisies}|saisies_generer_js_afficher_si{#GET{id_unique}})]\r
+ //]]></script>\r
+ </li>
\ No newline at end of file
diff --git a/www/plugins/saisies/inclure/saisies_aide.html b/www/plugins/saisies/inclure/saisies_aide.html
new file mode 100644 (file)
index 0000000..c3b6061
--- /dev/null
@@ -0,0 +1,65 @@
+
+Sauter à : <a href="#liste_saisies">Toutes les saisies</a>, <a href="#liste_options_saisies">Toutes les options</a>
+
+<h2 class="h2 spip">Utilisation des options</h2>
+<B_options_tableau>
+<div style="overflow:auto;">
+<table class="spip">
+<thead>
+       <tr class="first_row">
+               <th>Options \ Saisies</th>
+               <BOUCLE_saisies_tableau_th(POUR){tableau #ENV{saisies}}>
+               <th><a href="#saisie_#CLE">#CLE</a></th>
+               </BOUCLE_saisies_tableau_th>
+       </tr>
+</thead>
+<tbody>
+       <BOUCLE_options_tableau(DATA){source tableau, #ENV{options}}>
+       <tr class="tr_liste [(#COMPTEUR_BOUCLE|alterner{row_odd,row_even})]">
+               <th><a href="#option_#CLE">#CLE</a></th>
+               <BOUCLE_saisies_tableau(DATA){source tableau, #ENV{saisies}}>
+               <td>[(#CLE|in_array{#_options_tableau:UTILISEE_PAR}|?{'X','-'})]</td>
+               </BOUCLE_saisies_tableau>
+       </tr>
+       </BOUCLE_options_tableau>
+</tbody>
+</table>
+</div>
+</B_options_tableau>
+
+<h2 class="h2 spip" id="liste_saisies">Toutes les saisies</h2>
+<BOUCLE_saisies(DATA){source tableau, #ENV{saisies}}>
+<h3 class="h3 spip" id="saisie_#CLE">#TITRE (#CLE)</h3>
+<p class="description">
+       <strong>Description :</strong> #DESCRIPTION
+</p>
+<p class="options">
+       <strong>Options :</strong>
+       <BOUCLE_options_saisie(DATA){source tableau, #OPTIONS}{", "}><a href="#option_#CLE">#CLE</a></BOUCLE_options_saisie>
+</p>
+</BOUCLE_saisies>
+
+<h2 class="h2 spip" id="liste_options_saisies">Toutes les options</h2>
+<BOUCLE_options(DATA){source tableau, #ENV{options}}>
+[(#SET{label, #LABEL|sinon{#LABEL_CASE}})]
+<h3 class="h3 spip" id="option_#CLE">[(#GET{label}|?{#GET{label} [ ((#CLE))], #CLE})]</h3>
+[<p class="description">
+       <strong>Description :</strong> (#EXPLICATION|sinon{#LABEL|?{#LABEL_CASE}})
+</p>]
+<B_utilisee_par>
+<p class="utilisee_par">
+       <strong>Utilisée par :</strong>
+       <BOUCLE_utilisee_par(DATA){source tableau, #UTILISEE_PAR}{", "}><a href="#saisie_#VALEUR">#VALEUR</a></BOUCLE_utilisee_par>
+</p>
+</B_utilisee_par>
+<B_choix>
+<p class="choix_possibles">
+       <strong>Choix possibles :</strong>
+       <ul class="spip">
+               <BOUCLE_choix(DATA){source tableau, #DATAS}>
+               <li>"#CLE" : #VALEUR</li>
+               </BOUCLE_choix>
+       </ul>
+</p>
+</B_choix>
+</BOUCLE_options>
diff --git a/www/plugins/saisies/inclure/voir_saisies.html b/www/plugins/saisies/inclure/voir_saisies.html
new file mode 100644 (file)
index 0000000..7a77eee
--- /dev/null
@@ -0,0 +1,7 @@
+[(#REM) S'il y a des options afficher_si, il faut vérifier que les conditions sont remplies ]
+[(#SET{saisies,#ENV{saisies}|saisies_verifier_afficher_si{#ENV**|unserialize}})]
+<BOUCLE_saisies(POUR){tableau #GET{saisies}}>
+[(#VAL{saisie}|array_key_exists{#VALEUR}|oui)
+       [(#VALEUR|saisies_generer_vue{#ENV{_env}|sinon{#ENV**|unserialize}})]
+]
+</BOUCLE_saisies>
diff --git a/www/plugins/saisies/javascript/saisies.js b/www/plugins/saisies/javascript/saisies.js
new file mode 100644 (file)
index 0000000..9cdd288
--- /dev/null
@@ -0,0 +1,31 @@
+jQuery(function(){
+       saisies_fieldset_pliable();
+       onAjaxLoad(saisies_fieldset_pliable);
+});
+
+function saisies_fieldset_pliable(){
+       // On cherche les groupes de champs pliables
+       jQuery('li.fieldset.pliable')
+               .each(function(){
+                       var li = jQuery(this);
+                       var ul = jQuery(this).find('> fieldset > ul');
+                       var legend = jQuery(this).find('> fieldset > .legend');
+                       
+                       // S'il est déjà plié on cache le contenu
+                       if (li.is('.plie'))
+                               ul.hide();
+                       
+                       // Ensuite on ajoute une action sur le titre
+                       legend
+                               .unbind('click')
+                               .click(
+                                       function(){
+                                               li.toggleClass('plie');
+                                               if (ul.is(':hidden'))
+                                                       ul.show();
+                                               else
+                                                       ul.hide();
+                                       }
+                               );
+               });
+};
diff --git a/www/plugins/saisies/lang/paquet-saisies.xml b/www/plugins/saisies/lang/paquet-saisies.xml
new file mode 100644 (file)
index 0000000..0f911f7
--- /dev/null
@@ -0,0 +1,27 @@
+<traduction module="paquet-saisies" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/saisies/trunk/lang/" reference="fr">
+       <langue code="ar" url="http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=ar" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="George" lien="http://trad.spip.net/auteur/جورج-قندلفت" />
+       </langue>
+       <langue code="de" url="http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=de" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="klaus++" lien="http://trad.spip.net/auteur/klaus" />
+       </langue>
+       <langue code="en" url="http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=en" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       </langue>
+       <langue code="es" url="http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=es" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Raquel S. Bujaldón" lien="http://trad.spip.net/auteur/raquel-s-bujaldon" />
+       </langue>
+       <langue code="fr" url="http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=fr" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       </langue>
+       <langue code="fr_tu" url="http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=fr_tu" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Cerf" lien="http://trad.spip.net/auteur/cerf" />
+       </langue>
+       <langue code="nl" url="http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=nl" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
+       </langue>
+       <langue code="ru" url="http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=ru" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Serge Markitanenko" lien="http://trad.spip.net/auteur/serge-markitanenko" />
+       </langue>
+       <langue code="sk" url="http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=sk" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="jaro" lien="http://trad.spip.net/auteur/jaro" />
+       </langue>
+</traduction>
diff --git a/www/plugins/saisies/lang/paquet-saisies_ar.php b/www/plugins/saisies/lang/paquet-saisies_ar.php
new file mode 100644 (file)
index 0000000..bab403a
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=ar
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // S
+       'saisies_description' => 'يسهّل هذا الملحق إنشاء حقول للاستمارات من خلال توفيره علامة SAISIE#. تتوافق علامات HTML الناتجة مع تسمية الاستمارات 
+               المقترحة في SPIP بإصداراته الأحدث من ٢.٠ ومع ملحق الاعدادات CFG.',
+       'saisies_nom' => 'إدخال للاستمارات',
+       'saisies_slogan' => 'إنشاء حقول استمارات بسهولة',
+       'saisies_titre' => 'إدخال للاستمارات'
+);
+
+?>
diff --git a/www/plugins/saisies/lang/paquet-saisies_de.php b/www/plugins/saisies/lang/paquet-saisies_de.php
new file mode 100644 (file)
index 0000000..4fb90f5
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=de
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // S
+       'saisies_description' => 'Dieses Plugin erleichtert die Erstellung von Eingabefeldern für Formulare und bietet einen Tag #SAISIE. Das erzeugte HTML ist mit der Nomenklatur der von SPIP > 2.0 und dem Plugin CFG kompatibel.',
+       'saisies_nom' => 'Eingabefelder für Formulare',
+       'saisies_slogan' => 'Unkompliziertes Erstellen von Eingabefeldern für Formulare.',
+       'saisies_titre' => 'Eingabefelder für Formulare'
+);
+
+?>
diff --git a/www/plugins/saisies/lang/paquet-saisies_en.php b/www/plugins/saisies/lang/paquet-saisies_en.php
new file mode 100644 (file)
index 0000000..ac252f3
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=en
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // S
+       'saisies_description' => 'This plugin makes it easier to write form fields by providing a #SAISIE tag. 
+               The generated HTML is compatible with the classification of forms
+               proposed by SPIP > 2.0 and with the configuration plugin CFG.',
+       'saisies_nom' => 'Entries for forms',
+       'saisies_slogan' => 'Create easily forms fields.',
+       'saisies_titre' => 'Entries for forms'
+);
+
+?>
diff --git a/www/plugins/saisies/lang/paquet-saisies_es.php b/www/plugins/saisies/lang/paquet-saisies_es.php
new file mode 100644 (file)
index 0000000..eb06ed8
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=es
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // S
+       'saisies_description' => 'Este plugin permite facilitar la redacción de campos de formularios proponiendo una etiqueta #SAISIE. El HTML generado es compatible con la nomenclatura de los formularios propuestos por SPIP > 2.0 y con el plugin de configuración CFG.',
+       'saisies_nom' => 'Entradas para formularios',
+       'saisies_slogan' => 'Escribir fácilmente los campos de formularios.',
+       'saisies_titre' => 'Entradas para formularios'
+);
+
+?>
diff --git a/www/plugins/saisies/lang/paquet-saisies_fr.php b/www/plugins/saisies/lang/paquet-saisies_fr.php
new file mode 100644 (file)
index 0000000..3485022
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// Fichier source, a modifier dans svn://zone.spip.org/spip-zone/_plugins_/saisies/trunk/lang/
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // S
+       'saisies_description' => 'Ce plugin permet de faciliter l’écriture de champs de formulaires en proposant une
+               balise #SAISIE. Le HTML généré est compatible avec la nomenclature des formulaires
+               proposée par SPIP > 2.0 et avec le plugin de configuration CFG.',
+       'saisies_nom' => 'Saisies pour formulaires',
+       'saisies_slogan' => 'Écrire facilement des champs de formulaires.',
+       'saisies_titre' => 'Saisies pour formulaires'
+);
+
+?>
diff --git a/www/plugins/saisies/lang/paquet-saisies_fr_tu.php b/www/plugins/saisies/lang/paquet-saisies_fr_tu.php
new file mode 100644 (file)
index 0000000..f0dc892
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=fr_tu
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // S
+       'saisies_description' => 'Ce plugin permet de faciliter l’écriture de champs de formulaires en proposant une
+               balise #SAISIE. Le HTML généré est compatible avec la nomenclature des formulaires
+               proposée par SPIP > 2.0 et avec le plugin de configuration CFG.',
+       'saisies_nom' => 'Saisies pour formulaires',
+       'saisies_slogan' => 'Écrire facilement des champs de formulaires.',
+       'saisies_titre' => 'Saisies pour formulaires'
+);
+
+?>
diff --git a/www/plugins/saisies/lang/paquet-saisies_nl.php b/www/plugins/saisies/lang/paquet-saisies_nl.php
new file mode 100644 (file)
index 0000000..13b832b
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=nl
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // S
+       'saisies_description' => 'Deze plugin vereenvoudigt het maken van formuliervelden danzij een code #SAISIE. De gegenereerde HTML is compatibel met die van SPIP formulieren vanaf versie 2.0 en met de configuratie plugin CFG.',
+       'saisies_nom' => 'Saisies (Invoer) voor formulieren',
+       'saisies_slogan' => 'Eenvoudig formuliervelden maken.',
+       'saisies_titre' => 'Invoer voor formulieren'
+);
+
+?>
diff --git a/www/plugins/saisies/lang/paquet-saisies_ru.php b/www/plugins/saisies/lang/paquet-saisies_ru.php
new file mode 100644 (file)
index 0000000..1fc60c0
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=ru
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // S
+       'saisies_description' => 'Этот плагин облегчает работу по созданию форм. Вам предоставляется возможность создавать поля (input,textarea) в форме при помощи тега #SAISIE. Полученная форма полностью совместима со стандартом  SPIP 2.0+ и c плагином CFG.',
+       'saisies_nom' => 'Поля для форм (saises)',
+       'saisies_slogan' => 'Упрощение работы по созданию форм',
+       'saisies_titre' => 'Поля для форм (saises)'
+);
+
+?>
diff --git a/www/plugins/saisies/lang/paquet-saisies_sk.php b/www/plugins/saisies/lang/paquet-saisies_sk.php
new file mode 100644 (file)
index 0000000..406bad6
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=sk
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // S
+       'saisies_description' => 'Tento zásuvný modul uľahčuje zápis polí formulára ponúknutím tagu #INPUT. Vytvorený kód HTML je kompatibilný s klasifikáciou formulárov, ktorú ponúka SPIP > 2.0 a so zásuvným modulom na konfiguráciu – CFG.',
+       'saisies_nom' => 'Vstupy pre formuláre',
+       'saisies_slogan' => 'Jednoduchý zápis polí formulárov.',
+       'saisies_titre' => 'Vstupy pre formuláre'
+);
+
+?>
diff --git a/www/plugins/saisies/lang/saisies.xml b/www/plugins/saisies/lang/saisies.xml
new file mode 100644 (file)
index 0000000..4059c75
--- /dev/null
@@ -0,0 +1,39 @@
+<traduction module="saisies" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/saisies/trunk/lang/" reference="fr">
+       <langue code="ca" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=ca" total="162" traduits="85" relire="0" modifs="9" nouveaux="68" pourcent="52.47">
+       </langue>
+       <langue code="de" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=de" total="162" traduits="142" relire="0" modifs="3" nouveaux="17" pourcent="87.65">
+               <traducteur nom="klaus++" lien="http://trad.spip.net/auteur/klaus" />
+       </langue>
+       <langue code="en" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=en" total="162" traduits="162" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
+               <traducteur nom="kent1" lien="http://trad.spip.net/auteur/kent1" />
+               <traducteur nom="Paolo" lien="http://trad.spip.net/auteur/paolo" />
+       </langue>
+       <langue code="es" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=es" total="162" traduits="162" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="kent1" lien="http://trad.spip.net/auteur/kent1" />
+               <traducteur nom="Raquel S. Bujaldón" lien="http://trad.spip.net/auteur/raquel-s-bujaldon" />
+               <traducteur nom="severo" lien="http://trad.spip.net/auteur/severo" />
+       </langue>
+       <langue code="fa" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=fa" total="162" traduits="107" relire="0" modifs="3" nouveaux="52" pourcent="66.05">
+               <traducteur nom="Davood Hossein" lien="http://trad.spip.net/auteur/davood-hossein" />
+       </langue>
+       <langue code="fr" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=fr" total="162" traduits="162" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       </langue>
+       <langue code="fr_tu" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=fr_tu" total="162" traduits="159" relire="0" modifs="0" nouveaux="3" pourcent="98.15">
+               <traducteur nom="beatnick" lien="http://trad.spip.net/auteur/beatnick" />
+               <traducteur nom="Cerf" lien="http://trad.spip.net/auteur/cerf" />
+       </langue>
+       <langue code="it" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=it" total="162" traduits="139" relire="0" modifs="3" nouveaux="20" pourcent="85.80">
+       </langue>
+       <langue code="nl" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=nl" total="162" traduits="162" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
+               <traducteur nom="mpossoz" lien="http://trad.spip.net/auteur/mpossoz" />
+       </langue>
+       <langue code="ru" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=ru" total="162" traduits="109" relire="0" modifs="36" nouveaux="17" pourcent="67.28">
+               <traducteur nom="nazar" lien="http://trad.spip.net/auteur/nazar" />
+               <traducteur nom="Serge Markitanenko" lien="http://trad.spip.net/auteur/serge-markitanenko" />
+       </langue>
+       <langue code="sk" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=sk" total="162" traduits="162" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="jaro" lien="http://trad.spip.net/auteur/jaro" />
+       </langue>
+</traduction>
diff --git a/www/plugins/saisies/lang/saisies_ca.php b/www/plugins/saisies/lang/saisies_ca.php
new file mode 100644 (file)
index 0000000..829f1d5
--- /dev/null
@@ -0,0 +1,119 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=ca
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // B
+       'bouton_parcourir_docs_article' => 'Fullejar l’article',
+       'bouton_parcourir_docs_breve' => 'Fullejar la breu',
+       'bouton_parcourir_docs_rubrique' => 'Fullejar la secció',
+       'bouton_parcourir_mediatheque' => 'Fullejar la mediateca',
+
+       // L
+       'label_annee' => 'Any',
+       'label_jour' => 'Dia',
+       'label_mois' => 'Mes',
+
+       // O
+       'option_attention_explication' => 'Un missatge més important que l’explicació.',
+       'option_attention_label' => 'Advertència',
+       'option_cacher_option_intro_label' => 'Amagar la primera elecció buida',
+       'option_choix_destinataires_explication' => 'Un o diversos autors, d’entre els quals hi ha l’usuari, podrà fer la seva tria. Si no hi ha res seleccionat, és l’autor que ha instal·lat el lloc el que serà escollit.',
+       'option_choix_destinataires_label' => 'Possibles destinataris',
+       'option_class_label' => 'Classes CSS suplementàries',
+       'option_cols_explication' => 'Amplada del bloc en número de caràcters. Aquesta opció no s’aplica sempre ja que els estils CSS del vostre lloc el poden anul·lar.', # MODIF
+       'option_cols_label' => 'Amplada',
+       'option_datas_explication' => 'Heu d’especificar una opció per línia en la forma "cle|Label du choix"', # MODIF
+       'option_datas_label' => 'Llista d’eleccions possibles',
+       'option_defaut_label' => 'Valor per defecte',
+       'option_disable_avec_post_explication' => 'Idèntic que l’opció anterior però tanmateix envia el valor dins d’un camps amagat.',
+       'option_disable_avec_post_label' => 'Desactivar però enviar',
+       'option_disable_explication' => 'El camp no pot obtenir el focus.',
+       'option_disable_label' => 'Desactivar el camp',
+       'option_explication_explication' => 'Si és necessari, una frase curta descriu l’objecte del camp.',
+       'option_explication_label' => 'Explicació',
+       'option_groupe_affichage' => 'Visualització',
+       'option_groupe_description' => 'Descripció',
+       'option_groupe_utilisation' => 'Utilització',
+       'option_groupe_validation' => 'Validació',
+       'option_info_obligatoire_explication' => 'Podeu, per defecte, modificar la indicació d’obligació: <i>[Obligatoire]</i>.',
+       'option_info_obligatoire_label' => 'Indicació d’obligació',
+       'option_inserer_barre_choix_edition' => 'barra d’edició completa',
+       'option_inserer_barre_choix_forum' => 'barra dels fòrums',
+       'option_inserer_barre_explication' => 'Insereix una barra d’eines del portaplomes si aquest està activat.',
+       'option_inserer_barre_label' => 'Inserir una barra d’eines',
+       'option_label_case_label' => 'Etiqueta situada al costat de la casella',
+       'option_label_explication' => 'El títol que es mostrarà.',
+       'option_label_label' => 'Etiqueta',
+       'option_maxlength_explication' => 'L’usuari no podrà escriure més caràcters que aquest nombre.',
+       'option_maxlength_label' => 'Número màxim de caràcters',
+       'option_nom_explication' => 'Un nom informàtic que identificarà el camp. Ha de contenir només caràcters alfanumèrics minúsculs o el caràcter "_".', # MODIF
+       'option_nom_label' => 'Nom del camp',
+       'option_obligatoire_label' => 'Camp obligatori',
+       'option_option_intro_label' => 'Etiqueta de la primera elecció buida',
+       'option_pliable_label' => 'Plegable',
+       'option_pliable_label_case' => 'El grup de camps es podrà replegar.',
+       'option_plie_label' => 'Ja plegat',
+       'option_plie_label_case' => 'Si el grup de camps és plegable, ja estarà plegat a la visualització del formulari.',
+       'option_previsualisation_explication' => 'Si el portaplomes està activat, afegit una pestanya per fer una visualització prèvia del text introduït. ',
+       'option_previsualisation_label' => 'Activar la visualització prèvia',
+       'option_readonly_explication' => 'El camp es pot llegir, seleccionar, però no modificar.',
+       'option_readonly_label' => 'Només lectura',
+       'option_rows_explication' => 'Alçada del bloc en número de línies. Aquesta opció no es pot aplicar sempre ja que els estils CSS del vostre lloc el poden anul·lar.',
+       'option_rows_label' => 'Número de línies',
+       'option_size_explication' => 'Amplada del camp en número de caràcters. Aquesta opció no es pot aplicar sempre ja que els estils CSS del vostre lloc el poden anul·lar. ',
+       'option_size_label' => 'Mida del camp',
+       'option_type_choix_plusieurs' => 'Permetrà a l’usuari escollir <strong>diversos</strong> destinataris.', # MODIF
+       'option_type_choix_tous' => 'Posar <strong>tots</strong> aquests autors com a destinataris. L’usuari no tindrà cap tria.',
+       'option_type_choix_un' => 'Permetre a l’usuari escollir <strong>un únic</strong> destinatari.', # MODIF
+       'option_type_explication' => 'En mode "amagat", el contingut del camp no serà visible.',
+       'option_type_label' => 'Tipus del camp',
+       'option_type_password' => 'Amagat', # MODIF
+       'option_type_text' => 'Normal',
+
+       // S
+       'saisie_case_explication' => 'Permet activar o desactivar alguna cosa.',
+       'saisie_case_titre' => 'Casella única',
+       'saisie_checkbox_explication' => 'Permet escollir diverses opcions amb caselles.',
+       'saisie_checkbox_titre' => 'Caselles a marcar', # MODIF
+       'saisie_destinataires_explication' => 'Permet escollir un o diversos destinataris entre els autors seleccionats prèviament.',
+       'saisie_destinataires_titre' => 'Destinataris',
+       'saisie_explication_explication' => 'Un text explicatiu general.',
+       'saisie_explication_titre' => 'Explicació',
+       'saisie_fieldset_explication' => 'Un quadre que podrà englobar diversos camps.',
+       'saisie_fieldset_titre' => 'Grup de camps',
+       'saisie_file_explication' => 'Enviament d’un arxiu ',
+       'saisie_file_titre' => 'Arxiu',
+       'saisie_hidden_explication' => 'Un camp omplert prèviament que l’usuari no podrà veure.',
+       'saisie_hidden_titre' => 'Camp amagat',
+       'saisie_input_explication' => 'Una simple línia de text, que podrà ser visible o estar amagada (contrasenya).',
+       'saisie_input_titre' => 'Línia de text',
+       'saisie_oui_non_explication' => 'Si o no, està clar? :)',
+       'saisie_oui_non_titre' => 'Si o no ',
+       'saisie_radio_defaut_choix1' => 'Un',
+       'saisie_radio_defaut_choix2' => 'Dos',
+       'saisie_radio_defaut_choix3' => 'Tres',
+       'saisie_radio_explication' => 'Permet escollir una opció entre les diverses disponibles.',
+       'saisie_radio_titre' => 'Botons ràdios',
+       'saisie_selection_explication' => 'Escollir una opció en una llista desplegable.', # MODIF
+       'saisie_selection_multiple_explication' => 'Permet escollir diverses opcions amb una llista.',
+       'saisie_selection_multiple_titre' => 'Selecció múltiple',
+       'saisie_selection_titre' => 'Llista desplegable',
+       'saisie_textarea_explication' => 'Un camp de text en diverses línies.',
+       'saisie_textarea_titre' => 'Bloc de text',
+
+       // T
+       'tous_visiteurs' => 'Tots els visitants (fins i tot els no registrats)',
+
+       // V
+       'vue_sans_reponse' => '<i>Sense resposta</i>', # MODIF
+
+       // Z
+       'z' => 'zzz'
+);
+
+?>
diff --git a/www/plugins/saisies/lang/saisies_de.php b/www/plugins/saisies/lang/saisies_de.php
new file mode 100644 (file)
index 0000000..10a80af
--- /dev/null
@@ -0,0 +1,176 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=de
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // B
+       'bouton_parcourir_docs_article' => 'Artikel durchsuchen',
+       'bouton_parcourir_docs_breve' => 'Meldung durchsuchen',
+       'bouton_parcourir_docs_rubrique' => 'Rubrik durchsuchen',
+       'bouton_parcourir_mediatheque' => 'Mediathek durchsuchen',
+
+       // C
+       'construire_action_annuler' => 'Abbrechen',
+       'construire_action_configurer' => 'Konfigurieren',
+       'construire_action_deplacer' => 'Verschieben',
+       'construire_action_dupliquer' => 'Duplizieren',
+       'construire_action_dupliquer_copie' => '(Kopie)',
+       'construire_action_supprimer' => 'Löschen',
+       'construire_ajouter_champ' => 'Feld hinzufügen',
+       'construire_attention_enregistrer' => 'Vergessen Sie nicht, Ihre Änderungen zu speichern.',
+       'construire_attention_modifie' => 'Das untere Formular unterscheidet sich vom ursprünglichen. Sie können es in den Zustand vor den Änderungen zurücksetzen.',
+       'construire_attention_supprime' => 'Ihre Änderungen umfassen das Löschen von Feldern. Bitte bestätigen das Speichern dieser neuen Version des Formulars.',
+       'construire_aucun_champs' => 'Dieses Formular enthält noch keine Felder.',
+       'construire_confirmer_supprimer_champ' => 'Wollen Sie dieses Feld wirklich löschen?',
+       'construire_info_nb_champs_masques' => '@nb@ Feld/er während der Konfiguration der Gruppe ausgeblendet.',
+       'construire_position_explication' => 'Geben Sie an, vor welchem anderen Feld dieses erscheinen soll.',
+       'construire_position_fin_formulaire' => 'Am Ende des Formulars',
+       'construire_position_fin_groupe' => 'Am Ende der Gruppe @groupe@',
+       'construire_position_label' => 'Feldposition',
+       'construire_reinitialiser' => 'Formular neu initialisieren',
+       'construire_reinitialiser_confirmer' => 'Alle Ihre Änderungen werden verlorengehen. Wollen Sie wirklich das ursprüngliche Formular wieder herstellen?',
+       'construire_verifications_aucune' => 'Keine',
+       'construire_verifications_label' => 'Art der Überprüfung',
+
+       // E
+       'erreur_generique' => 'Fehler im folgenden Feld. Bitte überprüfen Sie Ihre Eingabe.',
+       'erreur_option_nom_unique' => 'Dieser Name wird bereits für ein anderes Feld verwendet; er kann in diesem Formular nur einmal verwendet werden.',
+
+       // I
+       'info_configurer_saisies' => 'Testseite der Eingabefelder.',
+
+       // L
+       'label_annee' => 'Jahr',
+       'label_jour' => 'Tag',
+       'label_mois' => 'Monat',
+
+       // O
+       'option_aff_art_interface_explication' => 'Ausschließlich Artikel in der Sprache des Nutzers anzeigen',
+       'option_aff_art_interface_label' => 'Mehrsprachige Anzeige',
+       'option_aff_langue_explication' => 'Sprache des ausgewählten Artikels oder der Rubrik vor dem Titel anzeigen',
+       'option_aff_langue_label' => 'Sprache anzeigen',
+       'option_aff_rub_interface_explication' => 'Ausschließlich Rubriken in der Sprache des Nutzers anzeigen',
+       'option_aff_rub_interface_label' => 'Mehrsprachige Anzeige',
+       'option_attention_explication' => 'Nachricht wichtiger als Meldung',
+       'option_attention_label' => 'Achtung',
+       'option_autocomplete_defaut' => 'Standardeinstellung belassen',
+       'option_autocomplete_explication' => 'Beim Laden der Seite kann Ihr Browser das Feld mit bereits verwendeten Werten vorausfüllen.',
+       'option_autocomplete_label' => 'Feld vorausfüllen',
+       'option_autocomplete_off' => 'Deaktivieren',
+       'option_autocomplete_on' => 'Aktivieren',
+       'option_cacher_option_intro_label' => 'Erste leere Auswahl ausblenden',
+       'option_choix_destinataires_explication' => 'Einer oder mehrere Empfänger, welche der Besucher auswählen kann. Wenn er keine Auswahl trifft, geht die Nachricht an den ersten Administrator (Autor 1).',
+       'option_choix_destinataires_label' => 'Mögliche Empfänger',
+       'option_class_label' => 'Zusätzliche CSS-Klassen',
+       'option_cols_explication' => 'Breite des Blocks in Zeichen. Diese Option kann durch ihre CSS-Stile unwirksam grmacht werden.',
+       'option_cols_label' => 'Breite',
+       'option_datas_explication' => 'Sie müssen in jeder Zeile eine Option im Format "Schlüssel|Bezeichnung" angeben.', # MODIF
+       'option_datas_label' => 'Mögliche Angaben',
+       'option_defaut_label' => 'Standardwert',
+       'option_disable_avec_post_explication' => 'Identisch mit voriger Option, jedoch wird der Wert in ein verstecktes Feld eingefügt.',
+       'option_disable_avec_post_label' => 'Senden trotz Deaktivierung',
+       'option_disable_explication' => 'Das Feld erhält keinen Fokus.',
+       'option_disable_label' => 'Feld deaktivieren',
+       'option_erreur_obligatoire_explication' => 'Sie können eine eigene Fehlermeldung bei nicht ausgefüllten Pflichtfeldern eingeben oder darauf verzichten.',
+       'option_erreur_obligatoire_label' => 'Hinweis Pflichtfeld',
+       'option_explication_explication' => 'Falls erforderlich kurze Beschreibung des Feldobjekts',
+       'option_explication_label' => 'Erläuterung',
+       'option_groupe_affichage' => 'Anzeige',
+       'option_groupe_description' => 'Beschreibung',
+       'option_groupe_utilisation' => 'Verwendung',
+       'option_groupe_validation' => 'Bestätigung',
+       'option_info_obligatoire_explication' => 'Sie können die Standardbezeichnung für Pflichtfelder ändern:<i>[Pflichtfeld]</i>.',
+       'option_info_obligatoire_label' => 'Pflichtfeld-Anzeige',
+       'option_inserer_barre_choix_edition' => 'Vollständige Symbolleiste "Bearbeiten"',
+       'option_inserer_barre_choix_forum' => 'Symbolleiste "Foren"',
+       'option_inserer_barre_explication' => 'Eine Symbolleiste hinzufügen, wenn das Plugin Porte-Plume aktiviert ist.',
+       'option_inserer_barre_label' => 'Symbolleiste einfügen',
+       'option_label_case_label' => 'Bezeichnung neben dem Feld',
+       'option_label_explication' => 'Anzeigetitel',
+       'option_label_label' => 'Bezeichnung',
+       'option_maxlength_explication' => 'Der Besucher kann maximal diese Anzahl Zeichen eingeben',
+       'option_maxlength_label' => 'Zeichen maximal',
+       'option_multiple_explication' => 'Der Nutzer kann mehrere Optionen auswählen',
+       'option_multiple_label' => 'Mehrfachauswahl',
+       'option_nom_explication' => 'Reserviertes Wort für das Feld. Darf nur alphanumerische klein geschriebene und das Zeichen "_" (Unterstrich) enthalten.',
+       'option_nom_label' => 'Feldname',
+       'option_obligatoire_label' => 'Pflichtfeld',
+       'option_option_intro_label' => 'Bezeichnung der ersten leeren Auswahl',
+       'option_option_statut_label' => 'Status anzeigen',
+       'option_pliable_label' => 'Klappbar',
+       'option_pliable_label_case' => 'Die Feldgruppe kann zugeklappt werden',
+       'option_plie_label' => 'Bereits zugeklappt',
+       'option_plie_label_case' => 'Wen die Feldgruppe klappbar ist, wird sie beim Anzeigen des Formulars zunächst zugeklappt angezeigt.',
+       'option_previsualisation_explication' => 'Reiter "Vorschau" hinzufügen, wenn das Plugin Porte-Plume aktiviert ist.',
+       'option_previsualisation_label' => 'Vorschau aktivieren',
+       'option_readonly_explication' => 'Dieses Feld kann angezeigt aber nicht bearbeitet werden.',
+       'option_readonly_label' => 'Nur Lesen',
+       'option_rows_explication' => 'Höhe des Blocks in Zeilen. Diese Option ist nicht immer wirksam, da sie von individuellen CSS-Stilen abgeschaltet werden kann.',
+       'option_rows_label' => 'Anzahl Zeilen',
+       'option_size_explication' => 'Breite des Felds in Zeichen. Diese Option ist nicht immer wirksam, da sie durch individuelle CSS.Stile abgeschaltet werden kann.',
+       'option_size_label' => 'Feldgröße',
+       'option_type_choix_plusieurs' => 'Auswahl <strong>mehrerer</strong> Empfänger erlauben',
+       'option_type_choix_tous' => '<strong>Alle</strong> diese Autoren als Empfänger hinzufügen. Der Besucher kann keine Auswahl treffen.',
+       'option_type_choix_un' => 'Der Besucher kann nur <strong>einen einzigen</strong> Empfänger auswählen.', # MODIF
+       'option_type_explication' => 'Im "versteckten" Modus wird der Inhalt dieses Felds nicht angezeigt.',
+       'option_type_label' => 'Feldtyp',
+       'option_type_password' => 'Versteckt', # MODIF
+       'option_type_text' => 'Normal',
+
+       // S
+       'saisie_auteurs_explication' => 'Ermöglicht einen oder mehrere Autoren auszuwählen',
+       'saisie_auteurs_titre' => 'Autoren',
+       'saisie_case_explication' => 'Ermöglicht Dinge zu aktivieren und deaktivieren',
+       'saisie_case_titre' => 'Nur eine Option',
+       'saisie_checkbox_explication' => 'Erlaubt mehrer Optionen auszuwählen',
+       'saisie_checkbox_titre' => 'Kästchen zum Abhaken',
+       'saisie_date_explication' => 'Datum aus Kalendarium auswählen',
+       'saisie_date_titre' => 'Datum',
+       'saisie_destinataires_explication' => 'Ermöglicht mehrere Empfänger aus den vorgeschlagenen Autoren auszuwählen',
+       'saisie_destinataires_titre' => 'Empfänger',
+       'saisie_explication_explication' => 'Allgemeine Beschreibung',
+       'saisie_explication_titre' => 'Beschreibung',
+       'saisie_fieldset_explication' => 'Ein Rahmen, der mehrere Felder enthalten kann',
+       'saisie_fieldset_titre' => 'Feldgruppe',
+       'saisie_file_explication' => 'Datei senden',
+       'saisie_file_titre' => 'Datei',
+       'saisie_hidden_explication' => 'Ein für den Nutzer unsichtbares, vorab ausgefülltes Feld',
+       'saisie_hidden_titre' => 'Verborgenes Feld',
+       'saisie_input_explication' => 'Eine einfache Textzeile, kann angezeigt oder ausgeblendet werden (Passwort)',
+       'saisie_input_titre' => 'Textzeile',
+       'saisie_oui_non_explication' => 'Ja oder nein, alle klar ? :)',
+       'saisie_oui_non_titre' => 'Ja oder nein',
+       'saisie_radio_defaut_choix1' => 'Eins',
+       'saisie_radio_defaut_choix2' => 'Zwei',
+       'saisie_radio_defaut_choix3' => 'Drei',
+       'saisie_radio_explication' => 'Ermöglicht eine Option aus mehreren verfügbaren auszuwählen',
+       'saisie_radio_titre' => 'Radioknöpfe',
+       'saisie_selecteur_article' => 'Werkzeug zur Auswahl eines Artikels anzeigen',
+       'saisie_selecteur_article_titre' => 'Artikelwahl',
+       'saisie_selecteur_rubrique' => 'Werkzeug zur Auswahl einer Rubrik anzeigen',
+       'saisie_selecteur_rubrique_article' => 'Werkzeug zur Auswahl einer Rubrik oder eines Artikels anzeigen',
+       'saisie_selecteur_rubrique_article_titre' => 'Rubrik- oder Artikelwahl',
+       'saisie_selecteur_rubrique_titre' => 'Rubrikwahl',
+       'saisie_selection_explication' => 'Eine Option aus einer Drop-Down-Liste auswählen.',
+       'saisie_selection_multiple_explication' => 'Mehrere Optionen aus einer Liste auswählen',
+       'saisie_selection_multiple_titre' => 'Mehrfachauswahl',
+       'saisie_selection_titre' => 'Drop-Down-Liste',
+       'saisie_textarea_explication' => 'Mehrzeiliges Textfeld',
+       'saisie_textarea_titre' => 'Textblock',
+
+       // T
+       'tous_visiteurs' => 'Alle Besucher (auch nicht eingeschriebene)',
+       'tout_selectionner' => 'Alles auswählen',
+
+       // V
+       'vue_sans_reponse' => '<i>Ohne Antwort</i>',
+
+       // Z
+       'z' => 'Zzz'
+);
+
+?>
diff --git a/www/plugins/saisies/lang/saisies_en.php b/www/plugins/saisies/lang/saisies_en.php
new file mode 100644 (file)
index 0000000..6e8cd5a
--- /dev/null
@@ -0,0 +1,193 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=en
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // B
+       'bouton_parcourir_docs_article' => 'Browse through the article',
+       'bouton_parcourir_docs_breve' => 'Browse through the news item',
+       'bouton_parcourir_docs_rubrique' => 'Browse through the section',
+       'bouton_parcourir_mediatheque' => 'Browse through the multimedia library',
+
+       // C
+       'construire_action_annuler' => 'Cancel',
+       'construire_action_configurer' => 'Set up',
+       'construire_action_deplacer' => 'Move',
+       'construire_action_dupliquer' => 'Duplicate',
+       'construire_action_dupliquer_copie' => '(copy)',
+       'construire_action_supprimer' => 'Delete',
+       'construire_ajouter_champ' => 'Add a field',
+       'construire_attention_enregistrer' => 'Remember to save your changes!',
+       'construire_attention_modifie' => 'The form below is different from the initial form. You can reset it to the state before the changes.',
+       'construire_attention_supprime' => 'Your changes include deletions of fields. Please confirm the registration of the new version of the form.',
+       'construire_aucun_champs' => 'There is currently no field in this form.',
+       'construire_confirmer_supprimer_champ' => 'Do you really want to delete this field?',
+       'construire_info_nb_champs_masques' => '@nb@ hidden field(s) the time to set up the group.',
+       'construire_position_explication' => 'Specify before which other field this one should be placed.',
+       'construire_position_fin_formulaire' => 'At the end of the form',
+       'construire_position_fin_groupe' => 'At the end of the group @groupe@',
+       'construire_position_label' => 'Position of the field',
+       'construire_reinitialiser' => 'Reset form',
+       'construire_reinitialiser_confirmer' => 'You will lose all your changes. Are you sure you want to go back to the original form?',
+       'construire_verifications_aucune' => 'None',
+       'construire_verifications_label' => 'Type of verification to be performed',
+
+       // E
+       'erreur_generique' => 'There are errors in the fields below, please check your inputs',
+       'erreur_option_nom_unique' => 'This name is already used by another field and it must be unique in this form.',
+
+       // I
+       'info_configurer_saisies' => 'Test page for Entries',
+
+       // L
+       'label_annee' => 'Year',
+       'label_jour' => 'Day',
+       'label_mois' => 'Month',
+
+       // O
+       'option_aff_art_interface_explication' => 'Display only the articles in the user’s language',
+       'option_aff_art_interface_label' => 'Multilingual display',
+       'option_aff_langue_explication' => 'Display the selected language of the article or section before the title',
+       'option_aff_langue_label' => 'Display the language',
+       'option_aff_rub_interface_explication' => 'Display only the sections in the user’s language',
+       'option_aff_rub_interface_label' => 'Multilingual display',
+       'option_afficher_si_explication' => 'Indicate the display conditions of this field in function of the value of other fields. The identifier of the other fields has to be entered between <code>@</code>. <br />Example <code>@selection_1@=="Toto"</code> conditions the display of the field only when field <code>selection_1</code> has a value of <code>Toto</code>.',
+       'option_afficher_si_label' => 'Conditional display',
+       'option_afficher_si_remplissage_explication' => 'Other than the previous option, this one is only displayed while the form is being entered and not when the result is displayed. Its  syntax is the same.',
+       'option_afficher_si_remplissage_label' => 'Conditional display when being filled',
+       'option_attention_explication' => 'A message more important than the explanation.',
+       'option_attention_label' => 'Warning',
+       'option_autocomplete_defaut' => 'Leave the default',
+       'option_autocomplete_explication' => 'At page load, your browser may pre-fill the field based on its history',
+       'option_autocomplete_label' => 'Pre-fill the field',
+       'option_autocomplete_off' => 'Disable',
+       'option_autocomplete_on' => 'Enable',
+       'option_cacher_option_intro_label' => 'Hide the first empty choice',
+       'option_choix_alternatif_label' => 'Allow to propose an alternative choice',
+       'option_choix_alternatif_label_defaut' => 'Other choice',
+       'option_choix_alternatif_label_label' => 'Label for this alternative choice',
+       'option_choix_destinataires_explication' => 'One or several authors among which the user can make his choice. If nothing selected, it will be the author who installed the site to be chosen.',
+       'option_choix_destinataires_label' => 'Possible recipients',
+       'option_class_label' => 'Additional CSS Classes',
+       'option_cols_explication' => 'Field width in characters. This option is not always applied/used because the CSS styles of your site may override it.',
+       'option_cols_label' => 'Width',
+       'option_datas_explication' => 'You need to specify a choice for each row in the form of "key|label of the choice"',
+       'option_datas_label' => 'List of the available choices',
+       'option_datas_sous_groupe_explication' => 'You can indicate a choice by line using the format "key|Label" of the choice. <br />You can indicate the start of a subgroup using the format "*Title of the subgroup". To end a subgroup you can start another one, or put a line containing "/*".',
+       'option_defaut_label' => 'Default value',
+       'option_disable_avec_post_explication' => 'Same as previous option position but still post value in a hidden field.',
+       'option_disable_avec_post_label' => 'Disabled but posted.',
+       'option_disable_explication' => 'The field can not get the focus.',
+       'option_disable_label' => 'Disable the field',
+       'option_erreur_obligatoire_explication' => 'You can customize the error message displayed to show an obligation (otherwise leave blank).',
+       'option_erreur_obligatoire_label' => 'Obligation message',
+       'option_explication_explication' => 'If necessary, a short sentence describing the subject field.',
+       'option_explication_label' => 'Explanation',
+       'option_groupe_affichage' => 'Display',
+       'option_groupe_description' => 'Description',
+       'option_groupe_utilisation' => 'Usage',
+       'option_groupe_validation' => 'Validation',
+       'option_heure_pas_explication' => 'When using the schedule, a menu is displayed to help enter hours and minutes. Here you can choose the time interval between each option (default 30 minutes).',
+       'option_heure_pas_label' => 'Interval of the minutes in the help menu of the input',
+       'option_horaire_label' => 'Schedule',
+       'option_horaire_label_case' => 'Allow to fill in the time',
+       'option_id_groupe_label' => 'Keyword group',
+       'option_info_obligatoire_explication' => 'You can modify the default indication of obligation: <i>[Obligatoire]</i>.',
+       'option_info_obligatoire_label' => 'Indication of obligation',
+       'option_inserer_barre_choix_edition' => 'complete editing toolbar',
+       'option_inserer_barre_choix_forum' => 'forums toolbar',
+       'option_inserer_barre_explication' => 'Insert a porte-plume toolbar if that tool is activated.',
+       'option_inserer_barre_label' => 'Insert a toolbar',
+       'option_label_case_label' => 'Label located beside the check box',
+       'option_label_explication' => 'The title that will be displayed.',
+       'option_label_label' => 'Label',
+       'option_maxlength_explication' => 'The user can not type more characters than this number.',
+       'option_maxlength_label' => 'Maximum number of characters',
+       'option_multiple_explication' => 'The user will be able to select several values',
+       'option_multiple_label' => 'Multiple selection',
+       'option_nom_explication' => 'A computer ID name that identifies the field. It may only contain lowercase alphanumeric characters or the underscore character "_".',
+       'option_nom_label' => 'Field name',
+       'option_obligatoire_label' => 'Required field',
+       'option_option_destinataire_intro_label' => 'Label of first choice empty (in list format)',
+       'option_option_intro_label' => 'Label for the first empty choice',
+       'option_option_statut_label' => 'Show the status',
+       'option_pliable_label' => 'Expandable',
+       'option_pliable_label_case' => 'The group of fields can be expanded or shrunk.',
+       'option_plie_label' => 'Already shrunk',
+       'option_plie_label_case' => 'If the group of fields can be expanded and shrunk, then this option will make it already shrink with the form displays.',
+       'option_previsualisation_explication' => 'If porte-plume is activated, add a tab to preview the appearance of the text entered.',
+       'option_previsualisation_label' => 'Activate previews',
+       'option_readonly_explication' => 'The field can be viewed, selected, but not modified.',
+       'option_readonly_label' => 'Read only',
+       'option_rows_explication' => 'Field height in lines. This option is not always applied/used because the CSS styles of your site can cancel it.',
+       'option_rows_label' => 'Lines number',
+       'option_size_explication' => 'Field width in characters. This option is not always applied/used because the CSS styles of your site can cancel it.',
+       'option_size_label' => 'Field size',
+       'option_type_choix_plusieurs' => 'Allow the user to choose <strong>several</ strong> message recipients.',
+       'option_type_choix_tous' => 'Make <strong>all</ strong> these authors as recipients. The user will not have choice.',
+       'option_type_choix_un' => 'Allow the user to choose <strong>only one</strong> message recipient (as a dropdown list).',
+       'option_type_choix_un_radio' => 'Allow the user to select <strong>one single</strong> addressee (in checklist format).',
+       'option_type_explication' => 'In "disguised" mode, the field contents as typed will be replaced with asterisks.',
+       'option_type_label' => 'Field type',
+       'option_type_password' => 'Text, hidden during input (eg. password)',
+       'option_type_text' => 'Normal',
+
+       // S
+       'saisie_auteurs_explication' => 'Allows you to select one or more authors',
+       'saisie_auteurs_titre' => 'Autors',
+       'saisie_case_explication' => 'Used to activate or deactivate a particular option.',
+       'saisie_case_titre' => 'Single check box',
+       'saisie_checkbox_explication' => 'Used to select several options using check boxes.',
+       'saisie_checkbox_titre' => 'Check boxes',
+       'saisie_date_explication' => 'Used to enter a date using a calendar tool',
+       'saisie_date_titre' => 'Date',
+       'saisie_destinataires_explication' => 'Used to select one or more recipients from among the pre-selected authors.',
+       'saisie_destinataires_titre' => 'Recipients',
+       'saisie_explication_explication' => 'A general explanatory description.',
+       'saisie_explication_titre' => 'Explanation',
+       'saisie_fieldset_explication' => 'A frame which may include several fields.',
+       'saisie_fieldset_titre' => 'Fieldset',
+       'saisie_file_explication' => 'Send a file',
+       'saisie_file_titre' => 'File',
+       'saisie_hidden_explication' => 'A pre-filled field that the user will never see.',
+       'saisie_hidden_titre' => 'Hidden field',
+       'saisie_input_explication' => 'A simple line of text that can be visible or hidden (password).',
+       'saisie_input_titre' => 'Textfield',
+       'saisie_mot_explication' => 'One or more keywords of a group',
+       'saisie_mot_titre' => 'Keyword',
+       'saisie_oui_non_explication' => 'Either a Yes or No response',
+       'saisie_oui_non_titre' => 'Yes or No',
+       'saisie_radio_defaut_choix1' => 'One',
+       'saisie_radio_defaut_choix2' => 'Two',
+       'saisie_radio_defaut_choix3' => 'Three',
+       'saisie_radio_explication' => 'Used to select one single option from several possibilities.',
+       'saisie_radio_titre' => 'Radio buttons',
+       'saisie_selecteur_article' => 'Display an article selection browser',
+       'saisie_selecteur_article_titre' => 'Article selector',
+       'saisie_selecteur_rubrique' => 'Display a section selector browser',
+       'saisie_selecteur_rubrique_article' => 'Display an article or section selector browser',
+       'saisie_selecteur_rubrique_article_titre' => 'Article or section selector',
+       'saisie_selecteur_rubrique_titre' => 'Section selector',
+       'saisie_selection_explication' => 'Select an option from a dropdown list box.',
+       'saisie_selection_multiple_explication' => 'Used for selecting several options from a list.',
+       'saisie_selection_multiple_titre' => 'Multiple selection',
+       'saisie_selection_titre' => 'Dropdown list box',
+       'saisie_textarea_explication' => 'A multilines text field.',
+       'saisie_textarea_titre' => 'Textarea',
+
+       // T
+       'tous_visiteurs' => 'All visitors (even if not registered)',
+       'tout_selectionner' => 'Select all',
+
+       // V
+       'vue_sans_reponse' => '<i>(no data entered)</i>',
+
+       // Z
+       'z' => 'zzz'
+);
+
+?>
diff --git a/www/plugins/saisies/lang/saisies_es.php b/www/plugins/saisies/lang/saisies_es.php
new file mode 100644 (file)
index 0000000..c37fa17
--- /dev/null
@@ -0,0 +1,193 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=es
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // B
+       'bouton_parcourir_docs_article' => 'Buscar artículo',
+       'bouton_parcourir_docs_breve' => 'Buscar breve',
+       'bouton_parcourir_docs_rubrique' => 'Buscar la sección',
+       'bouton_parcourir_mediatheque' => 'Examinar mediateca',
+
+       // C
+       'construire_action_annuler' => 'Anular',
+       'construire_action_configurer' => 'Configurar',
+       'construire_action_deplacer' => 'Mover',
+       'construire_action_dupliquer' => 'Duplicar',
+       'construire_action_dupliquer_copie' => '(copia)',
+       'construire_action_supprimer' => 'Eliminar',
+       'construire_ajouter_champ' => 'Añadir un campo',
+       'construire_attention_enregistrer' => '¡No olvide guardar sus cambios!',
+       'construire_attention_modifie' => 'Este formulario es diferente al original. Tiene la posibilidad de restablecerlo conforme a su estado inical. ',
+       'construire_attention_supprime' => 'Sus cambios implican suprimir campos. Confirme por favor esta nueva versión del formulario. ',
+       'construire_aucun_champs' => 'Actualmente no existen campos en este formulario. ',
+       'construire_confirmer_supprimer_champ' => '¿Desea eliminar realmente este campo?',
+       'construire_info_nb_champs_masques' => '@nb@ campo(s) oculto(s) el tiempo de configurar el grupo.',
+       'construire_position_explication' => 'Indique delante de qué otro campo se colocará.',
+       'construire_position_fin_formulaire' => 'Al final del formulario',
+       'construire_position_fin_groupe' => 'Al final del grupo @groupe@',
+       'construire_position_label' => 'Posición del campo',
+       'construire_reinitialiser' => 'Restablecer el formulario',
+       'construire_reinitialiser_confirmer' => 'Va a perder todos los cambios. ¿Está seguro de volver al formulario original?',
+       'construire_verifications_aucune' => 'Ninguna',
+       'construire_verifications_label' => 'Tipo de verificación a efectuar',
+
+       // E
+       'erreur_generique' => 'Hay errores en los siguientes campos, revise por favor sus entradas',
+       'erreur_option_nom_unique' => 'Este nombre ya ha sido utilizado en otro campo, y ha de ser único en el formulario.',
+
+       // I
+       'info_configurer_saisies' => 'Página de prueba de las entradas',
+
+       // L
+       'label_annee' => 'Año',
+       'label_jour' => 'Día',
+       'label_mois' => 'Mes',
+
+       // O
+       'option_aff_art_interface_explication' => 'Mostrar sólo los artículos en el idioma del usuario',
+       'option_aff_art_interface_label' => 'Aparencia multilingüe',
+       'option_aff_langue_explication' => 'Muestra el idioma del artículo o de la sección delante del título',
+       'option_aff_langue_label' => 'Mostrar el idioma',
+       'option_aff_rub_interface_explication' => 'Mostrar sólo las secciones en el idioma del usuario',
+       'option_aff_rub_interface_label' => 'Apariencia multilingüe',
+       'option_afficher_si_explication' => 'Indique las condiciones para mostrar el campo en función del valor de los otros campos. El identificador de los otros campos debe ser indicarse entre <code>@</code>. <br />Ejemplo <code>@selection_1@=="Toto"</code> condiciona la visualización del campo a que el campo <code>selection_1</code> tenga por valor <code>Toto</code>.',
+       'option_afficher_si_label' => 'Visualización condicional',
+       'option_afficher_si_remplissage_explication' => 'Contrariamente a la opción anterior, ésta condiciona la visualización sólo al rellenar el formulario, no al mostrar los resultados. La sintaxis es la misma.',
+       'option_afficher_si_remplissage_label' => 'presentación condicional durante el rellenado',
+       'option_attention_explication' => 'Un mensaje más importante que la explicación.',
+       'option_attention_label' => 'Aviso',
+       'option_autocomplete_defaut' => 'Dejar por defecto',
+       'option_autocomplete_explication' => 'Al cargar la página, su navegador puede rellenar el campo en función de su historial',
+       'option_autocomplete_label' => 'Pre-relleno del campo',
+       'option_autocomplete_off' => 'Desactivar',
+       'option_autocomplete_on' => 'Activar',
+       'option_cacher_option_intro_label' => 'Esconder la primera opción vacía',
+       'option_choix_alternatif_label' => 'Permitir proponer una elección alternativa',
+       'option_choix_alternatif_label_defaut' => 'Otra elección',
+       'option_choix_alternatif_label_label' => 'Etiqueta de esta elección alternativa',
+       'option_choix_destinataires_explication' => 'Uno o varios autores entre los cuales el autor podrá elegir. Si no se selecciona nada, será el autor que ha instalado el sitio el elegido. ',
+       'option_choix_destinataires_label' => 'Destinatarios posibles',
+       'option_class_label' => 'Clases CSS adicionales',
+       'option_cols_explication' => 'Ancho del bloque (en número de caracteres). Esta opción no se aplica siempre, porque puede ser cancelada por los estilos CSS de tu sitio.',
+       'option_cols_label' => 'Ancho',
+       'option_datas_explication' => 'Debe indicar una opción por línea bajo la forma "clave|Etiqueta de la opción"',
+       'option_datas_label' => 'Lista de opciones posibles',
+       'option_datas_sous_groupe_explication' => 'Debe indicar una opción por línea bajo la forma "clave|Etiqueta" de la opción". <br />Puede indicar el inicio de un subgrupo bajo la forma "*Título del subgrupo". Para terminar un subgrupo puede iniciar otro, o bien colocar una línea que contenga "/*".',
+       'option_defaut_label' => 'Valor por defecto',
+       'option_disable_avec_post_explication' => 'Como la opción anterior, pero publica el valor en un campo escondido.',
+       'option_disable_avec_post_label' => 'Deactivar pero enviar',
+       'option_disable_explication' => 'El campo ya no puede obtener el foco.',
+       'option_disable_label' => 'Deactivar el campo',
+       'option_erreur_obligatoire_explication' => 'Puede personalizar el mensaje de error mostrado para indicar una obligación (sino dejar en blanco).',
+       'option_erreur_obligatoire_label' => 'Mensaje de obligación',
+       'option_explication_explication' => 'Si hace falta, una frase corta que describe el campo',
+       'option_explication_label' => 'Explicación',
+       'option_groupe_affichage' => 'Apariencia',
+       'option_groupe_description' => 'Descripción',
+       'option_groupe_utilisation' => 'Uso',
+       'option_groupe_validation' => 'Validación',
+       'option_heure_pas_explication' => 'Cuando usa el horario, se muestra un menú para ayudar a introducir horas y minutos. Aquí puede elegir el intervalo de tiempo entre cada opción (por defecto 30 minutos).',
+       'option_heure_pas_label' => 'Intervalo de minutos en el menú de ayuda a la entrada',
+       'option_horaire_label' => 'Horario',
+       'option_horaire_label_case' => 'Permite introducir también la hora',
+       'option_id_groupe_label' => 'Grupo de palabras-claves',
+       'option_info_obligatoire_explication' => 'Puede modificar la indicación de campo obligatoria por defecto: <i>[Obligatorio</i>.',
+       'option_info_obligatoire_label' => 'Indicación de campo obligatorio',
+       'option_inserer_barre_choix_edition' => 'Barra de edición completa',
+       'option_inserer_barre_choix_forum' => 'barra de los foros',
+       'option_inserer_barre_explication' => 'Insertar una barra tipográfica si ésta está activada.',
+       'option_inserer_barre_label' => 'Insertar una barra de herramientas',
+       'option_label_case_label' => 'Etiqueta posicionada al lado de la casilla',
+       'option_label_explication' => 'El título que se mostrará.',
+       'option_label_label' => 'Etiqueta',
+       'option_maxlength_explication' => 'El campo no podrá contener más caracteres que este número.',
+       'option_maxlength_label' => 'Número máximo de caracteres',
+       'option_multiple_explication' => 'Se podrán seleccionar varias opciones',
+       'option_multiple_label' => 'Selección múltiple',
+       'option_nom_explication' => 'Un nombre informático que identificará el campo. Sólo puede contener caracteres alfanuméricos minúsculos o el carácter "_".',
+       'option_nom_label' => 'Nombre del campo',
+       'option_obligatoire_label' => 'Campo obligatorio',
+       'option_option_destinataire_intro_label' => 'Etiqueta de la primera opción vacía (cuando esté en forma de lista)',
+       'option_option_intro_label' => 'Etiqueta de la primera opción vacía',
+       'option_option_statut_label' => 'Mostrar el estatus',
+       'option_pliable_label' => 'Desplegable',
+       'option_pliable_label_case' => 'El grupo de campos se podrá contraer y desplegar.',
+       'option_plie_label' => 'Ya está contraido',
+       'option_plie_label_case' => 'Si el grupo de campos se puede contraer, ya estará contraido cuando se enseñe el formulario.',
+       'option_previsualisation_explication' => 'Si la barra tipográfica es activa, añade una pestaña de previsualización del texto.',
+       'option_previsualisation_label' => 'Activar la previsualización',
+       'option_readonly_explication' => 'El campo se puede leer, seleccionar, pero no se puede modificar.',
+       'option_readonly_label' => 'Sólo lectura',
+       'option_rows_explication' => 'Altura del bloque en número de líneas. Esta opción no se aplica siempre, porque puede ser cancelada por los estilos CSS de su sitio.',
+       'option_rows_label' => 'Número de líneas',
+       'option_size_explication' => 'Ancho del campo (número de caracteres). Esta opción no se aplica siempre, porque puede ser cancelada por los estilos CSS del sitio.',
+       'option_size_label' => 'Tamaño del campo',
+       'option_type_choix_plusieurs' => 'Permitirle al usuario elegir <strong>varias</strong> personas destinatarias.',
+       'option_type_choix_tous' => 'Poner a <strong>todos</strong> estos autores como destinatarios. El usuario no tendrá ninguna opción.',
+       'option_type_choix_un' => 'Permitir al usuario elegir <strong>sólo una</strong> persona destinataria (en forma de lista desplegable).',
+       'option_type_choix_un_radio' => 'Permitir al usuario elegir <strong>sólo una</strong> persona destinataria (en forma de lista de viñetas).',
+       'option_type_explication' => 'En modo "escondido", el contenido del campo no será visible.',
+       'option_type_label' => 'Tipo del campo',
+       'option_type_password' => 'Texto escondido mientras tecleando (por ej. contraseña)',
+       'option_type_text' => 'Normal',
+
+       // S
+       'saisie_auteurs_explication' => 'Permite seleccionar uno o más autores',
+       'saisie_auteurs_titre' => 'Autores',
+       'saisie_case_explication' => 'Permite activar o desactivar algo.',
+       'saisie_case_titre' => 'Casilla única',
+       'saisie_checkbox_explication' => 'Permite elegir varias opciones con las casillas a marcar.',
+       'saisie_checkbox_titre' => 'Casillas a marcar',
+       'saisie_date_explication' => '¿Permitir introducir una fecha? Ayuda de calendario',
+       'saisie_date_titre' => 'Fecha',
+       'saisie_destinataires_explication' => 'Permite elegir una o varias personas destinatarias entre las autoras preseleccionadas.',
+       'saisie_destinataires_titre' => 'Personas destinatarias',
+       'saisie_explication_explication' => 'Una explicación general.',
+       'saisie_explication_titre' => 'Explicación',
+       'saisie_fieldset_explication' => 'Un marco que podrá englobar varios campos.',
+       'saisie_fieldset_titre' => 'Grupo de campos',
+       'saisie_file_explication' => 'Mandar un archivo',
+       'saisie_file_titre' => 'Archivo',
+       'saisie_hidden_explication' => 'Un campo invisible, que ya contiene un valor',
+       'saisie_hidden_titre' => 'Campo escondido',
+       'saisie_input_explication' => 'Una sola línea de texto, que puede ser visible u ocultada (contraseña).',
+       'saisie_input_titre' => 'Línea de texto',
+       'saisie_mot_explication' => 'Una o varias palabras-claves de un grupo de palabras',
+       'saisie_mot_titre' => 'Palabra-clave',
+       'saisie_oui_non_explication' => 'Sí o no, ¿está claro? :)',
+       'saisie_oui_non_titre' => 'Sí o no',
+       'saisie_radio_defaut_choix1' => 'Uno',
+       'saisie_radio_defaut_choix2' => 'Dos',
+       'saisie_radio_defaut_choix3' => 'Tres',
+       'saisie_radio_explication' => 'Permite elegir una opción dentro de varias opciones disponibles.',
+       'saisie_radio_titre' => 'Botones de opción',
+       'saisie_selecteur_article' => 'Muestra un navegador de selección de artículo',
+       'saisie_selecteur_article_titre' => 'Selector de artículo',
+       'saisie_selecteur_rubrique' => 'Muestra un navegador de selección de sección',
+       'saisie_selecteur_rubrique_article' => 'Muestra un navegador de selección de artículo o de sección',
+       'saisie_selecteur_rubrique_article_titre' => 'Selector de artículo o de sección',
+       'saisie_selecteur_rubrique_titre' => 'Selector de sección',
+       'saisie_selection_explication' => 'Elegir una opción dentro de una lista desplegable.',
+       'saisie_selection_multiple_explication' => 'Permite elegir varias opciones con una lista.',
+       'saisie_selection_multiple_titre' => 'Selección múltiple',
+       'saisie_selection_titre' => 'Lista desplegable',
+       'saisie_textarea_explication' => 'Un campo de texto sobre varias líneas.',
+       'saisie_textarea_titre' => 'Bloque de texto',
+
+       // T
+       'tous_visiteurs' => 'Todos los visitantes (incluso no registrados)',
+       'tout_selectionner' => 'Seleccionar todo',
+
+       // V
+       'vue_sans_reponse' => '<i>Sin respuesta</i>',
+
+       // Z
+       'z' => 'zzz'
+);
+
+?>
diff --git a/www/plugins/saisies/lang/saisies_fa.php b/www/plugins/saisies/lang/saisies_fa.php
new file mode 100644 (file)
index 0000000..5f3a866
--- /dev/null
@@ -0,0 +1,135 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=fa
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // B
+       'bouton_parcourir_docs_article' => 'مرور مقاله',
+       'bouton_parcourir_docs_breve' => 'مرور خبر',
+       'bouton_parcourir_docs_rubrique' => 'مرور بخش',
+       'bouton_parcourir_mediatheque' => 'مرور كتابخانه چندرسانه‌اي',
+
+       // L
+       'label_annee' => 'سال',
+       'label_jour' => 'روز',
+       'label_mois' => 'ماه',
+
+       // O
+       'option_aff_art_interface_explication' => 'نمايش مقالات فقط به زبان كاربر',
+       'option_aff_art_interface_label' => 'نمايش چندزبانه',
+       'option_aff_langue_explication' => 'نمايش زبان مقاله يا بخش انتخاب شده‌ پيش از تيتر',
+       'option_aff_langue_label' => 'نمايش زبان',
+       'option_aff_rub_interface_explication' => 'نمايش بخش‌ها فقط به زبان كاربر',
+       'option_aff_rub_interface_label' => 'نمايش چندزبانه',
+       'option_attention_explication' => 'پيامي مهم‌تر از توضيح.',
+       'option_attention_label' => 'هشدار',
+       'option_cacher_option_intro_label' => 'پنهان‌سازي نخستين انتخاب خالي ',
+       'option_choix_destinataires_explication' => 'يك يا چند نويسنده كه كاربر مي‌تواند از ميانشان انتخاب كند. اگر كسي انتخاب نشود،‌ نويسنده‌اي كه سايت را نصب كرده انتخاب خواهد شد.<NEW>Un ou plusieurs auteurs parmis lesquels l’utilisateur pourra faire son choix. Si rien n’est sélectionné, c’est l’auteur qui a installé le site qui sera choisi.',
+       'option_choix_destinataires_label' => '<دريافت‌ كنندگان محتمل',
+       'option_class_label' => 'كلاس‌هاي سي.اس.اس اضافي ',
+       'option_cols_explication' => 'پهناي بلوك به تعداد كارآكترها. اين گزينه هميشه كاربرد ندارد چرا كه شيوه‌هاي سي.اس.اس سايت شما مي‌تواند آن را منتفي سازد.',
+       'option_cols_label' => 'پهنا',
+       'option_datas_explication' => 'لازم است براي هر رديف يك گزينه در قالب «گزينه‌ي كليد|برچسبِ» انتخاب كنيد.', # MODIF
+       'option_datas_label' => 'فهرست گزينه‌هاي ممكن ',
+       'option_defaut_label' => 'مقدار پيش‌ گزيده',
+       'option_disable_avec_post_explication' => 'همانند وضعيت گزينه‌ي قبلي اما هنوز مقدار در ميدان مخفي پست شود.',
+       'option_disable_avec_post_label' => 'غيرفعال اما پست شده',
+       'option_disable_explication' => 'ميدان نمي‌‌تواند تمركز بيشتري بگيرد.',
+       'option_disable_label' => 'غيرفعال سازي ميدن',
+       'option_explication_explication' => 'در هنگام نياز، يك عبارت كوتاه موضوع ميدان را بيان كند.',
+       'option_explication_label' => 'توضيح',
+       'option_groupe_affichage' => 'نمايش ',
+       'option_groupe_description' => 'توصيف',
+       'option_groupe_utilisation' => 'كاربرد',
+       'option_groupe_validation' => 'ارزش‌گذاري',
+       'option_info_obligatoire_explication' => 'مي‌توانيد كاربرد پيش‌ گزيده‌ي الزامي را اصلاح كنيد: <i>[Obligatoire]</i>.',
+       'option_info_obligatoire_label' => 'نشان الزام',
+       'option_inserer_barre_choix_edition' => 'ميل‌ابزار ويرايش كامل ',
+       'option_inserer_barre_choix_forum' => 'ميل‌ابزار سخنگاه‌ها',
+       'option_inserer_barre_explication' => 'گنجاندن يك ميل‌‌ابزار «چوب قلم» «porte-plume» اگر آن ابزار فعال باشد.',
+       'option_inserer_barre_label' => 'گنجاندن يك ميل‌ابزار',
+       'option_label_case_label' => 'برچسب كنار چك باكس ',
+       'option_label_explication' => 'تيتري كه نمايش داده خواهد شد.',
+       'option_label_label' => 'برچسب',
+       'option_maxlength_explication' => 'كاربر نمي‌تواند كارآكترهايي بيش از اين تعداد تايپ كند.',
+       'option_maxlength_label' => 'تعداد بيشترين كارآكتر',
+       'option_multiple_explication' => 'توانايي كاربر در انتخاب چند مقدار',
+       'option_multiple_label' => 'چندگزينه‌اي',
+       'option_nom_explication' => 'يك اسم انفورماتيك كه اين ميدان را معرفي كند. اين اسم فقط بايد مركب از حروف كوچك و يا آندر لاين «_» باشد. ',
+       'option_nom_label' => 'اسم ميدان',
+       'option_obligatoire_label' => 'ميدان الزامي',
+       'option_option_intro_label' => 'برچسب نخستين گزينه‌ي خالي ',
+       'option_pliable_label' => 'گسترشي ',
+       'option_pliable_label_case' => 'گروه‌ ميدان‌هاي چين خور.',
+       'option_plie_label' => 'چين خورده',
+       'option_plie_label_case' => 'اگر گروه ميدان‌ها چين خور باشد، اين گزينه آن‌ها را در نمايش فرم چين خور كرده است. ',
+       'option_previsualisation_explication' => 'اگر چوب قلم فعال باشد، يك تب براي پيش‌ نمايش متن ورودي اضافه كن.',
+       'option_previsualisation_label' => 'فعال سازي پيش نمايش ',
+       'option_readonly_explication' => 'ميدان قابل مشاهده و گزينش، اما ناويرايشي.',
+       'option_readonly_label' => 'فقط خوانداني',
+       'option_rows_explication' => 'بلندي ميدان در پيوند‌ها. اين گزينه كاربردي/كاربستي نيست زيرا سي.اس.اس سايت شما آن را رد مي‌كند.',
+       'option_rows_label' => 'تعداد پيوندها',
+       'option_size_explication' => 'پهناي ميدان بر اساس تعدا كارآكتر. اين گزينه هميشه كاربردي/كاربستي نيست زيرا سي.اس.اس سايت شما آن را رد مي‌كند.',
+       'option_size_label' => 'اندازه‌ي ميدان',
+       'option_type_choix_plusieurs' => 'اجازه به كاربر در انتخاب <strong> چند</strong> دريافت كننده‌ي ايميل.',
+       'option_type_choix_tous' => '<strong>تمام</strong> مؤلفان دريافت‌كننده شوند. كاربر گزينه‌اي نخواهد داشت.',
+       'option_type_choix_un' => 'اجازه به كاربر براي گزينش <strong> فقط يك </strong> دريافت‌كننده ', # MODIF
+       'option_type_explication' => 'در حالت «پوشيده»، محتواي ميدان قابل رؤيت نخواهد بود. ',
+       'option_type_label' => 'نوع ميدان',
+       'option_type_password' => 'پوشيده', # MODIF
+       'option_type_text' => 'عادي',
+
+       // S
+       'saisie_case_explication' => 'فعال يا غيرفعال‌سازي يك گزينه‌ي مشخص.',
+       'saisie_case_titre' => 'تك چك‌ باكش',
+       'saisie_checkbox_explication' => 'اجازه‌ي گزينش چك‌ باكس چندگزينه‌اي.',
+       'saisie_checkbox_titre' => 'چك باكس‌ها',
+       'saisie_date_explication' => 'اجازه واردسازي داده با كمك تقويم',
+       'saisie_date_titre' => 'تاريخ',
+       'saisie_destinataires_explication' => 'اجازه‌ي گزينش يك يا چند دريافت كننده از ميان نويسندگان پيش‌ گزيده.',
+       'saisie_destinataires_titre' => 'دريافت‌ كنندگان',
+       'saisie_explication_explication' => 'يك متن توصيفي كلي.',
+       'saisie_explication_titre' => 'توصيف',
+       'saisie_fieldset_explication' => 'كادري كه ممكن است چند ميدان داشته باشد.',
+       'saisie_fieldset_titre' => 'گروه ميدان',
+       'saisie_file_explication' => 'ارسال پرونده',
+       'saisie_file_titre' => 'پرونده',
+       'saisie_hidden_explication' => 'ميدان از‌پيش‌‌ پُري كه بازديدكننده هرگز نخواهد ديد.',
+       'saisie_hidden_titre' => 'ميدان پوشيده',
+       'saisie_input_explication' => 'خط ساده‌اي از متن كه مي‌تواند پوشيده يا آشكار باشد (گذرواژه)',
+       'saisie_input_titre' => 'ميدان متني',
+       'saisie_oui_non_explication' => 'بله يا نه، روشن است؟ :)',
+       'saisie_oui_non_titre' => 'بله يا نه',
+       'saisie_radio_defaut_choix1' => 'يك ',
+       'saisie_radio_defaut_choix2' => 'دو',
+       'saisie_radio_defaut_choix3' => 'سه',
+       'saisie_radio_explication' => 'اجازه‌ي گزينش يك تك گزينه‌اي از ميان چند امكان.',
+       'saisie_radio_titre' => 'دكمه راديويي',
+       'saisie_selecteur_article' => 'نمايش يك مرورگر گزينش مقاله',
+       'saisie_selecteur_article_titre' => 'گزينشگر مقاله',
+       'saisie_selecteur_rubrique' => 'نمايش مرورگر گزينشگر مقاله',
+       'saisie_selecteur_rubrique_article' => 'نمايش مرورگر گزينشگر يك مقاله يا يك بخش',
+       'saisie_selecteur_rubrique_article_titre' => 'گزينشگر مقاله يا بخش',
+       'saisie_selecteur_rubrique_titre' => 'گزينشگر بخش',
+       'saisie_selection_explication' => 'گزينش يك گزينه از فهرست آبشاري.',
+       'saisie_selection_multiple_explication' => 'اجازه‌ي گزينش چند گزينه با يك فهرست.',
+       'saisie_selection_multiple_titre' => 'چندگزينه‌اي',
+       'saisie_selection_titre' => 'فهرست‌دان آبشاري ',
+       'saisie_textarea_explication' => 'ميدان متن چندخطي.',
+       'saisie_textarea_titre' => 'بلوك‌متن',
+
+       // T
+       'tous_visiteurs' => 'تمام بازديدكنندگان (حتي ثبت ‌نام ناشدگان)',
+
+       // V
+       'vue_sans_reponse' => '<i>بي‌ پاسخ</i>',
+
+       // Z
+       'z' => 'zzz'
+);
+
+?>
diff --git a/www/plugins/saisies/lang/saisies_fr.php b/www/plugins/saisies/lang/saisies_fr.php
new file mode 100644 (file)
index 0000000..ce3dbd4
--- /dev/null
@@ -0,0 +1,191 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// Fichier source, a modifier dans svn://zone.spip.org/spip-zone/_plugins_/saisies/trunk/lang/
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // B
+       'bouton_parcourir_docs_article' => 'Parcourir l’article',
+       'bouton_parcourir_docs_breve' => 'Parcourir la brève',
+       'bouton_parcourir_docs_rubrique' => 'Parcourir la rubrique',
+       'bouton_parcourir_mediatheque' => 'Parcourir la médiathèque',
+
+       // C
+       'construire_action_annuler' => 'Annuler',
+       'construire_action_configurer' => 'Configurer',
+       'construire_action_deplacer' => 'Déplacer',
+       'construire_action_dupliquer' => 'Dupliquer',
+       'construire_action_dupliquer_copie' => '(copie)',
+       'construire_action_supprimer' => 'Supprimer',
+       'construire_ajouter_champ' => 'Ajouter un champ',
+       'construire_attention_enregistrer' => 'N’oubliez pas d’enregistrer vos modifications !',
+       'construire_attention_modifie' => 'Le formulaire ci-dessous est différent du formulaire initial. Vous avez la possibilité de le réinitialiser à son état avant vos modifications.',
+       'construire_attention_supprime' => 'Vos modifications comportent des suppressions de champs. Veuillez confirmer l’enregistrement de cette nouvelle version du formulaire.',
+       'construire_aucun_champs' => 'Il n’y a pour l’instant aucun champ dans ce formulaire.',
+       'construire_confirmer_supprimer_champ' => 'Voulez-vous vraiment supprimer ce champ ?',
+       'construire_info_nb_champs_masques' => '@nb@ champ(s) masqué(s) le temps de configurer le groupe.',
+       'construire_position_explication' => 'Indiquez devant quel autre champ sera placé celui-ci.',
+       'construire_position_fin_formulaire' => 'À la fin du formulaire',
+       'construire_position_fin_groupe' => 'À la fin du groupe @groupe@',
+       'construire_position_label' => 'Position du champ',
+       'construire_reinitialiser' => 'Réinitialiser le formulaire',
+       'construire_reinitialiser_confirmer' => 'Vous allez perdre toutes vos modifications. Êtes-vous sûr de vouloir revenir au formulaire initial ?',
+       'construire_verifications_aucune' => 'Aucune',
+       'construire_verifications_label' => 'Type de vérification à effectuer',
+
+       // E
+       'erreur_generique' => 'Il y a des erreurs dans les champs ci-dessous, veuillez vérifier vos saisies',
+       'erreur_option_nom_unique' => 'Ce nom est déjà utilisé par un autre champ et il doit être unique dans ce formulaire.',
+
+       // I
+       'info_configurer_saisies' => 'Page de test des Saisies',
+
+       // L
+       'label_annee' => 'Année',
+       'label_jour' => 'Jour',
+       'label_mois' => 'Mois',
+
+       // O
+       'option_aff_art_interface_explication' => 'Afficher uniquement les articles de la langue de l’utilisateur',
+       'option_aff_art_interface_label' => 'Affichage multilingue',
+       'option_aff_langue_explication' => 'Affiche la langue de l’article ou rubrique sélectionné devant le titre',
+       'option_aff_langue_label' => 'Afficher la langue',
+       'option_aff_rub_interface_explication' => 'Afficher uniquement les rubriques de la langue de l’utilisateur',
+       'option_aff_rub_interface_label' => 'Affichage multilingue',
+       'option_afficher_si_explication' => 'Indiquez les conditions pour afficher le champ en fonction de la valeur des autres champs. L’identifiant des autres champs doit être mis entre <code>@</code>. <br />Exemple <code>@selection_1@=="Toto"</code> conditionne l’affichage du champ à ce que le champ <code>selection_1</code> ait pour valeur <code>Toto</code>.',
+       'option_afficher_si_label' => 'Affichage conditionnel',
+       'option_afficher_si_remplissage_explication' => 'Contrairement à la précédente option, celle-ci ne conditionne l’affichage que lors du remplissage du formulaire, pas lors de l’affichage des résultats. Sa syntaxe est la même.',
+       'option_afficher_si_remplissage_label' => 'Affichage conditionnel lors du remplissage',
+       'option_attention_explication' => 'Un message plus important que l’explication.',
+       'option_attention_label' => 'Avertissement',
+       'option_autocomplete_defaut' => 'Laisser par défaut',
+       'option_autocomplete_explication' => 'Au chargement de la page, votre navigateur peut pré-remplir le champ en fonction de son historique',
+       'option_autocomplete_label' => 'Pré-remplissage du champ',
+       'option_autocomplete_off' => 'Désactiver',
+       'option_autocomplete_on' => 'Activer',
+       'option_cacher_option_intro_label' => 'Cacher le premier choix vide',
+       'option_choix_alternatif_label' => 'Permettre de proposer un choix alternatif',
+       'option_choix_alternatif_label_defaut' => 'Autre choix',
+       'option_choix_alternatif_label_label' => 'Label de ce choix alternatif',
+       'option_choix_destinataires_explication' => 'Un ou plusieurs auteurs parmis lesquels l’utilisateur pourra faire son choix. Si rien n’est sélectionné, c’est l’auteur qui a installé le site qui sera choisi.',
+       'option_choix_destinataires_label' => 'Destinataires possibles',
+       'option_class_label' => 'Classes CSS supplémentaires',
+       'option_cols_explication' => 'Largeur du bloc en nombre de caractères. Cette option n’est pas toujours appliquée car les styles CSS de votre site peuvent l’annuler.',
+       'option_cols_label' => 'Largeur',
+       'option_datas_explication' => 'Vous devez indiquez un choix par ligne sous la forme "cle|Label du choix"',
+       'option_datas_label' => 'Liste des choix possibles',
+       'option_datas_sous_groupe_explication' => 'Vous devez indiquez un choix par ligne sous la forme "cle|Label" du choix. <br />Vous pouvez indiquer le début d’un sous-groupe sous la forme "*Titre du sous-groupe". Pour finir un sous-groupe vous pouvez en entamez un autre, ou bien mettre une ligne contenant unique "/*".',
+       'option_defaut_label' => 'Valeur par défaut',
+       'option_disable_avec_post_explication' => 'Identique à l’option précédente mais poste quand même la valeur dans un champ caché.',
+       'option_disable_avec_post_label' => 'Désactiver mais poster',
+       'option_disable_explication' => 'Le champ ne peut plus obtenir le focus.',
+       'option_disable_label' => 'Désactiver le champ',
+       'option_erreur_obligatoire_explication' => 'Vous pouvez personnaliser le message d’erreur affiché pour indiquer l’obligation (sinon laisser vide).',
+       'option_erreur_obligatoire_label' => 'Message d’obligation',
+       'option_explication_explication' => 'Si besoin, une courte phrase décrivant l’objet du champ.',
+       'option_explication_label' => 'Explication',
+       'option_groupe_affichage' => 'Affichage',
+       'option_groupe_description' => 'Description',
+       'option_groupe_utilisation' => 'Utilisation',
+       'option_groupe_validation' => 'Validation',
+       'option_heure_pas_explication' => 'Lorsque vous utilisez l’horaire, un menu s’affiche pour aider à saisir heures et minutes. Vous pouvez ici choisir l’intervalle de temps entre chaque choix (par défaut 30min).',
+       'option_heure_pas_label' => 'Intervalle des minutes dans le menu d’aide à la saisie',
+       'option_horaire_label' => 'Horaire',
+       'option_horaire_label_case' => 'Permettre de saisir aussi l’horaire',
+       'option_id_groupe_label' => 'Groupe de mots',
+       'option_info_obligatoire_explication' => 'Vous pouvez modifier l’indication d’obligation par défaut : <i>[Obligatoire]</i>.',
+       'option_info_obligatoire_label' => 'Indication d’obligation',
+       'option_inserer_barre_choix_edition' => 'barre d’édition complète',
+       'option_inserer_barre_choix_forum' => 'barre des forums',
+       'option_inserer_barre_explication' => 'Insère une barre d’outils du porte-plume si ce dernier est activé.',
+       'option_inserer_barre_label' => 'Insérer une barre d’outils',
+       'option_label_case_label' => 'Label placé à côté de la case',
+       'option_label_explication' => 'Le titre qui sera affiché.',
+       'option_label_label' => 'Label',
+       'option_maxlength_explication' => 'L’utilisateur ne pourra pas taper plus de caractères que ce nombre.',
+       'option_maxlength_label' => 'Nombre de caractères maximum',
+       'option_multiple_explication' => 'L’utilisateur pourra sélectionner plusieurs valeurs',
+       'option_multiple_label' => 'Sélection multiple',
+       'option_nom_explication' => 'Un nom informatique qui identifiera le champ. Il ne doit contenir que des caractères alpha-numériques minuscules ou le caractère "_".',
+       'option_nom_label' => 'Nom du champ',
+       'option_obligatoire_label' => 'Champ obligatoire',
+       'option_option_destinataire_intro_label' => 'Label du premier choix vide (lorsque sous forme de liste)',
+       'option_option_intro_label' => 'Label du premier choix vide',
+       'option_option_statut_label' => 'Afficher les statuts',
+       'option_pliable_label' => 'Pliable',
+       'option_pliable_label_case' => 'Le groupe de champs pourra être replié.',
+       'option_plie_label' => 'Déjà plié',
+       'option_plie_label_case' => 'Si le groupe de champs est pliable, il sera déjà plié à l’affichage du formulaire.',
+       'option_previsualisation_explication' => 'Si le porte-plume est activé, ajoute un onglet pour prévisualiser le rendu du texte saisi.',
+       'option_previsualisation_label' => 'Activer la prévisualisation',
+       'option_readonly_explication' => 'Le champ peut être lu, sélectionné, mais pas modifié.',
+       'option_readonly_label' => 'Lecture seule',
+       'option_rows_explication' => 'Hauteur du bloc en nombre de ligne. Cette option n’est pas toujours appliquée car les styles CSS de votre site peuvent l’annuler.',
+       'option_rows_label' => 'Nombre de lignes',
+       'option_size_explication' => 'Largeur du champ en nombre de caractères. Cette option n’est pas toujours appliquée car les styles CSS de votre site peuvent l’annuler.',
+       'option_size_label' => 'Taille du champ',
+       'option_type_choix_plusieurs' => 'Permettre à l’utilisateur de choisir <strong>plusieurs</strong> destinataires.',
+       'option_type_choix_tous' => 'Mettre <strong>tous</strong> ces auteurs en destinataires. L’utilisateur n’aura aucun choix.',
+       'option_type_choix_un' => 'Permettre à l’utilisateur de choisir <strong>un seul</strong> destinataire (sous forme de liste déroulante).',
+       'option_type_choix_un_radio' => 'Permettre à l’utilisateur de choisir <strong>un seul</strong> destinataire (sous forme de liste à puce).',
+       'option_type_explication' => 'En mode "masqué", le contenu du champ ne sera pas visible.',
+       'option_type_label' => 'Type du champ',
+       'option_type_password' => 'Texte masqué lors de la saisie (ex : mot de passe)',
+       'option_type_text' => 'Normal',
+
+       // S
+       'saisie_auteurs_explication' => 'Permet de sélectionner un ou plusieurs auteurs',
+       'saisie_auteurs_titre' => 'Auteurs',
+       'saisie_case_explication' => 'Permet d’activer ou de désactiver quelque chose.',
+       'saisie_case_titre' => 'Case unique',
+       'saisie_checkbox_explication' => 'Permet de choisir plusieurs options avec des cases.',
+       'saisie_checkbox_titre' => 'Cases à cocher',
+       'saisie_date_explication' => 'Permet de saisir une date ? l’aide d’un calendrier',
+       'saisie_date_titre' => 'Date',
+       'saisie_destinataires_explication' => 'Permet de choisir un ou plusieurs destinataires parmis des auteurs pré-sélectionné.',
+       'saisie_destinataires_titre' => 'Destinataires',
+       'saisie_explication_explication' => 'Un texte explicatif général.',
+       'saisie_explication_titre' => 'Explication',
+       'saisie_fieldset_explication' => 'Un cadre qui pourra englober plusieurs champs.',
+       'saisie_fieldset_titre' => 'Groupe de champs',
+       'saisie_file_explication' => 'Envoi d’un fichier',
+       'saisie_file_titre' => 'Fichier',
+       'saisie_hidden_explication' => 'Un champ pré-rempli que l’utilisateur ne pourra pas voir.',
+       'saisie_hidden_titre' => 'Champ caché',
+       'saisie_input_explication' => 'Une simple ligne de texte, pouvant être visible ou masquée (mot de passe).',
+       'saisie_input_titre' => 'Ligne de texte',
+       'saisie_mot_explication' => 'Un ou plusieurs mots-clés d’un groupe de mot',
+       'saisie_mot_titre' => 'Mot-clé',
+       'saisie_oui_non_explication' => 'Oui ou non, c’est clair ? :)',
+       'saisie_oui_non_titre' => 'Oui ou non',
+       'saisie_radio_defaut_choix1' => 'Un',
+       'saisie_radio_defaut_choix2' => 'Deux',
+       'saisie_radio_defaut_choix3' => 'Trois',
+       'saisie_radio_explication' => 'Permet de choisir une option parmis plusieurs disponibles.',
+       'saisie_radio_titre' => 'Boutons radios',
+       'saisie_selecteur_article' => 'Affiche un navigateur de sélection d’article',
+       'saisie_selecteur_article_titre' => 'Sélecteur d’article',
+       'saisie_selecteur_rubrique' => 'Affiche un navigateur de sélection de rubrique',
+       'saisie_selecteur_rubrique_article' => 'Affiche un navigateur de sélection d’article ou de rubrique',
+       'saisie_selecteur_rubrique_article_titre' => 'Sélecteur d’article ou rubrique',
+       'saisie_selecteur_rubrique_titre' => 'Sélecteur de rubrique',
+       'saisie_selection_explication' => 'Choisir une option dans une liste déroulante.',
+       'saisie_selection_multiple_explication' => 'Permet de choisir plusieurs options avec une liste.',
+       'saisie_selection_multiple_titre' => 'Sélection multiple',
+       'saisie_selection_titre' => 'Liste déroulante',
+       'saisie_textarea_explication' => 'Un champ de texte sur plusieurs lignes.',
+       'saisie_textarea_titre' => 'Bloc de texte',
+
+       // T
+       'tous_visiteurs' => 'Tous les visiteurs (même non enregistrés)',
+       'tout_selectionner' => 'Tout sélectionner',
+
+       // V
+       'vue_sans_reponse' => '<i>Sans réponse</i>',
+
+       // Z
+       'z' => 'zzz'
+);
+
+?>
diff --git a/www/plugins/saisies/lang/saisies_fr_tu.php b/www/plugins/saisies/lang/saisies_fr_tu.php
new file mode 100644 (file)
index 0000000..1dbdbc1
--- /dev/null
@@ -0,0 +1,190 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=fr_tu
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // B
+       'bouton_parcourir_docs_article' => 'Parcourir l’article',
+       'bouton_parcourir_docs_breve' => 'Parcourir la brève',
+       'bouton_parcourir_docs_rubrique' => 'Parcourir la rubrique',
+       'bouton_parcourir_mediatheque' => 'Parcourir la médiathèque',
+
+       // C
+       'construire_action_annuler' => 'Annuler',
+       'construire_action_configurer' => 'Configurer',
+       'construire_action_deplacer' => 'Déplacer',
+       'construire_action_dupliquer' => 'Dupliquer',
+       'construire_action_dupliquer_copie' => '(copie)',
+       'construire_action_supprimer' => 'Supprimer',
+       'construire_ajouter_champ' => 'Ajouter un champ',
+       'construire_attention_enregistrer' => 'N’oublie pas d’enregistrer tes modifications !',
+       'construire_attention_modifie' => 'Le formulaire ci-dessous est différent du formulaire initial. Tu as la possibilité de le réinitialiser à son état avant tes modifications.',
+       'construire_attention_supprime' => 'Tes modifications comportent des suppressions de champs. Confirme l’enregistrement de cette nouvelle version du formulaire.',
+       'construire_aucun_champs' => 'Il n’y a pour l’instant aucun champ dans ce formulaire.',
+       'construire_confirmer_supprimer_champ' => 'Veux-tu vraiment supprimer ce champ ?',
+       'construire_info_nb_champs_masques' => '@nb@ champ(s) masqué(s) le temps de configurer le groupe.',
+       'construire_position_explication' => 'Indique devant quel autre champ sera placé celui-ci.',
+       'construire_position_fin_formulaire' => 'À la fin du formulaire',
+       'construire_position_fin_groupe' => 'À la fin du groupe @groupe@',
+       'construire_position_label' => 'Position du champ',
+       'construire_reinitialiser' => 'Réinitialiser le formulaire',
+       'construire_reinitialiser_confirmer' => 'Tu vas perdre toutes tes modifications. Es-tu sûr de vouloir revenir au formulaire initial ?',
+       'construire_verifications_aucune' => 'Aucune',
+       'construire_verifications_label' => 'Type de vérification à effectuer',
+
+       // E
+       'erreur_generique' => 'Il y a des erreurs dans les champs ci-dessous, vérifie tes saisies',
+       'erreur_option_nom_unique' => 'Ce nom est déjà utilisé par un autre champ et il doit être unique dans ce formulaire.',
+
+       // I
+       'info_configurer_saisies' => 'Page de test des Saisies',
+
+       // L
+       'label_annee' => 'Année',
+       'label_jour' => 'Jour',
+       'label_mois' => 'Mois',
+
+       // O
+       'option_aff_art_interface_explication' => 'Afficher uniquement les articles de la langue de l’utilisateur',
+       'option_aff_art_interface_label' => 'Affichage multilingue',
+       'option_aff_langue_explication' => 'Affiche la langue de l’article ou rubrique sélectionné devant le titre',
+       'option_aff_langue_label' => 'Afficher la langue',
+       'option_aff_rub_interface_explication' => 'Afficher uniquement les rubriques de la langue de l’utilisateur',
+       'option_aff_rub_interface_label' => 'Affichage multilingue',
+       'option_afficher_si_explication' => 'Indique les conditions pour afficher le champ en fonction de la valeur des autres champs. L’identifiant des autres champs doit être mis entre <code>@</code>. <br />Exemple <code>@selection_1@=="Toto"</code> conditionne l’affichage du champ à ce que le champ <code>selection_1</code> ait pour valeur <code>Toto</code>.',
+       'option_afficher_si_label' => 'Affichage conditionnel',
+       'option_afficher_si_remplissage_explication' => 'Contrairement à la précédente option, celle-ci ne conditionne l’affichage que lors du remplissage du formulaire, pas lors de l’affichage des résultats. Sa syntaxe est la même.',
+       'option_afficher_si_remplissage_label' => 'Affichage conditionnel lors du remplissage',
+       'option_attention_explication' => 'Un message plus important que l’explication.',
+       'option_attention_label' => 'Avertissement',
+       'option_autocomplete_defaut' => 'Laisser par défaut',
+       'option_autocomplete_explication' => 'Au chargement de la page, ton navigateur peut pré-remplir le champ en fonction de son historique',
+       'option_autocomplete_label' => 'Pré-remplissage du champ',
+       'option_autocomplete_off' => 'Désactiver',
+       'option_autocomplete_on' => 'Activer',
+       'option_cacher_option_intro_label' => 'Cacher le premier choix vide',
+       'option_choix_alternatif_label' => 'Permettre de proposer un choix alternatif',
+       'option_choix_alternatif_label_defaut' => 'Autre choix',
+       'option_choix_alternatif_label_label' => 'Label de ce choix alternatif',
+       'option_choix_destinataires_explication' => 'Un ou plusieurs auteurs parmis lesquels l’utilisateur pourra faire son choix. Si rien n’est sélectionné, c’est l’auteur qui a installé le site qui sera choisi.',
+       'option_choix_destinataires_label' => 'Destinataires possibles',
+       'option_class_label' => 'Classes CSS supplémentaires',
+       'option_cols_explication' => 'Largeur du bloc en nombre de caractères. Cette option n’est pas toujours appliquée car les styles CSS de ton site peuvent l’annuler.',
+       'option_cols_label' => 'Largeur',
+       'option_datas_explication' => 'Tu dois indiquer un choix par ligne sous la forme "cle|Label du choix"',
+       'option_datas_label' => 'Liste des choix possibles',
+       'option_datas_sous_groupe_explication' => 'Tu dois indiquez un choix par ligne sous la forme "cle|Label" du choix. <br />Tu peux indiquer le début d’un sous-groupe sous la forme "*Titre du sous-groupe". Pour finir un sous-groupe tu peux en entamez un autre, ou bien mettre une ligne contenant unique "/*".',
+       'option_defaut_label' => 'Valeur par défaut',
+       'option_disable_avec_post_explication' => 'Identique à l’option précédente mais poste quand même la valeur dans un champ caché.',
+       'option_disable_avec_post_label' => 'Désactiver mais poster',
+       'option_disable_explication' => 'Le champ ne peut plus obtenir le focus.',
+       'option_disable_label' => 'Désactiver le champ',
+       'option_erreur_obligatoire_explication' => 'Tu peux personnaliser le message d’erreur affiché pour indiquer l’obligation (sinon laisser vide).',
+       'option_erreur_obligatoire_label' => 'Message d’obligation',
+       'option_explication_explication' => 'Si besoin, une courte phrase décrivant l’objet du champ.',
+       'option_explication_label' => 'Explication',
+       'option_groupe_affichage' => 'Affichage',
+       'option_groupe_description' => 'Description',
+       'option_groupe_utilisation' => 'Utilisation',
+       'option_groupe_validation' => 'Validation',
+       'option_heure_pas_explication' => 'Lorsque tu utilises l’horaire, un menu s’affiche pour aider à saisir heures et minutes. Tu peux ici choisir l’intervalle de temps entre chaque choix (par défaut 30min).',
+       'option_heure_pas_label' => 'Intervalle des minutes dans le menu d’aide à la saisie',
+       'option_horaire_label' => 'Horaire',
+       'option_horaire_label_case' => 'Permettre de saisie aussi l’horaire',
+       'option_info_obligatoire_explication' => 'Tu peux modifier l’indication d’obligation par défaut : <i>[Obligatoire]</i>.',
+       'option_info_obligatoire_label' => 'Indication d’obligation',
+       'option_inserer_barre_choix_edition' => 'barre d’édition complète',
+       'option_inserer_barre_choix_forum' => 'barre des forums',
+       'option_inserer_barre_explication' => 'Insère une barre d’outils du porte-plume si ce dernier est activé.',
+       'option_inserer_barre_label' => 'Insérer une barre d’outils',
+       'option_label_case_label' => 'Label placé à côté de la case',
+       'option_label_explication' => 'Le titre qui sera affiché.',
+       'option_label_label' => 'Label',
+       'option_maxlength_explication' => 'L’utilisateur ne pourra pas taper plus de caractères que ce nombre.',
+       'option_maxlength_label' => 'Nombre de caractères maximum',
+       'option_multiple_explication' => 'L’utilisateur pourra sélectionner plusieurs valeurs',
+       'option_multiple_label' => 'Sélection multiple',
+       'option_nom_explication' => 'Un nom informatique qui identifiera le champ. Il ne doit contenir que des caractères alpha-numériques minuscules ou le caractère "_".',
+       'option_nom_label' => 'Nom du champ',
+       'option_obligatoire_label' => 'Champ obligatoire',
+       'option_option_destinataire_intro_label' => 'Label du premier choix vide (lorsque sous forme de liste)',
+       'option_option_intro_label' => 'Label du premier choix vide',
+       'option_option_statut_label' => 'Afficher les statuts',
+       'option_pliable_label' => 'Pliable',
+       'option_pliable_label_case' => 'Le groupe de champs pourra être replié.',
+       'option_plie_label' => 'Déjà plié',
+       'option_plie_label_case' => 'Si le groupe de champs est pliable, il sera déjà plié à l’affichage du formulaire.',
+       'option_previsualisation_explication' => 'Si le porte-plume est activé, ajoute un onglet pour prévisualiser le rendu du texte saisi.',
+       'option_previsualisation_label' => 'Activer la prévisualisation',
+       'option_readonly_explication' => 'Le champ peut être lu, sélectionné, mais pas modifié.',
+       'option_readonly_label' => 'Lecture seule',
+       'option_rows_explication' => 'Hauteur du bloc en nombre de ligne. Cette option n’est pas toujours appliquée car les styles CSS de votre site peuvent l’annuler.',
+       'option_rows_label' => 'Nombre de lignes',
+       'option_size_explication' => 'Largeur du champ en nombre de caractères. Cette option n’est pas toujours appliquée car les styles CSS de ton site peuvent l’annuler.',
+       'option_size_label' => 'Taille du champ',
+       'option_type_choix_plusieurs' => 'Permettre à l’utilisateur de choisir <strong>plusieurs</strong> destinataires.',
+       'option_type_choix_tous' => 'Mettre <strong>tous</strong> ces auteurs en destinataires. L’utilisateur n’aura aucun choix.',
+       'option_type_choix_un' => 'Permettre à l’utilisateur de choisir <strong>un seul</strong> destinataire.Permettre à l’utilisateur de choisir <strong>un seul</strong> destinataire (sous forme de liste déroulante).',
+       'option_type_choix_un_radio' => 'Permettre à l’utilisateur de choisir <strong>un seul</strong> destinataire (sous forme de liste à puce).',
+       'option_type_explication' => 'En mode "masqué", le contenu du champ ne sera pas visible.',
+       'option_type_label' => 'Type du champ',
+       'option_type_password' => 'Texte masqué lors de la saisie (ex : mot de passe)',
+       'option_type_text' => 'Normal',
+
+       // S
+       'saisie_auteurs_explication' => 'Permet de sélectionner un ou plusieurs auteurs',
+       'saisie_auteurs_titre' => 'Auteurs',
+       'saisie_case_explication' => 'Permet d’activer ou de désactiver quelque chose.',
+       'saisie_case_titre' => 'Case unique',
+       'saisie_checkbox_explication' => 'Permet de choisir plusieurs options avec des cases.',
+       'saisie_checkbox_titre' => 'Cases à cocher',
+       'saisie_date_explication' => 'Permet de saisir une date ? l’aide d’un calendrier',
+       'saisie_date_titre' => 'Date',
+       'saisie_destinataires_explication' => 'Permet de choisir un ou plusieurs destinataires parmis des auteurs pré-sélectionné.',
+       'saisie_destinataires_titre' => 'Destinataires',
+       'saisie_explication_explication' => 'Un texte explicatif général.',
+       'saisie_explication_titre' => 'Explication',
+       'saisie_fieldset_explication' => 'Un cadre qui pourra englober plusieurs champs.',
+       'saisie_fieldset_titre' => 'Groupe de champs',
+       'saisie_file_explication' => 'Envoi d’un fichier',
+       'saisie_file_titre' => 'Fichier',
+       'saisie_hidden_explication' => 'Un champ pré-rempli que l’utilisateur ne pourra pas voir.',
+       'saisie_hidden_titre' => 'Champ caché',
+       'saisie_input_explication' => 'Une simple ligne de texte, pouvant être visible ou masquée (mot de passe).',
+       'saisie_input_titre' => 'Ligne de texte',
+       'saisie_oui_non_explication' => 'Oui ou non, c’est clair ? :)',
+       'saisie_oui_non_titre' => 'Oui ou non',
+       'saisie_radio_defaut_choix1' => 'Un',
+       'saisie_radio_defaut_choix2' => 'Deux',
+       'saisie_radio_defaut_choix3' => 'Trois',
+       'saisie_radio_explication' => 'Permet de choisir une option parmis plusieurs disponibles.',
+       'saisie_radio_titre' => 'Boutons radios',
+       'saisie_selecteur_article' => 'Affiche un navigateur de sélection d’article',
+       'saisie_selecteur_article_titre' => 'Sélecteur d’article',
+       'saisie_selecteur_rubrique' => 'Affiche un navigateur de sélection de rubrique',
+       'saisie_selecteur_rubrique_article' => 'Affiche un navigateur de sélection d’article ou de rubrique',
+       'saisie_selecteur_rubrique_article_titre' => 'Sélecteur d’article ou rubrique',
+       'saisie_selecteur_rubrique_titre' => 'Sélecteur de rubrique',
+       'saisie_selection_explication' => 'Choisir une option dans une liste déroulante.',
+       'saisie_selection_multiple_explication' => 'Permet de choisir plusieurs options avec une liste.',
+       'saisie_selection_multiple_titre' => 'Sélection multiple',
+       'saisie_selection_titre' => 'Liste déroulante',
+       'saisie_textarea_explication' => 'Un champ de texte sur plusieurs lignes.',
+       'saisie_textarea_titre' => 'Bloc de texte',
+
+       // T
+       'tous_visiteurs' => 'Tous les visiteurs (même non enregistrés)',
+       'tout_selectionner' => 'Tout sélectionner',
+
+       // V
+       'vue_sans_reponse' => '<i>Sans réponse</i>',
+
+       // Z
+       'z' => 'zzz'
+);
+
+?>
diff --git a/www/plugins/saisies/lang/saisies_it.php b/www/plugins/saisies/lang/saisies_it.php
new file mode 100644 (file)
index 0000000..de77118
--- /dev/null
@@ -0,0 +1,173 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=it
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // B
+       'bouton_parcourir_docs_article' => 'Sfoglia l’articolo',
+       'bouton_parcourir_docs_breve' => 'Sfoglia la breve',
+       'bouton_parcourir_docs_rubrique' => 'Sfoglia la rubrica',
+       'bouton_parcourir_mediatheque' => 'Sfoglia la mediateca',
+
+       // C
+       'construire_action_annuler' => 'Annulla',
+       'construire_action_configurer' => 'Configura',
+       'construire_action_deplacer' => 'Sposta',
+       'construire_action_dupliquer' => 'Duplica',
+       'construire_action_dupliquer_copie' => '(copia)',
+       'construire_action_supprimer' => 'Elimina',
+       'construire_ajouter_champ' => 'Aggiungi un campo',
+       'construire_attention_enregistrer' => 'Non dimenticare di salvare le tue modifiche!',
+       'construire_attention_modifie' => 'Il modulo in oggetto è diverso dal modulo iniziale. Hai la possibilità di reinizializzare il suo stato a quello precedente alle modifiche.',
+       'construire_attention_supprime' => 'Le modifiche includono l’eliminazione di alcuni campi. Conferma il salvataggio di questa nuova versione del modulo.',
+       'construire_aucun_champs' => 'Al momento non è presente alcun campo in questo modulo.',
+       'construire_confirmer_supprimer_champ' => 'Vuoi veramente eliminare questo campo?',
+       'construire_info_nb_champs_masques' => '@nb@ campo(i) con maschera. Configura il gruppo.',
+       'construire_position_explication' => 'Indica prima di quale altro campo sarà spostato quello corrente.',
+       'construire_position_fin_formulaire' => 'Alla fine del modulo',
+       'construire_position_fin_groupe' => 'Alla fine del gruppo @groupe@',
+       'construire_position_label' => 'Posizione del campo',
+       'construire_reinitialiser' => 'Reinizializza il modulo',
+       'construire_reinitialiser_confirmer' => 'Perderai tutte le modifiche. Sei sicuro di voler tornare al modulo iniziale?',
+       'construire_verifications_aucune' => 'Nessuna',
+       'construire_verifications_label' => 'Tipo di verifica da effettuare',
+
+       // E
+       'erreur_generique' => 'Ci sono degli errori nei campi di seguito, si prega di verificare gli inserimenti',
+       'erreur_option_nom_unique' => 'Questo nome è già utilizzato da un altro campo e deve essere univoco all’interno del modulo.',
+
+       // I
+       'info_configurer_saisies' => 'Pagina di test di Saisies',
+
+       // L
+       'label_annee' => 'Anno',
+       'label_jour' => 'Giorno',
+       'label_mois' => 'Mese',
+
+       // O
+       'option_aff_art_interface_explication' => 'Mostra unicamente gli articoli della lingua dell’utente',
+       'option_aff_art_interface_label' => 'Visualizzazione multilingua',
+       'option_aff_langue_explication' => 'Mostra la lingua dell’articolo o della rubrica selezionata davanti al titolo',
+       'option_aff_langue_label' => 'Mostra la lingua',
+       'option_aff_rub_interface_explication' => 'Mostra unicamente le rubriche della lingua dell’utente',
+       'option_aff_rub_interface_label' => 'Visualizzazione multilingua',
+       'option_attention_explication' => 'Un messaggio più importante dei una spiegazione.',
+       'option_attention_label' => 'Avvertimento',
+       'option_autocomplete_defaut' => 'Lascia predefinito',
+       'option_autocomplete_explication' => 'Al caricamento della pagina, il tuo navigatore può preimpostare il campo in funzione della sua storia',
+       'option_autocomplete_label' => 'Preimpostazione del campo',
+       'option_autocomplete_off' => 'Disattiva',
+       'option_autocomplete_on' => 'Attiva',
+       'option_cacher_option_intro_label' => 'Nascondi la prima scelta vuota',
+       'option_choix_destinataires_explication' => 'Uno o più autori tra i quali l’utente potrà fare una scelta. Se non si seleziona niente, è l’autore che ha installato il sito che sarà scelto.',
+       'option_choix_destinataires_label' => 'Possibili destinatari',
+       'option_class_label' => 'Classi CSS supplementari',
+       'option_cols_explication' => 'Larghezza del blocco in numero di caratteri. Questa opzione non è sempre applicata poichè gli stili CSS la possono annullare.',
+       'option_cols_label' => 'Larghezza',
+       'option_datas_explication' => 'Indica una scelta per riga con il formato "chiave|Etichetta della scelta"', # MODIF
+       'option_datas_label' => 'Elenco delle scelte possibili',
+       'option_defaut_label' => 'Valore predefinito',
+       'option_disable_avec_post_explication' => 'Identica all’opzione precedente ma invia lo stesso il valore in un campo nascosto.',
+       'option_disable_avec_post_label' => 'Disattiva ma invia',
+       'option_disable_explication' => 'Il campo non può ottenere il focus.',
+       'option_disable_label' => 'Disattiva il campo',
+       'option_explication_explication' => 'Se necessario, una frase breve che descrive il campo.',
+       'option_explication_label' => 'Spiegazione',
+       'option_groupe_affichage' => 'Visualizzazione',
+       'option_groupe_description' => 'Descrizione',
+       'option_groupe_utilisation' => 'Utilizzazione',
+       'option_groupe_validation' => 'Validazione',
+       'option_info_obligatoire_explication' => 'Puoi modificare l’indicazione predefinita per i campi obbligatori : <i>[Obbligatorio]</i>.',
+       'option_info_obligatoire_label' => 'Indicazione obbligatorio',
+       'option_inserer_barre_choix_edition' => 'barra del testo completa',
+       'option_inserer_barre_choix_forum' => 'barra dei forum',
+       'option_inserer_barre_explication' => 'Inserisci una barra del testo se disponibile (porte-plume attivo).',
+       'option_inserer_barre_label' => 'Inserisci una barra di utility',
+       'option_label_case_label' => 'Etichetta a lato della casella',
+       'option_label_explication' => 'Il titolo che sarà mostrato.',
+       'option_label_label' => 'Etichetta',
+       'option_maxlength_explication' => 'L’utente non può digiatare più caratteri del numero qui indicato.',
+       'option_maxlength_label' => 'Numero massimo di caratteri',
+       'option_multiple_explication' => 'L’utente può selezionare più valori',
+       'option_multiple_label' => 'Scelta multipla',
+       'option_nom_explication' => 'Un nome informatico che indentifica il campo. Deve contentere solo caratteri alfanumerici minuscoli o il carattere "_".',
+       'option_nom_label' => 'Nome del campo',
+       'option_obligatoire_label' => 'Campo obbligatorio',
+       'option_option_intro_label' => 'Etichetta del primo campo vuoto',
+       'option_option_statut_label' => 'Mostra gli stati',
+       'option_pliable_label' => 'Richiudibile',
+       'option_pliable_label_case' => 'Il gruppo di campi può essere chiuso.',
+       'option_plie_label' => 'Già chiuso',
+       'option_plie_label_case' => 'Se il gruppo di campi è richiudibile, sarà già chiuso alla visualizzazione del modulo.',
+       'option_previsualisation_explication' => 'Se porte-plume è attivo, aggiungi una scheda per previsualizzare la resa del testo inserito.',
+       'option_previsualisation_label' => 'Attiva la previsualizzazione',
+       'option_readonly_explication' => 'Il campo può essere letto, selezionato, ma non modificato.',
+       'option_readonly_label' => 'Sola lettura',
+       'option_rows_explication' => 'Altezza del blocco in numero ri righe. Questa opzione non è sempre applicata poichè gli stili CSS del sito potrebbero annullarla.',
+       'option_rows_label' => 'Numero di righe',
+       'option_size_explication' => 'Larghezza del campo in numero di caratteri. Questa opzione non è sempre applicata poich%egrave; gli stili CSS del sito potrebbero annullarla.',
+       'option_size_label' => 'Dimensione del campo',
+       'option_type_choix_plusieurs' => 'Consenti all’utente di scegliere <strong>più</strong> destinatari.',
+       'option_type_choix_tous' => 'Imposta <strong>tutti</strong> questi autori come destinatari. L’utente non avrà alcuna scelta.',
+       'option_type_choix_un' => 'Consenti all’utente di scegliere <strong>un solo</strong> destinatario.', # MODIF
+       'option_type_explication' => 'In modalità "mascherata", il contenuto del campo non sarà visibile.',
+       'option_type_label' => 'Tipo del campo',
+       'option_type_password' => 'Mascherato', # MODIF
+       'option_type_text' => 'Normale',
+
+       // S
+       'saisie_auteurs_explication' => 'Consente di selezionare uno o più autori',
+       'saisie_auteurs_titre' => 'Autori',
+       'saisie_case_explication' => 'Consente di attivare o disattivare qualcosa.',
+       'saisie_case_titre' => 'Casella di spunta',
+       'saisie_checkbox_explication' => 'Consente di scegliere più opzioni da spuntare.',
+       'saisie_checkbox_titre' => 'Caselle di spunta',
+       'saisie_date_explication' => 'Consente di inserire una data con l’aiuto di un calendario',
+       'saisie_date_titre' => 'Data',
+       'saisie_destinataires_explication' => 'Consente di scegliere uno o più destinatari tra gli autore selezionati.',
+       'saisie_destinataires_titre' => 'Destinatari',
+       'saisie_explication_explication' => 'Un testo esplicativo generale.',
+       'saisie_explication_titre' => 'Spiegazione',
+       'saisie_fieldset_explication' => 'Un blocco che può contenere più campi.',
+       'saisie_fieldset_titre' => 'Gruppo di campi',
+       'saisie_file_explication' => 'Invio di un file',
+       'saisie_file_titre' => 'File',
+       'saisie_hidden_explication' => 'Un campo preimpostato che l’utente non potrà vedere.',
+       'saisie_hidden_titre' => 'Campo nascosto',
+       'saisie_input_explication' => 'Una semplice riga di testo, che può essere visibile o mascherata (password).',
+       'saisie_input_titre' => 'Riga di testo',
+       'saisie_oui_non_explication' => 'Si o no',
+       'saisie_oui_non_titre' => 'Si o no',
+       'saisie_radio_defaut_choix1' => 'Uno',
+       'saisie_radio_defaut_choix2' => 'Due',
+       'saisie_radio_defaut_choix3' => 'Tre',
+       'saisie_radio_explication' => 'Consente di scegliere un’opzione tra più disponibili.',
+       'saisie_radio_titre' => 'Scelta unica',
+       'saisie_selecteur_article' => 'Mostra un navigatore per la selezione di un articolo',
+       'saisie_selecteur_article_titre' => 'Selettore d’articolo',
+       'saisie_selecteur_rubrique' => 'Mostra un navigatore per la selezione di una rubrica',
+       'saisie_selecteur_rubrique_article' => 'Mostra un navigatore per la selezione di un articolo o di una rubrica',
+       'saisie_selecteur_rubrique_article_titre' => 'Selettore d’articolo o rubrica',
+       'saisie_selecteur_rubrique_titre' => 'Selettore di rubrica',
+       'saisie_selection_explication' => 'Scegli una opzione nel menu a tendina.',
+       'saisie_selection_multiple_explication' => 'Consente di scegliere più opzioni con un elenco.',
+       'saisie_selection_multiple_titre' => 'Scelta multipla',
+       'saisie_selection_titre' => 'Menu a tendina',
+       'saisie_textarea_explication' => 'Un campo di testo su più linee.',
+       'saisie_textarea_titre' => 'Blocco di testo',
+
+       // T
+       'tous_visiteurs' => 'Tutti gli utenti (anche non registrati)',
+
+       // V
+       'vue_sans_reponse' => '<i>Senza risposta</i>',
+
+       // Z
+       'z' => 'zzz'
+);
+
+?>
diff --git a/www/plugins/saisies/lang/saisies_nl.php b/www/plugins/saisies/lang/saisies_nl.php
new file mode 100644 (file)
index 0000000..76d61d5
--- /dev/null
@@ -0,0 +1,193 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=nl
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // B
+       'bouton_parcourir_docs_article' => 'Blader door het artikel',
+       'bouton_parcourir_docs_breve' => 'Blader door het nieuwsbericht',
+       'bouton_parcourir_docs_rubrique' => 'Blader door de rubriek',
+       'bouton_parcourir_mediatheque' => 'Blader door de mediatheek',
+
+       // C
+       'construire_action_annuler' => 'Annuleren',
+       'construire_action_configurer' => 'Instellen',
+       'construire_action_deplacer' => 'Verplaats',
+       'construire_action_dupliquer' => 'Kopieer',
+       'construire_action_dupliquer_copie' => '(copy)',
+       'construire_action_supprimer' => 'Verwijder',
+       'construire_ajouter_champ' => 'Voeg veld toe',
+       'construire_attention_enregistrer' => 'Sla je wijzingen op!',
+       'construire_attention_modifie' => 'Het onderstaande formulier wijkt van het oorspronkelijke af. Je kunt het naar de oorspronkelijke staat herstellen.',
+       'construire_attention_supprime' => 'Je wijzigingen bevatten verwijderingen van velden. Bevestig de nieuwe formulierversie.',
+       'construire_aucun_champs' => 'Dit formulier heeft geen velden.',
+       'construire_confirmer_supprimer_champ' => 'Wil je dit veld echt verwijderen?',
+       'construire_info_nb_champs_masques' => '@nb@ verborgen veld(en) bij het opzetten van de groep.',
+       'construire_position_explication' => 'Geef aan voor welk veld dit veld moet worden geplaatst.',
+       'construire_position_fin_formulaire' => 'Aan het eind van het formulier',
+       'construire_position_fin_groupe' => 'Aan het eind van groep @groupe@',
+       'construire_position_label' => 'Positie van het veld',
+       'construire_reinitialiser' => 'Formulier resetten',
+       'construire_reinitialiser_confirmer' => 'Je verliest alle aanpassingen. Weet je zeker dat je naar het originele formulier terug wilt?',
+       'construire_verifications_aucune' => 'Geen',
+       'construire_verifications_label' => 'Toe te passen verificatie',
+
+       // E
+       'erreur_generique' => 'Er zitten fouten in onderstaande velden. Controleer je invoer',
+       'erreur_option_nom_unique' => 'Deze naam wordt al door een ander veld gebruikt. Het moet binnen het formulier een unieke naam hebben.',
+
+       // I
+       'info_configurer_saisies' => 'Testbladzijde voor invoer',
+
+       // L
+       'label_annee' => 'Jaar',
+       'label_jour' => 'Dag',
+       'label_mois' => 'Maand',
+
+       // O
+       'option_aff_art_interface_explication' => 'Toon uitsluitend artikelen in de gebruikerstaal',
+       'option_aff_art_interface_label' => 'Meertalige display',
+       'option_aff_langue_explication' => 'Toon de gekozen taal voor de titel van het artikel of de rubriek',
+       'option_aff_langue_label' => 'Toon d etaal',
+       'option_aff_rub_interface_explication' => 'Toon alleen rubrieken in de gebruikerstaal',
+       'option_aff_rub_interface_label' => 'Meertalige display',
+       'option_afficher_si_explication' => 'Geef de voorwaarde op voor het vertonen van het veld in functie van de waarde van andere velden. De identificatie van de andere velden moet tussen <code>@</code> worden geplaatst. <br />Bijvoorbeeld <code>@selection_1@=="Toto"</code> geeft de voorwaarde aan dat het veld moet worden getoond wanneer veld <code>selection_1</code> de waarde <code>Toto</code> heeft.',
+       'option_afficher_si_label' => 'Tonen onder voorwaarde',
+       'option_afficher_si_remplissage_explication' => 'In tegenstelling met de vorige optie verschijnt deze wanneer het formulier wordt getoond en niet wanneer de resultaten worden getoond. De syntax is dezelfde.',
+       'option_afficher_si_remplissage_label' => 'Tonen indien ingevuld',
+       'option_attention_explication' => 'Een boodschap die belangrijker is dan de uitleg.',
+       'option_attention_label' => 'Waarschuwing',
+       'option_autocomplete_defaut' => 'Neem de standaardwaarde',
+       'option_autocomplete_explication' => 'Bij het laden kan de webbrowser de velden met historische informatie vullen',
+       'option_autocomplete_label' => 'Vul de velden vooraf in',
+       'option_autocomplete_off' => 'Niet',
+       'option_autocomplete_on' => 'Wel',
+       'option_cacher_option_intro_label' => 'Verberg de eerste lege keuze',
+       'option_choix_alternatif_label' => 'Toelaten een andere keuze voor te stellen',
+       'option_choix_alternatif_label_defaut' => 'Andere keuze',
+       'option_choix_alternatif_label_label' => 'Label voor deze alternatieve keuze',
+       'option_choix_destinataires_explication' => 'Een of meer auteurs waaruit de gebruiker kan kiezen. Standaard wordt de auteur die de site maakte gekozen.',
+       'option_choix_destinataires_label' => 'Mogelijke ontvangers',
+       'option_class_label' => 'Extra CSS Classes',
+       'option_cols_explication' => 'Veldbreedte in tekens. Deze optie kan door CSS overschreven worden.',
+       'option_cols_label' => 'Breedte',
+       'option_datas_explication' => 'Je moet voor elke rij in het formulier een keuze opgeven in het formaat "key|label of the choice"',
+       'option_datas_label' => 'Lijst van mogelijke keuzes',
+       'option_datas_sous_groupe_explication' => 'Je moet per regel een keuze aangeven in de vorm van "sleutel|Label" van de keuze. <br />Je kunt het begin van een subgroep aangeven met "*Titel van de subgroep". Om een subgroep af te sluiten kun je een nieuwe beginnen of een regel met uitsluitend "/*" invoegen.',
+       'option_defaut_label' => 'Standaardwaarde',
+       'option_disable_avec_post_explication' => 'Als de vorige optie maar de waarde gaat in een verborgen veld.',
+       'option_disable_avec_post_label' => 'Geblokkeerd maar gepost.',
+       'option_disable_explication' => 'Dit veld kan geen focus krijgen.',
+       'option_disable_label' => 'Blokkeer het veld',
+       'option_erreur_obligatoire_explication' => 'Hier kun je de standaard tekst voor een foutboodschap ivm verplichte invoer instellen (anders leeglaten).',
+       'option_erreur_obligatoire_label' => 'Bericht verplicht veld',
+       'option_explication_explication' => 'Indien nodig, een korte omschrijving van het betroffen veld.',
+       'option_explication_label' => 'Uitleg',
+       'option_groupe_affichage' => 'Tonen',
+       'option_groupe_description' => 'Omschrijving',
+       'option_groupe_utilisation' => 'Gebruik',
+       'option_groupe_validation' => 'Validatie',
+       'option_heure_pas_explication' => 'Een hulpmenu laat je het tijdstip in een uurrooster kiezen. Je kunt hier de tijdinterval tussen twee tijdstippen instellen (standaard 30 min).',
+       'option_heure_pas_label' => 'Interval in minuten in het hulpmenu',
+       'option_horaire_label' => 'Uurrooster',
+       'option_horaire_label_case' => 'Ook het uurrooster opnemen',
+       'option_id_groupe_label' => 'Trefwoordengroep',
+       'option_info_obligatoire_explication' => 'Je kan de standaard indicatie aanpassen van verplichting: <i>[Obligatoire]</i>.',
+       'option_info_obligatoire_label' => 'Indicatie verplichting',
+       'option_inserer_barre_choix_edition' => 'edit toolbar compleet',
+       'option_inserer_barre_choix_forum' => 'forum toolbar',
+       'option_inserer_barre_explication' => 'Voeg een porte-plume toolbar toe indien geactiveerd.',
+       'option_inserer_barre_label' => 'Voeg een toolbar toe',
+       'option_label_case_label' => 'Label staat naast de checkbox',
+       'option_label_explication' => 'De titel die zal worden weergegeven.',
+       'option_label_label' => 'Label',
+       'option_maxlength_explication' => 'De gebruikers kan niet meer tekens invoeren dan dit aantal.',
+       'option_maxlength_label' => 'Maximum aantal tekens',
+       'option_multiple_explication' => 'De gebruiker kan meerdere waardes kiezen',
+       'option_multiple_label' => 'Meerdere keuzes',
+       'option_nom_explication' => 'Een ID-naam dat het veld identificeert. Het mag bestaan uit kleine letters of een underscore teken "_".',
+       'option_nom_label' => 'Veldnaam',
+       'option_obligatoire_label' => 'Verplicht veld',
+       'option_option_destinataire_intro_label' => 'Label voor de eerste lege keuze (wanneer in lijstvorm)',
+       'option_option_intro_label' => 'Label voor de eerste lege keuze',
+       'option_option_statut_label' => 'Toon de status',
+       'option_pliable_label' => 'Uitvouwbaar',
+       'option_pliable_label_case' => 'De group velden kan worden uit- en ingevouwen.',
+       'option_plie_label' => 'Al ingevouwen',
+       'option_plie_label_case' => 'Als de groep kan worden in- en uitgevouwen, zorgt deze optie ervoor dat hij zal zijn ingevouwen wanneer het formulier wordt getoond.',
+       'option_previsualisation_explication' => 'Wanneer porte-plume actief is, wordt een preview tab toegevoegd.',
+       'option_previsualisation_label' => 'Preview activeren',
+       'option_readonly_explication' => 'Het veld kan worden bekeken, geselecteerd, maar niet worden aangepast.',
+       'option_readonly_label' => 'Alleen lezen',
+       'option_rows_explication' => 'Veldhoogte in regels. Deze optie kan door CSS worden overschreven.',
+       'option_rows_label' => 'Aantal regels',
+       'option_size_explication' => 'Veldbreedte in tekens. Deze optie kan door CSS worden overschreven.',
+       'option_size_label' => 'Veldbreedte',
+       'option_type_choix_plusieurs' => 'Laat de gebruiker <strong>meerdere</ strong> ontvangers kiezen.',
+       'option_type_choix_tous' => 'Maak <strong>alle</ strong> auteurs ontvanger. De gebruiker kan niet kiezen.',
+       'option_type_choix_un' => 'Laat de gebruiker <strong>een enkele</strong> ontvanger kiezen.',
+       'option_type_choix_un_radio' => 'Sta de gebruiker toe <strong>een enkele</strong> geadresseerde te kiezen (door middel van een lijst).',
+       'option_type_explication' => 'In "discrete" modus wordt de inhoud door sterretjes vervangen.',
+       'option_type_label' => 'Veldtype',
+       'option_type_password' => 'Verborgen tekst tijdens invoer (bv: wachtwoord)',
+       'option_type_text' => 'Normaal',
+
+       // S
+       'saisie_auteurs_explication' => 'Laat je een of meer auteurs kiezen',
+       'saisie_auteurs_titre' => 'Auteurs',
+       'saisie_case_explication' => 'Activeer of disactiveer een bepaalde optie.',
+       'saisie_case_titre' => 'Enkele checkbox',
+       'saisie_checkbox_explication' => 'Voor het kiezen van verschillende opties met checkboxes.',
+       'saisie_checkbox_titre' => 'Checkboxes',
+       'saisie_date_explication' => 'Datuminvoer via een kalender-tool',
+       'saisie_date_titre' => 'Datum',
+       'saisie_destinataires_explication' => 'Voor het kiezen van een of meer ontvangers uit voorgeselecteerde auteurs.',
+       'saisie_destinataires_titre' => 'Ontvangers',
+       'saisie_explication_explication' => 'Een algemene omschrijving.',
+       'saisie_explication_titre' => 'Uitleg',
+       'saisie_fieldset_explication' => 'Een kader dat meerdere velden kan bevatten.',
+       'saisie_fieldset_titre' => 'Fieldset',
+       'saisie_file_explication' => 'Zend een bestand',
+       'saisie_file_titre' => 'Bestand',
+       'saisie_hidden_explication' => 'Een vooraf ingevuld veld dat de gebruiker niet ziet.',
+       'saisie_hidden_titre' => 'Verborgen veld',
+       'saisie_input_explication' => 'Een enkele tekstregel die zichtbaar of verborgen (wachtwoord) kan zijn.',
+       'saisie_input_titre' => 'Tekstveld',
+       'saisie_mot_explication' => 'Een of meer trefwoorden uit een groep',
+       'saisie_mot_titre' => 'Trefwoord',
+       'saisie_oui_non_explication' => 'Ja of nee antwoord',
+       'saisie_oui_non_titre' => 'Ja of Nee',
+       'saisie_radio_defaut_choix1' => 'Een',
+       'saisie_radio_defaut_choix2' => 'Twee',
+       'saisie_radio_defaut_choix3' => 'Drie',
+       'saisie_radio_explication' => 'Voor het kiezen van een enkele optie uit meerder mogelijkheden.',
+       'saisie_radio_titre' => 'Radio knop',
+       'saisie_selecteur_article' => 'Toon een artikelkeuze',
+       'saisie_selecteur_article_titre' => 'Artikelkeuze',
+       'saisie_selecteur_rubrique' => 'Toon een rubriekkeuze',
+       'saisie_selecteur_rubrique_article' => 'Toon een artikel- of rubriekkeuze',
+       'saisie_selecteur_rubrique_article_titre' => 'Artikel- of rubriekkeuze',
+       'saisie_selecteur_rubrique_titre' => 'Rubriekkeuze',
+       'saisie_selection_explication' => 'Kies een waarde uit een dropdown list box.',
+       'saisie_selection_multiple_explication' => 'Voor het keizen van meerder waardes uit een lijst.',
+       'saisie_selection_multiple_titre' => 'Meerkeuze',
+       'saisie_selection_titre' => 'Dropdown listbox',
+       'saisie_textarea_explication' => 'Een tekstveld meet meerder regels.',
+       'saisie_textarea_titre' => 'Textarea',
+
+       // T
+       'tous_visiteurs' => 'Alle bezoekers (ook niet-geregistreerde)',
+       'tout_selectionner' => 'Alles kiezen',
+
+       // V
+       'vue_sans_reponse' => '<i>Zonder antwoord</i>',
+
+       // Z
+       'z' => 'zzz'
+);
+
+?>
diff --git a/www/plugins/saisies/lang/saisies_ru.php b/www/plugins/saisies/lang/saisies_ru.php
new file mode 100644 (file)
index 0000000..e1634f0
--- /dev/null
@@ -0,0 +1,176 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=ru
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // B
+       'bouton_parcourir_docs_article' => 'Посмотреть статью', # MODIF
+       'bouton_parcourir_docs_breve' => 'Посмотреть новость', # MODIF
+       'bouton_parcourir_docs_rubrique' => 'Посмотреть раздел', # MODIF
+       'bouton_parcourir_mediatheque' => 'Изменить библиотеку мультимедиа', # MODIF
+
+       // C
+       'construire_action_annuler' => 'Отменить',
+       'construire_action_configurer' => 'Настройки',
+       'construire_action_deplacer' => 'Переместить',
+       'construire_action_dupliquer' => 'Скопировать',
+       'construire_action_dupliquer_copie' => '(копия)',
+       'construire_action_supprimer' => 'Удалить',
+       'construire_ajouter_champ' => 'Добавить поле',
+       'construire_attention_enregistrer' => 'Обязательно нажмите кнопку СОХРАНИТЬ, если вы что то изменяли!',
+       'construire_attention_modifie' => 'На этой странице у вас есть возможность редактировать форму. Для того, чтобы вернуться к списку выбора форм, нажмите на кнопку ниже', # MODIF
+       'construire_attention_supprime' => 'Изменения включают удаление полей. Пожалуйста, подтвердите регистрацию новой формы.', # MODIF
+       'construire_aucun_champs' => 'На данный момент поля в форме отсутствуют', # MODIF
+       'construire_confirmer_supprimer_champ' => 'Вы действительно хотите удалить это поле?',
+       'construire_info_nb_champs_masques' => '@nb@ скрытых полей', # MODIF
+       'construire_position_explication' => 'На месте какого поля показывать?',
+       'construire_position_fin_formulaire' => 'В самом конце',
+       'construire_position_fin_groupe' => 'После группы @groupe@',
+       'construire_position_label' => 'Расположение',
+       'construire_reinitialiser' => 'Отменить изменения',
+       'construire_reinitialiser_confirmer' => 'Вы действительно хотите отменить все изменения? ',
+       'construire_verifications_aucune' => 'Не выполнять проверку',
+       'construire_verifications_label' => 'Тип проверки',
+
+       // E
+       'erreur_generique' => 'Вы допустили ошибку при заполнении полей формы. Проверьте корректность введенной информации',
+       'erreur_option_nom_unique' => 'Такое название поля уже используется.',
+
+       // I
+       'info_configurer_saisies' => 'Тестовая страница формы', # MODIF
+
+       // L
+       'label_annee' => 'Год',
+       'label_jour' => 'День',
+       'label_mois' => 'Месяц',
+
+       // O
+       'option_aff_art_interface_explication' => 'Отображать только статьи в настройках языка пользователя', # MODIF
+       'option_aff_art_interface_label' => 'Многоязычное отображение', # MODIF
+       'option_aff_langue_explication' => 'Показать выбранный язык статьи или раздела перед названием', # MODIF
+       'option_aff_langue_label' => 'Вывод языка статьи',
+       'option_aff_rub_interface_explication' => 'Отображать только разделы в языковых настройках пользователя.', # MODIF
+       'option_aff_rub_interface_label' => 'Многоязычное отображение', # MODIF
+       'option_attention_explication' => 'Сообщение, которое является более важным нежели комментарий.', # MODIF
+       'option_attention_label' => 'Предупреждение',
+       'option_autocomplete_defaut' => 'Оставить по умолчанию',
+       'option_autocomplete_explication' => 'При загрузке страницы браузер может предварительно заполнить поля на основании истории', # MODIF
+       'option_autocomplete_label' => 'Предварительное заполнение поля',
+       'option_autocomplete_off' => 'Отключить',
+       'option_autocomplete_on' => 'Включить',
+       'option_cacher_option_intro_label' => 'Не отображать первый пустой вариант', # MODIF
+       'option_choix_destinataires_explication' => 'Один или несколько авторов, среди которых пользователь может сделать свой ​​выбор. Если ничего не выбрано, то то будет выбран автор, который сейчас на сайте.', # MODIF
+       'option_choix_destinataires_label' => 'Возможные получатели', # MODIF
+       'option_class_label' => 'Дополнительные CSS классы',
+       'option_cols_explication' => 'Длина поля в символах. Эта опция не всегда работает, так CSS стили сайта могут отменять заданное значение.',
+       'option_cols_label' => 'Ширина',
+       'option_datas_explication' => 'Задайте элементы списка в  формате: ключ|название опции',
+       'option_datas_label' => 'Значения списка',
+       'option_defaut_label' => 'Выводить по умолчанию',
+       'option_disable_avec_post_explication' => 'Такой же вариант как и предыдущий, но вносит значение в скрытое поле.', # MODIF
+       'option_disable_avec_post_label' => 'Отключено, но опубликовано.', # MODIF
+       'option_disable_explication' => 'Поле не может получить фокус',
+       'option_disable_label' => 'Отключить поле',
+       'option_erreur_obligatoire_explication' => 'Выводится, если поле не заполнено. Если оставить пустым - то выведется сообщение по умолчанию.', # MODIF
+       'option_erreur_obligatoire_label' => 'Сообщение о ошибке', # MODIF
+       'option_explication_explication' => 'Пояснение о назначении поля',
+       'option_explication_label' => 'Пояснение',
+       'option_groupe_affichage' => 'Вывод',
+       'option_groupe_description' => 'Основное',
+       'option_groupe_utilisation' => 'Свойства',
+       'option_groupe_validation' => 'Проверка',
+       'option_info_obligatoire_explication' => 'Вы можете изменить стандартные настройки обязательного заполнения полей.. ', # MODIF
+       'option_info_obligatoire_label' => 'Обязательное заполнение полей', # MODIF
+       'option_inserer_barre_choix_edition' => 'добавить полную панель',
+       'option_inserer_barre_choix_forum' => 'добавить сокращенную панель ',
+       'option_inserer_barre_explication' => 'Добавить панель для редактирования текста (кнопки стилей)?',
+       'option_inserer_barre_label' => 'Панель редактирования',
+       'option_label_case_label' => 'Позиция чекбокса', # MODIF
+       'option_label_explication' => 'Название поля', # MODIF
+       'option_label_label' => 'Название',
+       'option_maxlength_explication' => 'Максимальное к-во символов, которое можно ввести в поле:',
+       'option_maxlength_label' => 'Максимальное к-во символов',
+       'option_multiple_explication' => 'Пользователь сможет выбрать несколько вариантов',
+       'option_multiple_label' => 'Выбор нескольких значений',
+       'option_nom_explication' => 'Задать id поля. Латинские буквы, цифры и символ  "_". ',
+       'option_nom_label' => 'Название поля',
+       'option_obligatoire_label' => 'Обязательное поле',
+       'option_option_intro_label' => 'Название первого элемента списка',
+       'option_option_statut_label' => 'Показать статус',
+       'option_pliable_label' => 'Расширяемая', # MODIF
+       'option_pliable_label_case' => 'Группа полей может быть развернута или сжата.', # MODIF
+       'option_plie_label' => 'Уже сжато', # MODIF
+       'option_plie_label_case' => 'Если группу полей можно расширить или сжать, тогда эта опция их сожмет с отображением полей.', # MODIF
+       'option_previsualisation_explication' => 'Добавить вкладку предварительного просмотра?',
+       'option_previsualisation_label' => 'Предварительный просмотр',
+       'option_readonly_explication' => 'Поле показывается, его можно выбрать, но нельзя изменить.',
+       'option_readonly_label' => 'Только для чтения',
+       'option_rows_explication' => 'Высота поля в строках. Эта опция не всегда работает, так CSS стили сайта могут отменять заданное значение.',
+       'option_rows_label' => 'Количество строк',
+       'option_size_explication' => 'Длина поля в символах. Эта опция не всегда работает, так CSS стили сайта могут отменять заданное значение.',
+       'option_size_label' => 'Размер поля',
+       'option_type_choix_plusieurs' => 'Позволяет выбрать <strong>несколько</strong> получателей.', # MODIF
+       'option_type_choix_tous' => 'Отметить <strong>всех</strong> авторов как получателей. Пользователю выбор не предоставляется.', # MODIF
+       'option_type_choix_un' => 'Сделать возможным выбор только одного получателя.', # MODIF
+       'option_type_explication' => 'Если выбран "ввод пароля", то символы в поле будут превращаться в звездочки',
+       'option_type_label' => 'Тип поля',
+       'option_type_password' => 'Ввод пароля', # MODIF
+       'option_type_text' => 'Обычный',
+
+       // S
+       'saisie_auteurs_explication' => 'Выбор автора из зарегистрированных на сайте',
+       'saisie_auteurs_titre' => 'Выбор автора',
+       'saisie_case_explication' => 'Используется для включения/отключения определенной опции.',
+       'saisie_case_titre' => 'Единичный выбор ',
+       'saisie_checkbox_explication' => 'Используется для выбора нескольких вариантов при помощи check-box.',
+       'saisie_checkbox_titre' => 'Чекбокс',
+       'saisie_date_explication' => 'Используется для ввода даты при помощи календаря.',
+       'saisie_date_titre' => 'Дата',
+       'saisie_destinataires_explication' => 'Используется для выбора одного или нескольких получателей из числа предварительно выбранных авторов.',
+       'saisie_destinataires_titre' => 'Получатели',
+       'saisie_explication_explication' => 'Используется для вывода поясняющей информации',
+       'saisie_explication_titre' => 'Пояснение',
+       'saisie_fieldset_explication' => 'FIELDSET. Используется для группировки полей ',
+       'saisie_fieldset_titre' => 'Филдсет',
+       'saisie_file_explication' => 'Отправить файл',
+       'saisie_file_titre' => 'Файл',
+       'saisie_hidden_explication' => 'Значение этого поля не отображается для пользователя. INPUT TYPE=HIDDEN',
+       'saisie_hidden_titre' => 'Невидимое (скрытое) поле',
+       'saisie_input_explication' => 'Основное поле для ввода информации',
+       'saisie_input_titre' => 'Текстовая строка',
+       'saisie_oui_non_explication' => 'Выбор только одного варианта ответа: ДА или НЕТ',
+       'saisie_oui_non_titre' => 'Да или Нет',
+       'saisie_radio_defaut_choix1' => 'Один',
+       'saisie_radio_defaut_choix2' => 'Два',
+       'saisie_radio_defaut_choix3' => 'Три',
+       'saisie_radio_explication' => 'Используется для выбора одной опции из нескольких.',
+       'saisie_radio_titre' => 'Радио кнопка',
+       'saisie_selecteur_article' => 'Позволяет выбратью статью из структуры сайта',
+       'saisie_selecteur_article_titre' => 'Выбор статьи',
+       'saisie_selecteur_rubrique' => 'Позволяет выбратью раздел из структуры сайта',
+       'saisie_selecteur_rubrique_article' => 'Позволяет выбратью статью или раздел из структуры сайта',
+       'saisie_selecteur_rubrique_article_titre' => 'Выбор статьи или раздела',
+       'saisie_selecteur_rubrique_titre' => 'Выбор раздела',
+       'saisie_selection_explication' => 'SELECT - позволяет выбрать один пункт из выпадающего списка',
+       'saisie_selection_multiple_explication' => 'SELECT - позволяет выбрать несколько пунктов из списка',
+       'saisie_selection_multiple_titre' => 'Выбор нескольких опций',
+       'saisie_selection_titre' => 'Выпадающий список',
+       'saisie_textarea_explication' => 'Textarea - для работы с текстом большого размера',
+       'saisie_textarea_titre' => 'Текстовое поле',
+
+       // T
+       'tous_visiteurs' => 'Все посетители (в том числе не зарегистрированы)',
+       'tout_selectionner' => 'Выбрать все',
+
+       // V
+       'vue_sans_reponse' => '<i>ничего не задано</i>',
+
+       // Z
+       'z' => 'zzz' # MODIF
+);
+
+?>
diff --git a/www/plugins/saisies/lang/saisies_sk.php b/www/plugins/saisies/lang/saisies_sk.php
new file mode 100644 (file)
index 0000000..b8504d2
--- /dev/null
@@ -0,0 +1,193 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=sk
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // B
+       'bouton_parcourir_docs_article' => 'Prehľadať článok',
+       'bouton_parcourir_docs_breve' => 'Prehľadať novinku',
+       'bouton_parcourir_docs_rubrique' => 'Prehľadať rubriku',
+       'bouton_parcourir_mediatheque' => 'Prehľadať knižnicu multimédií',
+
+       // C
+       'construire_action_annuler' => 'Zrušiť',
+       'construire_action_configurer' => 'Nastaviť',
+       'construire_action_deplacer' => 'Presunúť',
+       'construire_action_dupliquer' => 'Duplikovať',
+       'construire_action_dupliquer_copie' => '(kópia)',
+       'construire_action_supprimer' => 'Odstrániť',
+       'construire_ajouter_champ' => 'Pridať pole',
+       'construire_attention_enregistrer' => 'Nezabudnite uložiť zmeny!',
+       'construire_attention_modifie' => 'Tento formulár sa odlišuje od pôvodného. Máte možnosť ho obnoviť do stavu pred svojimi zmenami.',
+       'construire_attention_supprime' => 'Medzi vašimi zmenami je vymazanie niekoľkých polí. Potvrďte, prosím, registráciu tejto verzie formulára.',
+       'construire_aucun_champs' => 'V tomto formulári nie je momentálne žiadne pole',
+       'construire_confirmer_supprimer_champ' => 'Chcete odstrániť toto pole?',
+       'construire_info_nb_champs_masques' => '@nb@ skryté (-ých) pole (-í/-ia) času na nastavenie skupiny.',
+       'construire_position_explication' => 'Uveďte akékoľvek ďalšie pole predtým, ako sa vloží.',
+       'construire_position_fin_formulaire' => 'Na koniec formulára',
+       'construire_position_fin_groupe' => 'Na koniec skupiny @groupe@',
+       'construire_position_label' => 'Umiestnenie poľa',
+       'construire_reinitialiser' => 'Znova načítať formulár',
+       'construire_reinitialiser_confirmer' => 'Stratíte všetky svoje zmeny. Určite sa chcete vrátiť na pôvodný formulár?',
+       'construire_verifications_aucune' => 'Žiadne',
+       'construire_verifications_label' => 'Typ overenia, ktorý sa má vykonať',
+
+       // E
+       'erreur_generique' => 'V poliach sú chyby; prosím, skontrolujte údaje, ktoré ste zadali',
+       'erreur_option_nom_unique' => 'Tento názov už používa iné pole, a v tomto formulári musí byť jedinečný.',
+
+       // I
+       'info_configurer_saisies' => 'Testovacia stránka Saisies',
+
+       // L
+       'label_annee' => 'Rok',
+       'label_jour' => 'Deň',
+       'label_mois' => 'Mesiac',
+
+       // O
+       'option_aff_art_interface_explication' => 'Zobrazovať len články v jazyku používateľa',
+       'option_aff_art_interface_label' => 'Viacjazyčné zobrazenie',
+       'option_aff_langue_explication' => 'Pred názvom zobraziť vybratý jazyk článku alebo rubriky',
+       'option_aff_langue_label' => 'Zobraziť jazyk',
+       'option_aff_rub_interface_explication' => 'Zobraziť len rubriky v jazyku používateľa',
+       'option_aff_rub_interface_label' => 'Viacjazyčné zobrazenie',
+       'option_afficher_si_explication' => 'Uveďte podmienky zobrazenia poľa podľa hodnoty iných polí. Čísla iných polí musia byť medzi <code>@</code>. <br />Príklad <code>@selection_1@=="Toto"</code> prikazuje zobraziť pole, ktoré <code>selection_1</code> má hodnotu <code>Toto</code>.',
+       'option_afficher_si_label' => 'Podmienené zobrazenie',
+       'option_afficher_si_remplissage_explication' => 'V porovnaní s predchádzajúcou možnosťou táto podmienka sa týka zobrazenia formulára, nie jeho výsledkov. Jej syntax je rovnaká.',
+       'option_afficher_si_remplissage_label' => 'Podmienené zobrazenie pri vypĺňaní',
+       'option_attention_explication' => 'Správa je dôležitejšia ako vysvetlivka.',
+       'option_attention_label' => 'Varovanie',
+       'option_autocomplete_defaut' => 'Nechať ako predvolené',
+       'option_autocomplete_explication' => 'Pri nahrávaní stránky môže váš prehliadač automaticky vyplniť polia podľa histórie',
+       'option_autocomplete_label' => 'Automaticky vyplniť pole',
+       'option_autocomplete_off' => 'Deaktivovať',
+       'option_autocomplete_on' => 'Aktivovať',
+       'option_cacher_option_intro_label' => 'Skryť prvý prázdny výber',
+       'option_choix_alternatif_label' => 'Nastaviť možnosť vytvárania rozbaľovacích menu',
+       'option_choix_alternatif_label_defaut' => 'Iný výber',
+       'option_choix_alternatif_label_label' => 'Pomenovanie výberu z viacerých možností',
+       'option_choix_destinataires_explication' => 'Jeden autor alebo viacerí, z ktorých si používateľ môže vybrať. Ak nie je vybratý žiaden, vyberie sa autor, ktorý nainštaloval stránku.',
+       'option_choix_destinataires_label' => 'Možní príjemcovia',
+       'option_class_label' => 'Ďalšie triedy CSS',
+       'option_cols_explication' => 'Šírka poľa v znakoch. Táto možnosť sa vždy nepoužíva, lebo štýly CSS na vašej stránke ju môžu prepísať.',
+       'option_cols_label' => 'Šírka',
+       'option_datas_explication' => 'Musíte definovať výber pre každý riadok vo formulári "kľúč|označenie výberu"',
+       'option_datas_label' => 'Zoznam dostupných možností',
+       'option_datas_sous_groupe_explication' => 'Online výber musíte zadať vo forme "kľúč|menovka" výberu. <br />Môžete označiť začiatok podskupiny formulára "*Nadpis podskupiny". Skupinu môžete skončiť na riadku alebo do ďalšieho riadka môžete napísať len "/*".',
+       'option_defaut_label' => 'Predvolená hodnota',
+       'option_disable_avec_post_explication' => 'Rovnaká ako predošlá možnosť, ale hodnotu pošle v skrytom poli.',
+       'option_disable_avec_post_label' => 'Deaktivovať ale poslať.',
+       'option_disable_explication' => 'Na pole sa nedá zacieliť.',
+       'option_disable_label' => 'Deaktivovať pole',
+       'option_erreur_obligatoire_explication' => 'Zobrazovanú chybovú správu môžete upraviť tak, aby označovala povinnosť',
+       'option_erreur_obligatoire_label' => 'Správa s povinnosťou',
+       'option_explication_explication' => 'Ak je to potrebné, krátka veta opisujúca zmysel daného poľa.',
+       'option_explication_label' => 'Vysvetlenie',
+       'option_groupe_affichage' => 'Zobraziť',
+       'option_groupe_description' => 'Opis',
+       'option_groupe_utilisation' => 'Použitie',
+       'option_groupe_validation' => 'Overenie',
+       'option_heure_pas_explication' => 'Pri používaní diára sa zobrazí menu, ktoré vám pomôže zadať hodiny a minúty. Tak môžete vybrať časový interval pre každý údaj (predvolený – 30 min).',
+       'option_heure_pas_label' => 'Interval v minútach v menu pomocníka k zápisu do poľa',
+       'option_horaire_label' => 'Diár',
+       'option_horaire_label_case' => 'Povoliť zápis do diára',
+       'option_id_groupe_label' => 'Skupina slov',
+       'option_info_obligatoire_explication' => 'Môžete zmeniť predvolenú povinnosť vyplniť polia: <i>[Povinné]</i>.',
+       'option_info_obligatoire_label' => 'Označenie povinnosti',
+       'option_inserer_barre_choix_edition' => 'celý editovací panel s nástrojmi',
+       'option_inserer_barre_choix_forum' => 'panel s nástrojmi pre diskusné fóra',
+       'option_inserer_barre_explication' => 'Vložiť panel s nástrojmi porte-plume, ak je tento nástroj aktivovaný.',
+       'option_inserer_barre_label' => 'Vložiť panel s nástrojmi',
+       'option_label_case_label' => 'Označenie sa nachádza pod zaškrtávacím políčkom',
+       'option_label_explication' => 'Zobrazí sa titulok.',
+       'option_label_label' => 'Označenie',
+       'option_maxlength_explication' => 'Používateľ nemôže napísať viac znakov ako určuje toto číslo.',
+       'option_maxlength_label' => 'Maximálny počet znakov',
+       'option_multiple_explication' => 'Používateľ si bude môcť vybrať niekoľko hodnôt',
+       'option_multiple_label' => 'Výber z viacerých možností',
+       'option_nom_explication' => 'Počítačový názov, ktorý pomenúva pole. Môžu v ňom byť malé písmená abecedy alebo podčiarkovník "_".',
+       'option_nom_label' => 'Názov poľa',
+       'option_obligatoire_label' => 'Povinné pole',
+       'option_option_destinataire_intro_label' => 'Pomenovanie prvého prázdneho výberu(ak má formu zoznamu)',
+       'option_option_intro_label' => 'Označenie prvého prázdneho výberu',
+       'option_option_statut_label' => 'Zobraziť stav',
+       'option_pliable_label' => 'Roztiahnuteľné',
+       'option_pliable_label_case' => 'Skupina polí sa dá roztiahnuť alebo stiahnuť.',
+       'option_plie_label' => 'Už stiahnutá',
+       'option_plie_label_case' => 'Ak sa dá skupina polí stiahnuť a roztiahnuť, táto možnosť ju v zobrazeniach formulára nastaví ako už stiahnutú.',
+       'option_previsualisation_explication' => 'Ak je aktivovaný porte-plume, pridať kartu do ukážky vzhľadu zadaného textu.',
+       'option_previsualisation_label' => 'Aktivovať ukážky',
+       'option_readonly_explication' => 'Toto pole môžete zobraziť, vybrať, ale nie upravovať.',
+       'option_readonly_label' => 'Len na čítanie',
+       'option_rows_explication' => 'Výška poľa v riadkoch. Táto možnosť sa nepoužije vždy, pretože  štýly CSS na vašej stránke ju môžu zmeniť.',
+       'option_rows_label' => 'Počet riadkov',
+       'option_size_explication' => 'Šírka poľa v riadkoch. Táto možnosť sa nepoužije vždy, pretože štýly CSS na vašej stránke ju môžu zmeniť.',
+       'option_size_label' => 'Veľkosť poľa',
+       'option_type_choix_plusieurs' => 'Umožní používateľovi vybrať si <strong>mnohých</ strong> príjemcov.',
+       'option_type_choix_tous' => 'Urobiť príjemcov zo <strong>všetkých</ strong> týchto autorov. Používateľ nebude mať na výber.',
+       'option_type_choix_un' => 'Povoliť používateľovi vybrať si <strong>jedného</ strong> príjemcu (vo forme rozbaľovacieho menu).',
+       'option_type_choix_un_radio' => 'Umožniť používateľovi vybrať si <strong>jediného</strong> príjemcu (vo forme zoznamu s odrážkami).',
+       'option_type_explication' => 'V režime hesla sa obsah napísaný do poľa nahradí hviezdičkami.',
+       'option_type_label' => 'Typ poľa',
+       'option_type_password' => 'Skrývaný text pri písaní (napr. heslo)',
+       'option_type_text' => 'Normálny',
+
+       // S
+       'saisie_auteurs_explication' => 'Umožňuje vám vybrať jedného alebo viacerých autorov',
+       'saisie_auteurs_titre' => 'Autori',
+       'saisie_case_explication' => 'Používa sa na aktivovanie alebo deaktivovanie konkrétnej možnosti.',
+       'saisie_case_titre' => 'Jedno zaškrtávacie políčko',
+       'saisie_checkbox_explication' => 'Používa sa na výber niekoľkých možností pomocou zaškrtávacích políčok.',
+       'saisie_checkbox_titre' => 'Zaškrtávacie políčka',
+       'saisie_date_explication' => 'Používa sa na zadávanie dátumu pomocou nástroja kalendára',
+       'saisie_date_titre' => 'Dátum',
+       'saisie_destinataires_explication' => 'Používa sa na výber jedného príjemcu alebo viacerých z dopredu vybraných autorov.',
+       'saisie_destinataires_titre' => 'Príjemcovia',
+       'saisie_explication_explication' => 'Všeobecný vysvetľujúci text.',
+       'saisie_explication_titre' => 'Vysvetlenie',
+       'saisie_fieldset_explication' => 'Rám, v ktorom môže byť niekoľko polí.',
+       'saisie_fieldset_titre' => 'Skupina polí',
+       'saisie_file_explication' => 'Poslať súbor',
+       'saisie_file_titre' => 'Súbor',
+       'saisie_hidden_explication' => 'Predvyplnené pole, ktoré používateľ nikdy neuvidí.',
+       'saisie_hidden_titre' => 'Skryté pole',
+       'saisie_input_explication' => 'Jednoduchý riadok s textom, ktorý môže byť viditeľný alebo skrytý (heslo).',
+       'saisie_input_titre' => 'Textové pole',
+       'saisie_mot_explication' => 'Jedno alebo viac kľúčových slov zo skupiny slov',
+       'saisie_mot_titre' => 'Kľúčové slovo',
+       'saisie_oui_non_explication' => 'Odpoveď buď Áno alebo Nie',
+       'saisie_oui_non_titre' => 'Áno alebo Nie',
+       'saisie_radio_defaut_choix1' => 'Jeden',
+       'saisie_radio_defaut_choix2' => 'Dva',
+       'saisie_radio_defaut_choix3' => 'Tri',
+       'saisie_radio_explication' => 'Používa sa na výber jednej možnosti z viacerých dostupných.',
+       'saisie_radio_titre' => 'Rádiové gombíky',
+       'saisie_selecteur_article' => 'Zobraziť prehliadač výberu článku',
+       'saisie_selecteur_article_titre' => 'Výber článku',
+       'saisie_selecteur_rubrique' => 'Zobraziť prehliadač výberu rubriky',
+       'saisie_selecteur_rubrique_article' => 'Zobraziť prehliadač výberu článku alebo rubriky',
+       'saisie_selecteur_rubrique_article_titre' => 'Výber článku alebo rubriky',
+       'saisie_selecteur_rubrique_titre' => 'Výber rubriky',
+       'saisie_selection_explication' => 'Vyberte možnosť z rozbaľovacieho zoznamu.',
+       'saisie_selection_multiple_explication' => 'Používa sa na výber niekoľkých možností zo zoznamu.',
+       'saisie_selection_multiple_titre' => 'Výber z viacerých možností',
+       'saisie_selection_titre' => 'Rozbaľovací zoznam',
+       'saisie_textarea_explication' => 'Textové pole s viacerými riadkami.',
+       'saisie_textarea_titre' => 'Blok textu',
+
+       // T
+       'tous_visiteurs' => 'Všetci návštevníci (aj ne­za­re­gis­tro­va­ní)',
+       'tout_selectionner' => 'Vybrať všetko',
+
+       // V
+       'vue_sans_reponse' => '<i>Bez reakcie</i>',
+
+       // Z
+       'z' => 'zzz'
+);
+
+?>
diff --git a/www/plugins/saisies/paquet.xml b/www/plugins/saisies/paquet.xml
new file mode 100644 (file)
index 0000000..0e2de34
--- /dev/null
@@ -0,0 +1,29 @@
+<paquet\r
+       prefix="saisies"\r
+       categorie="outil"\r
+       version="2.0.3"\r
+       etat="stable"\r
+       compatibilite="[3.0.0;3.1.*]"\r
+       logo="images/logo_saisie_48.png"\r
+       documentation="http://contrib.spip.net/Saisies"\r
+>\r
+       <nom>Saisies</nom>\r
+       <auteur lien="http://contrib.spip.net/Matthieu-Marcillaud">Matthieu Marcillaud</auteur>\r
+       <auteur lien="http://contrib.spip.net/RastaPopoulos">RastaPopoulos</auteur>\r
+       <auteur lien="http://contrib.spip.net/Joseph">Joseph</auteur>\r
+       <auteur lien="http://www.ldd.fr">Les Développements Durables</auteur>\r
+       <licence>GNU/GPL</licence>\r
+\r
+       <traduire module="saisies" reference="fr" gestionnaire="salvatore" />\r
+\r
+       <pipeline nom="header_prive" inclure="saisies_pipelines.php" />\r
+       <pipeline nom="affichage_final" inclure="saisies_pipelines.php" />\r
+       <pipeline nom="saisies_autonomes" inclure="saisies_pipelines.php" />\r
+       <pipeline nom="formulaire_saisies" inclure="saisies_pipelines.php" />\r
+       <pipeline nom="formulaire_charger" inclure="saisies_pipelines.php" />\r
+       <pipeline nom="formulaire_verifier" inclure="saisies_pipelines.php" />\r
+       <pipeline nom="styliser" inclure="saisies_pipelines.php" />\r
+\r
+       <utilise nom="verifier" compatibilite="[0.1.10;]" />\r
+       <utilise nom="spip_bonux" compatibilite="[3.0.0;]" />\r
+</paquet>\r
diff --git a/www/plugins/saisies/prive/exec/construire_formulaire.html b/www/plugins/saisies/prive/exec/construire_formulaire.html
new file mode 100644 (file)
index 0000000..5cd0537
--- /dev/null
@@ -0,0 +1,3 @@
+<h1><:saisies:info_configurer_saisies:></h1>
+
+<div class="ajax">#FORMULAIRE_CONSTRUIRE_FORMULAIRE{test,#ARRAY,#ARRAY{modifier_nom,oui,nom_unique,oui}}</div>
diff --git a/www/plugins/saisies/prive/listes/articles_originaux_recursifs.html b/www/plugins/saisies/prive/listes/articles_originaux_recursifs.html
new file mode 100644 (file)
index 0000000..c62602e
--- /dev/null
@@ -0,0 +1,14 @@
+[(#SET{iteration,#ENV{iteration,1}})]
+[(#SET{separateur,[(#ENV{separateur}|concat{&nbsp;&rsaquo;&nbsp;})]})]
+<BOUCLE_rubriques(RUBRIQUES){id_parent}{tout}>
+       <optgroup label="#GET{separateur}#TITRE">
+               <BOUCLE_articles_originaux(ARTICLES){id_rubrique}{origine_traduction}>
+               [(#ENV{multiple}|oui)
+               <option value="#ID_ARTICLE"[(#ID_ARTICLE|in_array{#ENV{valeur,#ENV{defaut,#ARRAY}}}|oui) selected="selected"]>#GET{separateur}#TITRE</option>]
+               [(#ENV{multiple}|non)
+               <option value="#ID_ARTICLE"[(#ID_ARTICLE|=={#ENV{valeur,#ENV{defaut}}}|oui) selected="selected"]>#GET{separateur}#TITRE</option>]
+               <INCLURE{fond=prive/listes/articles_originaux_recursifs}{valeur=#ENV{valeur}}{id_rubrique=#ID_RUBRIQUE}{iteration=(#GET{iteration}|plus{1})}{separateur=#GET{separateur}}{_multiple}{defaut} />
+               </BOUCLE_articles_originaux>
+               <BOUCLE_ssrubriques(BOUCLE_rubriques)> </BOUCLE_ssrubriques>
+       </optgroup>
+</BOUCLE_rubriques>
diff --git a/www/plugins/saisies/prive/listes/rubriques_recursives.html b/www/plugins/saisies/prive/listes/rubriques_recursives.html
new file mode 100644 (file)
index 0000000..368e56b
--- /dev/null
@@ -0,0 +1,8 @@
+[(#SET{separateur,[(#ENV{separateur}|concat{&nbsp;&rsaquo;&nbsp;})]})]
+<BOUCLE_rubriques(RUBRIQUES){id_parent=#ENV{id_parent,#ENV{id_rubrique}}}{tout}>
+       [(#ENV{multiple}|oui)
+       <option value="#ID_RUBRIQUE"[(#ID_RUBRIQUE|in_array{#ENV{valeur,#ENV{defaut,#ARRAY}}}|oui) selected="selected"]>#GET{separateur}#TITRE</option>]
+       [(#ENV{multiple}|non)
+       <option value="#ID_RUBRIQUE"[(#ID_RUBRIQUE|=={#ENV{valeur,#ENV{defaut}}}|oui) selected="selected"]>#GET{separateur}#TITRE</option>]
+       <INCLURE{fond=prive/listes/rubriques_recursives,env,id_rubrique=#ID_RUBRIQUE,separateur=#GET{separateur}} />
+</BOUCLE_rubriques>
diff --git a/www/plugins/saisies/saisies-vues/_base.html b/www/plugins/saisies/saisies-vues/_base.html
new file mode 100644 (file)
index 0000000..85c612c
--- /dev/null
@@ -0,0 +1,42 @@
+[(#SET{sans_reponse,#ENV{sans_reponse}|is_null|?{<:saisies:vue_sans_reponse:>,#ENV{sans_reponse}}})]\r
+\r
+#SET{valeur_uniquement,#ENV{valeur_uniquement}|et{#ENV{valeur_uniquement}|!={non}}|oui}\r
+#SET{enfants,#ENV*{saisies}|et{#ENV*{saisies}|is_array}}\r
+\r
+[(#REM) On génère la réponse et on l'enregistre dans une variable. Doit être VIDE s'il n'y a pas de réponse. ]\r
+       #SET{reponse,''}\r
+       [(#CHEMIN{saisies-vues/#ENV{type_saisie}.html}|oui)\r
+               #SET{reponse,#INCLURE{fond=saisies-vues/#ENV{type_saisie},env,sans_reponse=#GET{sans_reponse}}|trim}\r
+       ]\r
+       [(#CHEMIN{saisies-vues/#ENV{type_saisie}.html}|non)\r
+               #SET{reponse,#ENV*{valeur}|saisie_traitement_vue{#ENV**}}\r
+       ]\r
+\r
+[(#REM) Maintenant on affiche en encapsulant ou pas ]\r
+\r
+[(#REM) Cas normal avec présentation ]\r
+[(#GET{valeur_uniquement}|non)\r
+<div class="afficher[ afficher_(#ENV{nom})][ saisie_(#ENV{type_saisie})][ (#ENV{li_class})][ (#GET{reponse}|non)sans_reponse vide]">\r
+       [(#REM) S'il y a des enfants on n'inclut que la vue ]\r
+       [(#GET{enfants}|oui)\r
+               #GET{reponse}\r
+       ]\r
+       [(#GET{enfants}|non|et{#ENV{type_saisie}|!={explication}})\r
+               [<strong class="label">(#ENV{label_case,#ENV{label,#ENV{nom}}})</strong>]\r
+               <div class="valeur">\r
+               [(#GET{reponse}|sinon{#GET{sans_reponse}})]\r
+               </div>\r
+       ]\r
+</div>\r
+]\r
+\r
+[(#REM) Cas où on demande uniquement la valeur ]\r
+[(#GET{valeur_uniquement}|oui)\r
+       [(#REM) S'il y a des enfants on inclut que la vue ]\r
+       [(#GET{enfants}|oui)\r
+               #GET{reponse}\r
+       ]\r
+       [(#GET{enfants}|non)\r
+               [(#GET{reponse}|sinon{#GET{sans_reponse}})]\r
+       ]\r
+]\r
diff --git a/www/plugins/saisies/saisies-vues/auteurs.html b/www/plugins/saisies/saisies-vues/auteurs.html
new file mode 100644 (file)
index 0000000..e46baf5
--- /dev/null
@@ -0,0 +1,16 @@
+<BOUCLE_test_multiple(CONDITION){si #ENV{multiple}|=={on}|oui}>
+       [(#SET{valeur,[(#ENV*{valeur}|is_array|?{[(#ENV*{valeur})],[(#ENV*{valeur}|explode{','})]})]})]
+       <B_auteurs_selectionnes>
+       <ul>
+       <BOUCLE_auteurs_selectionnes(AUTEURS){id_auteur IN #GET*{valeur}}
+               {par num nom, nom}{statut ?}{statut != 5poubelle}{tout}>
+               <li class="choix">#NOM (#ID_AUTEUR)</li>
+       </BOUCLE_auteurs_selectionnes>
+       </ul>
+       </B_auteurs_selectionnes>
+</BOUCLE_test_multiple>
+       <BOUCLE_auteur_selectionne(AUTEURS){id_auteur=#ENV{valeur}}
+               {statut ?}{statut != 5poubelle}{tout}>
+               <p>#NOM (#ID_AUTEUR)</p>
+       </BOUCLE_auteur_selectionne>
+<//B_test_multiple>
diff --git a/www/plugins/saisies/saisies-vues/case.html b/www/plugins/saisies/saisies-vues/case.html
new file mode 100644 (file)
index 0000000..12a6e3e
--- /dev/null
@@ -0,0 +1 @@
+[<p>(#ENV*{valeur}|?{<:item_oui:>,<:item_non:>})</p>]
diff --git a/www/plugins/saisies/saisies-vues/checkbox.html b/www/plugins/saisies/saisies-vues/checkbox.html
new file mode 100644 (file)
index 0000000..4818e29
--- /dev/null
@@ -0,0 +1,12 @@
+[(#REM) datas peut être une chaine qu'on sait décomposer ]\r
+#SET{datas, #ENV{datas}|saisies_chaine2tableau}\r
+#SET{valeur, #ENV{valeur}|saisies_valeur2tableau}\r
+<B_choix>\r
+<ul>\r
+       <BOUCLE_choix(POUR){tableau #GET{valeur}}>\r
+               [<li class="choix">(#GET{datas/#VALEUR})</li>]\r
+       </BOUCLE_choix>\r
+\r
+       [<li class="choix"><em>#ENV{choix_alternatif_label}</em> : (#GET{valeur/choix_alternatif})</li>]\r
+</ul>\r
+</B_choix>\r
diff --git a/www/plugins/saisies/saisies-vues/date.html b/www/plugins/saisies/saisies-vues/date.html
new file mode 100644 (file)
index 0000000..d0173b9
--- /dev/null
@@ -0,0 +1,10 @@
+#SET{valeur,#ENV{valeur}|vider_date}
+#SET{date_mysql,([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])}
+[(#GET{valeur}|match{#GET{date_mysql}}|oui)
+       [(#ENV{horaire}|?{      
+               #SET{valeur,#ENV{valeur}|affdate{'d/m/Y H:i:s'}}
+               ,
+               #SET{valeur,#ENV{valeur}|affdate{'d/m/Y'}}
+       })]
+]
+[<p class="date">(#GET{valeur})</p>]
diff --git a/www/plugins/saisies/saisies-vues/destinataires.html b/www/plugins/saisies/saisies-vues/destinataires.html
new file mode 100644 (file)
index 0000000..12ac840
--- /dev/null
@@ -0,0 +1,8 @@
+[(#SET{valeur,[(#ENV*{valeur}|is_array|?{[(#ENV*{valeur})],[(#ENV*{valeur}|explode{','})]})]})]\r
+<B_destinataires>\r
+<ul>\r
+       <BOUCLE_destinataires(AUTEURS){tout}{id_auteur IN #GET*{valeur}}>\r
+       <li class="choix">#NOM</li>\r
+       </BOUCLE_destinataires>\r
+</ul>\r
+</B_destinataires>\r
diff --git a/www/plugins/saisies/saisies-vues/explication.html b/www/plugins/saisies/saisies-vues/explication.html
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/www/plugins/saisies/saisies-vues/fieldset.html b/www/plugins/saisies/saisies-vues/fieldset.html
new file mode 100644 (file)
index 0000000..253044e
--- /dev/null
@@ -0,0 +1,5 @@
+[<h3 class="legend">(#ENV{label})</h3>]
+
+[(#ENV{saisies}|is_array|oui)
+       #INCLURE{fond=inclure/voir_saisies, env, from_fieldset='on'}
+]
diff --git a/www/plugins/saisies/saisies-vues/groupe_mots.html b/www/plugins/saisies/saisies-vues/groupe_mots.html
new file mode 100644 (file)
index 0000000..a820b0b
--- /dev/null
@@ -0,0 +1,9 @@
+[(#SET{valeurs,[(#ENV{multiple}|oui|?{#ENV*{valeur},#ARRAY{0,#ENV{valeur}}})]})]
+
+<B_choix>
+<ul>
+       <BOUCLE_choix(GROUPES_MOTS){id_groupe IN #GET{valeurs}}{par titre}>
+       <li class="choix">#TITRE</li>
+       </BOUCLE_choix>
+</ul>
+</B_choix>
diff --git a/www/plugins/saisies/saisies-vues/mot.html b/www/plugins/saisies/saisies-vues/mot.html
new file mode 100644 (file)
index 0000000..5e9f0a6
--- /dev/null
@@ -0,0 +1,3 @@
+[(#REM) datas peut être une chaine qu'on sait décomposer ]\r
+#SET{valeur, #ENV{valeur}|saisies_valeur2tableau}\r
+<p><BOUCLE_mots(POUR){tableau #GET{valeur}}{', '}><a href="[(#VALEUR|generer_url_entite{mot})]">#INFO_TITRE{mot,#VALEUR}</a></BOUCLE_mots></p>\r
diff --git a/www/plugins/saisies/saisies-vues/oui_non.html b/www/plugins/saisies/saisies-vues/oui_non.html
new file mode 100644 (file)
index 0000000..dcd2157
--- /dev/null
@@ -0,0 +1 @@
+[<p>(#ENV*{valeur}|et{#ENV{valeur}|!={#ENV{valeur_non,non}}}|?{<:item_oui:>,<:item_non:>})</p>]
diff --git a/www/plugins/saisies/saisies-vues/radio.html b/www/plugins/saisies/saisies-vues/radio.html
new file mode 100644 (file)
index 0000000..debb78b
--- /dev/null
@@ -0,0 +1,4 @@
+[(#REM) datas peut être une chaine qu'on sait décomposer ]\r
+#SET{datas, #ENV{datas}|saisies_chaine2tableau}\r
+\r
+[<p>(#GET{datas/#ENV{valeur}})</p>]\r
diff --git a/www/plugins/saisies/saisies-vues/secteur.html b/www/plugins/saisies/saisies-vues/secteur.html
new file mode 100644 (file)
index 0000000..626572c
--- /dev/null
@@ -0,0 +1,10 @@
+[(#REM) valeur peut être une chaine qu'on sait décomposer ]\r
+#SET{valeur, #ENV{valeur}|saisies_chaine2tableau}\r
+\r
+<B_choix>\r
+<ul>\r
+       <BOUCLE_choix(RUBRIQUES){id_rubrique IN #GET*{valeur}}>\r
+       <li class="choix">#TITRE</li>\r
+       </BOUCLE_choix>\r
+</ul>\r
+</B_choix>\r
diff --git a/www/plugins/saisies/saisies-vues/selecteur.html b/www/plugins/saisies/saisies-vues/selecteur.html
new file mode 100644 (file)
index 0000000..419acb6
--- /dev/null
@@ -0,0 +1,30 @@
+[(#REM) 
+         
+  ### /!\ selecteur (spip Bonux) ###
+         Attention, ce qui est retourne est un tableau :
+         _request($name) = array('rubrique|3', 'rubrique|9', 'rubrique|10');
+         Une fonction de SPIP Bonux permet de récupérer un tableau d'identifiants par type d'objet :
+         [(#CHAMP|picker_selected{article})]
+         Cette fonction peut etre pratique dans une boucle en utilisant le critere IN
+         
+]
+
+
+[(#SET{selection,#VALEUR|picker_selected_par_objet})]
+
+<BOUCLE_au_moins_un(CONDITION){si #GET{selection}}>
+       <BOUCLE_un(DATA){source table,#GET{selection}}{si (#GET{selection}|count|=={1})}>
+               <p>
+                       [(#OBJET|objet_info{texte_objet}|_T)] #ID_OBJET&nbsp;: #INFO_TITRE{#OBJET,#ID_OBJET}
+               </p>
+       </BOUCLE_un>
+       <B_plusieurs>
+               <ul>
+       <BOUCLE_plusieurs(DATA){source table,#GET{selection}}{si (#GET{selection}|count|>{1})}>
+                       <li class="choix">
+                               [(#OBJET|objet_info{texte_objet}|_T)] #ID_OBJET&nbsp;: #INFO_TITRE{#OBJET,#ID_OBJET}
+                       </li>
+       </BOUCLE_plusieurs>
+               </ul>
+       </B_plusieurs>
+</BOUCLE_au_moins_un>
diff --git a/www/plugins/saisies/saisies-vues/selecteur_article.html b/www/plugins/saisies/saisies-vues/selecteur_article.html
new file mode 100644 (file)
index 0000000..d7341bb
--- /dev/null
@@ -0,0 +1,23 @@
+[(#REM) \r
+         \r
+  ### /!\ selecteur (spip Bonux) ###\r
+         Attention, ce qui est retourne est un tableau :\r
+         _request($name) = array('article|3', 'article|9', 'article|10');\r
+         Une fonction de SPIP Bonux permet de récupérer un tableau d'identifiants par type d'objet :\r
+         [(#CHAMP|picker_selected{article})]\r
+         Cette fonction peut etre pratique dans une boucle en utilisant le critere IN\r
+         \r
+]\r
+<BOUCLE_test_multiple(CONDITION){si #ENV{multiple}|=={on}|oui}>\r
+       <B_articles_selectionnes>\r
+       <ul>\r
+       <BOUCLE_articles_selectionnes(ARTICLES){id_article IN #ENV*{valeur}|picker_selected{article}}>\r
+               <li class="choix">#TITRE (<:article:> #ID_ARTICLE) - #STATUT</li>\r
+       </BOUCLE_articles_selectionnes>\r
+       </ul>\r
+       </B_articles_selectionnes>\r
+</BOUCLE_test_multiple>\r
+       <BOUCLE_article_selectionne(ARTICLES){id_article IN #ENV*{valeur}|picker_selected{article}}>\r
+               <p>#TITRE (<:article:> #ID_ARTICLE) - #STATUT</p>\r
+       </BOUCLE_article_selectionne>\r
+<//B_test_multiple>\r
diff --git a/www/plugins/saisies/saisies-vues/selecteur_article_fonctions.php b/www/plugins/saisies/saisies-vues/selecteur_article_fonctions.php
new file mode 100644 (file)
index 0000000..ba76acb
--- /dev/null
@@ -0,0 +1,5 @@
+<?php
+
+include_spip('prive/formulaires/selecteur/selecteur_fonctions');
+
+?>
diff --git a/www/plugins/saisies/saisies-vues/selecteur_document.html b/www/plugins/saisies/saisies-vues/selecteur_document.html
new file mode 100644 (file)
index 0000000..1e4ba7d
--- /dev/null
@@ -0,0 +1,3 @@
+<BOUCLE_doc(DOCUMENTS){id_document=#ENV{valeur}}>\r
+<p>#ID_DOCUMENT - #TITRE (#TYPE_DOCUMENT [(#TAILLE|taille_en_octets)])</p>\r
+</BOUCLE_doc>\r
diff --git a/www/plugins/saisies/saisies-vues/selecteur_rubrique.html b/www/plugins/saisies/saisies-vues/selecteur_rubrique.html
new file mode 100644 (file)
index 0000000..2bf9b24
--- /dev/null
@@ -0,0 +1,24 @@
+[(#REM) \r
+         \r
+  ### /!\ selecteur (spip Bonux) ###\r
+         Attention, ce qui est retourne est un tableau :\r
+         _request($name) = array('rubrique|3', 'rubrique|9', 'rubrique|10');\r
+         Une fonction de SPIP Bonux permet de récupérer un tableau d'identifiants par type d'objet :\r
+         [(#CHAMP|picker_selected{article})]\r
+         Cette fonction peut etre pratique dans une boucle en utilisant le critere IN\r
+         \r
+]\r
+\r
+<BOUCLE_test_multiple(CONDITION){si #ENV{multiple}|=={on}|oui}>\r
+       <B_rubriques_selectionnees>\r
+       <ul>\r
+       <BOUCLE_rubriques_selectionnees(RUBRIQUES){tout}{id_rubrique IN #ENV*{valeur}|picker_selected{rubrique}}>\r
+               <li class="choix">#TITRE (<:rubrique:> #ID_RUBRIQUE) - #STATUT</li>\r
+       </BOUCLE_rubriques_selectionnees>\r
+       </ul>\r
+       </B_rubriques_selectionnees>\r
+</BOUCLE_test_multiple>\r
+       <BOUCLE_rubrique_selectionnee(RUBRIQUES){tout}{id_rubrique IN #ENV*{valeur}|picker_selected{rubrique}}>\r
+               <p>#TITRE (<:rubrique:> #ID_RUBRIQUE) - #STATUT</p>\r
+       </BOUCLE_rubrique_selectionnee>\r
+<//B_test_multiple>\r
diff --git a/www/plugins/saisies/saisies-vues/selecteur_rubrique_article.html b/www/plugins/saisies/saisies-vues/selecteur_rubrique_article.html
new file mode 100644 (file)
index 0000000..c4d9724
--- /dev/null
@@ -0,0 +1,38 @@
+[(#REM) \r
+         \r
+  ### /!\ selecteur (spip Bonux) ###\r
+         Attention, ce qui est retourne est un tableau :\r
+         _request($name) = array('article|3', 'article|9', 'rubrique|10');\r
+         Une fonction de SPIP Bonux permet de récupérer un tableau d'identifiants par type d'objet :\r
+         [(#CHAMP|picker_selected{article})]\r
+         Cette fonction peut etre pratique dans une boucle en utilisant le critere IN\r
+         \r
+]\r
+#SET{reponse,""}\r
+\r
+<BOUCLE_test_multiple(CONDITION){si #ENV{multiple}|=={on}|oui}>\r
+       <B_rubriques_selectionnees>\r
+       <ul>\r
+       <BOUCLE_rubriques_selectionnees(RUBRIQUES){tout}{id_rubrique IN #ENV*{valeur}|picker_selected{rubrique}}>\r
+               <li class="choix">#TITRE (<:rubrique:> #ID_RUBRIQUE) - #STATUT</li>\r
+       </BOUCLE_rubriques_selectionnees>\r
+       </ul>\r
+       </B_rubriques_selectionnees>\r
+\r
+       <B_articles_selectionnes>\r
+       <ul>\r
+       <BOUCLE_articles_selectionnes(ARTICLES){id_article IN #ENV*{valeur}|picker_selected{article}}>\r
+               <li class="choix">#TITRE (<:article:> #ID_ARTICLE) - #STATUT</li>\r
+       </BOUCLE_articles_selectionnes>\r
+       </ul>\r
+       </B_articles_selectionnes>\r
+   \r
+</BOUCLE_test_multiple>\r
+       <BOUCLE_rubrique_selectionnee(RUBRIQUES){tout}{id_rubrique IN #ENV*{valeur}|picker_selected{rubrique}}>\r
+               <p>#TITRE (<:rubrique:> #ID_RUBRIQUE) - #STATUT</p>\r
+       </BOUCLE_rubrique_selectionnee>\r
+   \r
+   <BOUCLE_article_selectionne(ARTICLES){id_article IN #ENV*{valeur}|picker_selected{article}}>\r
+               <p>#TITRE (<:article:> #ID_ARTICLE) - #STATUT</p>\r
+       </BOUCLE_article_selectionne>\r
+<//B_test_multiple>\r
diff --git a/www/plugins/saisies/saisies-vues/selecteur_site.html b/www/plugins/saisies/saisies-vues/selecteur_site.html
new file mode 100644 (file)
index 0000000..1c5eca6
--- /dev/null
@@ -0,0 +1,13 @@
+<BOUCLE_test_multiple(CONDITION){si #ENV{multiple}|oui}>\r
+       <B_sites_selectionnes>\r
+       <ul>\r
+       <BOUCLE_sites_selectionnes(SITES){id_syndic IN #ENV*{valeur}}>\r
+               <li class="choix">#NOM SITE (#ID_SYNDIC)</li>\r
+       </BOUCLE_sites_selectionnes>\r
+       </ul>\r
+       </B_sites_selectionnes>\r
+</BOUCLE_test_multiple>\r
+       <BOUCLE_site_selectionne(SITES){id_syndic=#ENV{valeur}}>\r
+               <p>#NOM_SITE (#ID_SYNDIC)</p>\r
+       </BOUCLE_site_selectionne>\r
+<//B_test_multiple>\r
diff --git a/www/plugins/saisies/saisies-vues/selection.html b/www/plugins/saisies/saisies-vues/selection.html
new file mode 100644 (file)
index 0000000..4d5f5a6
--- /dev/null
@@ -0,0 +1,4 @@
+[(#REM) datas peut être une chaine qu'on sait décomposer ]\r
+#SET{datas, #ENV{datas}|saisies_chaine2tableau|saisies_aplatir_tableau}\r
+\r
+[<p>(#GET{datas/#ENV{valeur}})</p>]\r
diff --git a/www/plugins/saisies/saisies-vues/selection_multiple.html b/www/plugins/saisies/saisies-vues/selection_multiple.html
new file mode 100644 (file)
index 0000000..a13d173
--- /dev/null
@@ -0,0 +1,10 @@
+[(#REM) datas peut être une chaine qu'on sait décomposer ]\r
+#SET{datas, #ENV{datas}|saisies_chaine2tableau|saisies_aplatir_tableau}\r
+#SET{valeur, #ENV*{valeur}|saisies_valeur2tableau}\r
+<B_choix>\r
+<ul>\r
+       <BOUCLE_choix(POUR){tableau #GET{valeur}}>\r
+       <li class="choix">#GET{datas/#VALEUR}</li>\r
+       </BOUCLE_choix>\r
+</ul>\r
+</B_choix>\r
diff --git a/www/plugins/saisies/saisies.css.html b/www/plugins/saisies/saisies.css.html
new file mode 100644 (file)
index 0000000..1686610
--- /dev/null
@@ -0,0 +1,48 @@
+#CACHE{3600*100,cache-client}\r
+[(#REM)<style>]\r
+#HTTP_HEADER{Content-Type: text/css; charset=iso-8859-15}\r
+#HTTP_HEADER{Vary: Accept-Encoding}\r
+\r
+#SET{left,#LANG_DIR|choixsiegal{ltr,left,right}}\r
+#SET{right,#LANG_DIR|choixsiegal{ltr,right,left}}\r
+#SET{fleche,#LANG_DIR|choixsiegal{ltr,#CHEMIN{images/deplierhaut.gif},#CHEMIN{images/deplierhaut_rtl.gif}}}\r
+\r
+/* Dans l'espace privé, afficher les labels des vues de Saisies */\r
+#wysiwyg .afficher .label{ display:block; }\r
+\r
+li.fieldset.pliable > fieldset > .legend{\r
+       cursor:pointer;\r
+}\r
+\r
+li.fieldset.pliable > fieldset > .legend span{\r
+       padding-#GET{left}:15px;\r
+       background:transparent url(#CHEMIN{images/deplierbas.gif}) [(#GET{left}) ]center no-repeat;\r
+}\r
+\r
+li.fieldset.plie > fieldset > .legend span{\r
+       background-image:url(#GET{fleche});\r
+}\r
+/*[(#REM) date triple champs...\r
+       jour    mois    annee\r
+       /12/    /10/    /2010/\r
+]*/\r
+.saisie_date_jour_mois_annee .choix {float:left;}\r
+.saisie_date_jour_mois_annee .choix+.choix {margin-left:1em;}\r
+.saisie_date_jour_mois_annee .choix label{display:block; width:auto;}\r
+.saisie_date_jour_mois_annee .choix .text{width:auto;}\r
+\r
+/*[(#REM) Styles prives de bonux pour la mise en forme du selecteur d'article et/ou de rubrique si bonux disponible]*/\r
+[(#CHEMIN{prive/style_prive_plugin_bonux.html}|oui) \r
+  [(#INCLURE{fond=prive/style_prive_plugin_bonux,ltr=#LANG_LEFT})]\r
+]\r
+\r
+/*[(#REM) On ecrase le style de bonux qui met tous les labels a droite (ou gauche).\r
+        Sinon le p.explication du selecteur ne va pas a la ligne ]*/\r
+.formulaire_spip li.selecteur_item > label {\r
+       float:none;\r
+}\r
+\r
+.formulaire_spip li.selecteur_item div.choix label {\r
+       float:none;\r
+   display:inline;\r
+}\r
diff --git a/www/plugins/saisies/saisies/_base.html b/www/plugins/saisies/saisies/_base.html
new file mode 100644 (file)
index 0000000..626f280
--- /dev/null
@@ -0,0 +1,54 @@
+[(#REM) \r
+\r
+  Parametres :\r
+  ** : obligatoire\r
+  * : fortement conseille\r
+  \r
+  - ** nom : nom du parametre\r
+  - * label : nom joli\r
+  (- * erreurs : tableau des erreurs) (transmis par defaut avec SAISIE)\r
+  (- * valeur : valeur actuelle du parametre) (transmis par defaut avec SAISIE : valeur=#ENV{nom du parametre}})\r
+  - defaut : valeur par defaut du parametre\r
+  - obligatoire : est-ce un parametre obligatoire ? (defaut: non, valeurs : null/"non"/autre=oui )\r
+  - info_obligatoire : si obligatoire, ajoute ce contenu apres le label (defaut : "")\r
+  - explication : texte d'explication suppplementaire\r
+  - attention : texte pour les cas graves !\r
+  - disable : est-ce que le champ est desactive ? (pas de saisie possible, selection impossible, contenus non postes)\r
+              (defaut: non, valeurs : null/"non"/autre=oui ) n'est peut etre pas valable pour toutes les saisies.\r
+  - disable_avec_post : idem disable, mais en envoyant en hidden le champ tout de meme.\r
+  - readonly : est-ce que le champ est non modifiable ? (pas de saisie possible, selection possible, contenus postes)\r
+              (defaut: non, valeurs : null/"non"/autre=oui ) n'est peut etre pas valable pour toutes les saisies.\r
+\r
+  \r
+  Exemples d'appels :\r
+    [(#SAISIE{input, couleur_foncee,\r
+               label=<:spa:couleur_foncee:>,\r
+               obligatoire=oui})]\r
+\r
+]\r
+\r
+[(#ENV{nom}|oui)\r
+       #SET{obligatoire,#ENV{obligatoire}|et{#ENV{obligatoire}|!={non}}|?{obligatoire,''}}\r
+       #SET{disable,#ENV{disable,#ENV{disable_avec_post}}|et{#ENV{disable,#ENV{disable_avec_post}}|!={non}}|?{#ENV{disable}|is_array|?{#ENV{disable,#ARRAY},disabled},''}}\r
+       #SET{readonly,#ENV{readonly}|et{#ENV{readonly}!={non}}|?{readonly,''}}\r
+       #SET{saisies_autonomes,#VAL|saisies_autonomes}\r
+\r
+       [(#ENV{type_saisie}|in_array{#GET{saisies_autonomes}}|oui)\r
+               [(#INCLURE{fond=saisies/#ENV{type_saisie},env,obligatoire=#GET{obligatoire},disable=#GET{disable},readonly=#GET{readonly}})]\r
+       ]\r
+       [(#ENV{type_saisie}|in_array{#GET{saisies_autonomes}}|non)\r
+               #SET{erreurs,#ENV**{erreurs/#ENV{nom}}}\r
+               #SET{li_class,#ENV{type_saisie}|substr{0,9}|=={selecteur}|?{selecteur_item,''}}\r
+               <!--!inserer_saisie_editer-->\r
+               <li class="editer editer_[(#ENV{nom}|saisie_nom2classe)][ (#GET{obligatoire})][ (#GET{erreurs}|oui)erreur][ (#GET{li_class})][ (#ENV{li_class})][ saisie_(#ENV{type_saisie})]"[ data-id="(#ENV{id_saisie})"]>\r
+                       #ENV*{inserer_debut}\r
+                       [<label[(#ENV{type_saisie}|match{oui_non|radio|checkbox}|non) for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"]>(#ENV*{label})[<span class='obligatoire'>(#GET{obligatoire}|oui)[(#ENV*{info_obligatoire}|is_null|?{<:info_obligatoire_02:>,#ENV*{info_obligatoire}})]</span>]</label>]\r
+                       [<span class='erreur_message'>(#GET{erreurs})</span>]\r
+                       [<p class='explication'>(#ENV*{explication})</p>]\r
+                       [<em class='attention'>(#ENV*{attention})</em>]\r
+                       [(#INCLURE{fond=saisies/#ENV{type_saisie},env,nom=[(#ENV{nom}|saisie_nom2name)], disable=#GET{disable},readonly=#GET{readonly}})]\r
+                       [(#ENV{disable_avec_post}|et{#ENV{disable_avec_post}|!={non}}|oui)<input type='hidden' name='[(#ENV{nom}|saisie_nom2name)]' value="#ENV{valeur,#ENV{defaut}}" />]\r
+                       #ENV**{inserer_fin}\r
+               </li>\r
+       ]\r
+]\r
diff --git a/www/plugins/saisies/saisies/articles_originaux.html b/www/plugins/saisies/saisies/articles_originaux.html
new file mode 100644 (file)
index 0000000..d0e8d66
--- /dev/null
@@ -0,0 +1,51 @@
+[(#REM)\r
+\r
+Saisies qui liste les articles originaux (origine_traduction) du site\r
+Par défaut ne liste que ceux des rubriques à la racine (secteurs)\r
+\r
+  Parametres :\r
+  - class : classe(s) css ajoutes au select\r
+  - multiple : si quelquechose est passe, le select est multiple, sinon, c'est un select simple\r
+       Dans le cas multiple, defaut et valeur doivent etre un array, sinon un int\r
+  - recursif : si oui liste les articles des rubriques de facon recursive, et pas uniquement les secteurs\r
+  - option_intro : chaine de langue de la premiere ligne vide ? (defaut:"")\r
+    (seulement si non multiple)\r
+  - cacher_option_intro : pas de premier option vide  (defaut:"")\r
+  - defaut : valeur par defaut si pas présente dans l'environnement\r
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement\r
+\r
+  Exemple d'appel :\r
+       [(#SAISIE{articles_originaux,articles,\r
+               label=<:plugin:label_articles:>,\r
+               multiple=oui})]\r
+]\r
+<select name="#ENV{nom}[(#ENV{multiple}|?{\[\]})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][(#ENV{multiple}|oui) multiple="multiple" size="#ENV{size,10}"][ (#ENV*{attributs})]>\r
+       [(#ENV{cacher_option_intro}|ou{#ENV{multiple}}|non)\r
+               <option value="0">[(#ENV{option_intro})]</option>]\r
+               <BOUCLE_articles_originaux_racine(ARTICLES){id_rubrique}{origine_traduction}>\r
+               [(#ENV{multiple}|oui)\r
+               <option value="#ID_ARTICLE"[(#ID_ARTICLE|in_array{#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut,#ARRAY}}}}|oui) selected="selected"]>#TITRE</option>]\r
+               [(#ENV{multiple}|non)\r
+               <option value="#ID_ARTICLE"[(#ID_ARTICLE|=={#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}}|oui) selected="selected"]>#TITRE</option>]\r
+               </BOUCLE_articles_originaux_racine>\r
+       <BOUCLE_articles_page(ARTICLES){id_secteur<1}{origine_traduction}>\r
+       [(#ENV{multiple}|oui)\r
+               <option value="#ID_ARTICLE"[(#ID_ARTICLE|in_array{#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut,#ARRAY}}}}|oui) selected="selected"]>#TITRE</option>]\r
+               [(#ENV{multiple}|non)\r
+               <option value="#ID_ARTICLE"[(#ID_ARTICLE|=={#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}}|oui) selected="selected"]>#TITRE</option>]\r
+       </BOUCLE_articles_page>\r
+       <BOUCLE_secteurs(RUBRIQUES){id_parent = #ENV{id_rubrique,0}}{par num titre, titre}{tout}>\r
+       <optgroup label="#TITRE">\r
+               <B_articles_originaux>\r
+               <BOUCLE_articles_originaux(ARTICLES){id_rubrique}{origine_traduction}>\r
+               [(#ENV{multiple}|oui)\r
+               <option value="#ID_ARTICLE"[(#ID_ARTICLE|in_array{#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut,#ARRAY}}}}|oui) selected="selected"]>#TITRE</option>]\r
+               [(#ENV{multiple}|non)\r
+               <option value="#ID_ARTICLE"[(#ID_ARTICLE|=={#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}}|oui) selected="selected"]>#TITRE</option>]\r
+               </BOUCLE_articles_originaux>\r
+               </B_articles_originaux>\r
+               [(#ENV{recursif}|oui)\r
+               <INCLURE{fond=prive/listes/articles_originaux_recursifs,valeur=#ENV{valeur_forcee,#ENV{valeur}},id_rubrique,multiple} />]\r
+       </optgroup>             \r
+       </BOUCLE_secteurs>\r
+</select>\r
diff --git a/www/plugins/saisies/saisies/auteurs.html b/www/plugins/saisies/saisies/auteurs.html
new file mode 100644 (file)
index 0000000..39d4ed8
--- /dev/null
@@ -0,0 +1,44 @@
+[(#REM)\r
+\r
+       Todo:\r
+                       Faire fonctionner les images de statut avec SPIP 3...\r
+                       Elles sont dans [(#CHEMIN_IMAGE{auteur-1comite-16.png})]\r
+       \r
+  Parametres :\r
+  - class : classe(s) css ajoutes au select\r
+  - multiple : si quelquechose est passe, le select est multiple, sinon, c'est un select simple\r
+       Dans le cas multiple, defaut et valeur doivent etre un array, sinon un int\r
+  - option_intro : chaine de langue de la premiere ligne vide ? (defaut:"")\r
+    (seulement si non multiple)\r
+  - option_statut: si quelque chose est passe on insere un bonhomme de statut a cote du nom, sinon rien\r
+    (defaut = rien)\r
+  - cacher_option_intro : pas de premier option vide  (defaut:"")\r
+  - defaut : valeur par defaut si pas présente dans l'environnement\r
+  - tri : trier la liste (par défaut par nom), la valeur peut être un tableau [(#ARRAY{statut,nom})] ou une chaine "nom"\r
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement\r
+  - webmestre : si oui, ne selectionne que les webmestres\r
+  \r
+  Exemple d'appel :\r
+       [(#SAISIE{auteurs,auteurs_site,\r
+               label=<:plugin:auteurs_du_site:>,\r
+               multiple=oui\r
+               statut=0minirezo})]\r
+]\r
+#SET{bonhomme_statut,#ARRAY}\r
+[(#ENV{option_statut}|oui)\r
+       #SET{bonhomme_statut, #ARRAY{0minirezo, admin-12.gif, 1comite, redac-12.gif, 6forum, visit-12.gif}}\r
+]\r
+[(#ENV{multiple}|oui)\r
+       [(#SET{valeur,[(#ENV*{valeur}|is_array|?{[(#ENV*{valeur})],[(#ENV*{valeur}|explode{','})]})]})]\r
+]\r
+<select name="#ENV{nom}[(#ENV{multiple}|?{\[\]})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][(#ENV{multiple}|oui) multiple="multiple" size="#ENV{size,10}"][ disabled="(#ENV{disable})"][ (#ENV*{attributs})]>\r
+       [(#ENV{cacher_option_intro}|ou{#ENV{multiple}}|non)\r
+               <option value="">[(#ENV{option_intro})]</option>]\r
+       <BOUCLE_auteurs(AUTEURS){statut ?}{statut != 5poubelle}{webmestre ?}{par #ENV{tri,#ARRAY{num nom, nom}}|is_array|?{#ENV{tri,#ARRAY{num nom, nom}}|implode{','},#ENV{tri,#ARRAY{num nom, nom}}}}{tout}>\r
+               #SET{image_statut, #GET{bonhomme_statut/#STATUT}}\r
+               [(#ENV{multiple}|oui)\r
+               <option value="#ID_AUTEUR"[(#ID_AUTEUR|in_array{#ENV{valeur_forcee,#GET{valeur,#ENV{defaut,#ARRAY}}}}|oui) selected="selected"][(#GET{image_statut}|oui)[style="background:url((#CHEMIN{prive/images/#GET{image_statut}})) no-repeat left; padding-left: 20px;"]]>#NOM</option>]\r
+               [(#ENV{multiple}|non)\r
+               <option value="#ID_AUTEUR"[(#ID_AUTEUR|=={#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}}|oui) selected="selected"][(#GET{image_statut}|oui)[style="background:url((#CHEMIN{prive/images/#GET{image_statut}})) no-repeat left; padding-left: 20px;"]]>#NOM</option>]\r
+       </BOUCLE_auteurs>\r
+</select>\r
diff --git a/www/plugins/saisies/saisies/auteurs.yaml b/www/plugins/saisies/saisies/auteurs.yaml
new file mode 100644 (file)
index 0000000..0a089d4
--- /dev/null
@@ -0,0 +1,136 @@
+
+titre: '<:saisies:saisie_auteurs_titre:>'
+description: '<:saisies:saisie_auteurs_explication:>'
+icone: 'images/saisies_auteurs.png'
+options:
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'description'
+      label: '<:saisies:option_groupe_description:>'
+    saisies:
+      -
+        saisie: 'input'
+        options:
+          nom: 'label'
+          label: '<:saisies:option_label_label:>'
+          explication: '<:saisies:option_label_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'explication'
+          label: '<:saisies:option_explication_label:>'
+          explication: '<:saisies:option_explication_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'defaut'
+          label: '<:saisies:option_defaut_label:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'utilisation'
+      label: '<:saisies:option_groupe_utilisation:>'
+    saisies:
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'multiple'
+          label: '<:saisies:option_multiple_label:>'
+          explication: '<:saisies:option_multiple_explication:>'
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'disable'
+          label: '<:saisies:option_disable_label:>'
+          explication: '<:saisies:option_disable_explication:>'
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'disable_avec_post'
+          label: '<:saisies:option_disable_avec_post_label:>'
+          explication: '<:saisies:option_disable_avec_post_explication:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'affichage'
+      label: '<:saisies:option_groupe_affichage:>'
+    saisies:
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si'
+          label: '<:saisies:option_afficher_si_label:>'
+          explication: '<:saisies:option_afficher_si_explication:>'
+          rows: 5
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si_remplissage'
+          label: '<:saisies:option_afficher_si_remplissage_label:>'
+          explication: '<:saisies:option_afficher_si_remplissage_explication:>'
+          rows: 5
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'option_statut'
+          label: '<:saisies:option_option_statut_label:>'      
+      -
+        saisie: 'input'
+        options:
+          nom: 'option_intro'
+          label: '<:saisies:option_option_intro_label:>'
+          size: 50
+      -
+        saisie: 'case'
+        options:
+          nom: 'cacher_option_intro'
+          label_case: '<:saisies:option_cacher_option_intro_label:>'
+          defaut: 'on'
+      -
+        saisie: 'input'
+        options:
+          nom: 'attention'
+          label: '<:saisies:option_attention_label:>'
+          explication: '<:saisies:option_attention_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'size'
+          label: '<:saisies:option_size_label:>'
+          explication: '<:saisies:option_size_explication:>'
+        verifier:
+          type: 'entier'
+          options:
+            min: 1
+      -
+        saisie: 'input'
+        options:
+          nom: 'info_obligatoire'
+          label: '<:saisies:option_info_obligatoire_label:>'
+          explication: '<:saisies:option_info_obligatoire_explication:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'obligatoire'
+      label: '<:saisies:option_groupe_validation:>'
+    saisies:
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'obligatoire'
+          label: '<:saisies:option_obligatoire_label:>'
+      -
+        saisie: 'input'
+        options:
+          nom: 'erreur_obligatoire'
+          label: '<:saisies:option_erreur_obligatoire_label:>'
+          explication: '<:saisies:option_erreur_obligatoire_explication:>'
+defaut:
+  options:
+    label: '<:saisies:saisie_auteurs_titre:>'
+    # champs extras (definition du champ sql)
+    sql: "text DEFAULT '' NOT NULL"
diff --git a/www/plugins/saisies/saisies/case.html b/www/plugins/saisies/saisies/case.html
new file mode 100644 (file)
index 0000000..3a570c2
--- /dev/null
@@ -0,0 +1,21 @@
+[(#REM) 
+
+  Action :
+    - Rempli "on" si oui, "" si non.
+  
+  Parametres :
+    - label_case : pour un label a cote de la case (defaut:"")
+       - defaut : valeur par defaut si pas présente dans l'environnement
+    - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement
+  
+  Exemple d'appel :
+       [(#SAISIE{case, afficher_liste,
+               label=<:plugin:afficher_liste:>,
+               label_case=<:plugin:activer:>,
+               explication=<:plugin:explication_afficher_liste:>})]
+]#SET{valeur,#ENV{valeur_forcee,#ENV{valeur}}|is_null|?{#ENV{defaut},#ENV{valeur_forcee,#ENV{valeur}}}}
+<div class="choix">
+       [(#ENV{disable}|non)<input type="hidden" name="#ENV{nom}" value="#ENV{valeur_non,''}" />]
+       <input type="checkbox" name="#ENV{nom}" class="checkbox" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ (#GET{valeur}|=={#ENV{valeur_oui,on}}|oui)checked="checked"] value="#ENV{valeur_oui,on}" [ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][ (#ENV*{attributs})]/>
+       [<label for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[(#GET{valeur}|=={#ENV{valeur_oui,on}}|oui)class="on"]>(#ENV*{label_case})</label>]
+</div>
diff --git a/www/plugins/saisies/saisies/case.yaml b/www/plugins/saisies/saisies/case.yaml
new file mode 100644 (file)
index 0000000..98523a7
--- /dev/null
@@ -0,0 +1,116 @@
+
+titre: '<:saisies:saisie_case_titre:>'
+description: '<:saisies:saisie_case_explication:>'
+icone: 'images/saisies_case.png'
+options:
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'description'
+      label: '<:saisies:option_groupe_description:>'
+    saisies:
+      -
+        saisie: 'input'
+        options:
+          nom: 'label'
+          label: '<:saisies:option_label_label:>'
+          explication: '<:saisies:option_label_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'label_case'
+          label: '<:saisies:option_label_case_label:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'explication'
+          label: '<:saisies:option_explication_label:>'
+          explication: '<:saisies:option_explication_explication:>'
+          size: 50
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'defaut'
+          label: '<:saisies:option_defaut_label:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'utilisation'
+      label: '<:saisies:option_groupe_utilisation:>'
+    saisies:
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'disable'
+          label: '<:saisies:option_disable_label:>'
+          explication: '<:saisies:option_disable_explication:>'
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'disable_avec_post'
+          label: '<:saisies:option_disable_avec_post_label:>'
+          explication: '<:saisies:option_disable_avec_post_explication:>'
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'readonly'
+          label: '<:saisies:option_readonly_label:>'
+          explication: '<:saisies:option_readonly_explication:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'affichage'
+      label: '<:saisies:option_groupe_affichage:>'
+    saisies:
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si'
+          label: '<:saisies:option_afficher_si_label:>'
+          explication: '<:saisies:option_afficher_si_explication:>'
+          rows: 5
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si_remplissage'
+          label: '<:saisies:option_afficher_si_remplissage_label:>'
+          explication: '<:saisies:option_afficher_si_remplissage_explication:>'
+          rows: 5
+      -
+        saisie: 'input'
+        options:
+          nom: 'attention'
+          label: '<:saisies:option_attention_label:>'
+          explication: '<:saisies:option_attention_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'info_obligatoire'
+          label: '<:saisies:option_info_obligatoire_label:>'
+          explication: '<:saisies:option_info_obligatoire_explication:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'obligatoire'
+      label: '<:saisies:option_groupe_validation:>'
+    saisies:
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'obligatoire'
+          label: '<:saisies:option_obligatoire_label:>'
+      -
+        saisie: 'input'
+        options:
+          nom: 'erreur_obligatoire'
+          label: '<:saisies:option_erreur_obligatoire_label:>'
+          explication: '<:saisies:option_erreur_obligatoire_explication:>'
+defaut:
+  options:
+    label: '<:saisies:saisie_case_titre:>'
+    label_case: '<:saisies:saisie_case_titre:>'
+    # champs extras (definition du champ sql)
+    sql: "varchar(3) DEFAULT '' NOT NULL"
diff --git a/www/plugins/saisies/saisies/checkbox.html b/www/plugins/saisies/saisies/checkbox.html
new file mode 100644 (file)
index 0000000..1c26d92
--- /dev/null
@@ -0,0 +1,61 @@
+[(#REM) 
+
+  ### /!\ boucle POUR (spip Bonux) ###
+  
+  Parametres :
+  - datas : tableau de donnees cle=>valeur
+  - defaut : valeur par defaut si pas présente dans l'environnement
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement
+  
+  Exemple d'appel :
+       [(#SAISIE{checkbox, criteres,
+               label=<:plugin:choisir_criteres:>,
+               defaut=#ARRAY{0,cle1,1,cle3}, 
+               defaut=cle2,
+               datas=#ARRAY{
+                       cle1,valeur1,
+                       cle2,valeur2,
+                       cle3,valeur3}})]
+]
+
+[(#REM) datas peut être une chaine qu'on sait décomposer ]
+#SET{datas, #ENV*{datas}|saisies_chaine2tableau}
+
+[(#REM) defaut peut être une chaine (plusieurs valeurs ou pas) qu'on sait décomposer ]
+#SET{defaut, #ENV{defaut}|saisies_chaine2tableau}
+
+[(#REM) valeur doit être un tableau ! ]
+#SET{valeur, #ENV{valeur}|saisies_valeur2tableau}
+
+
+[(#REM) lorsque qu'on donne un 'disabled' qui est une chaine,
+       il faut la transformer en tableau. Ce tableau est vide si la chaine valait ''
+       sinon une clé 0 serait considérée disabled à tord
+]
+#SET{disabled,#ENV{disable}}
+[(#GET{disabled}|is_string|oui)
+       [(#GET{disabled}|strlen|non) #SET{disabled,#ARRAY} ]
+       [(#GET{disabled}|oui) #SET{disabled,#ARRAY|push{#GET{disabled}}} ]
+]
+[(#ENV{tout_selectionner}|oui)
+       <div class="[(#ENV{choix,choix})][ (#ENV{choix,choix})_tout_selectionner ]none-nojs">
+               <input type="checkbox" name="#ENV{nom}_tout" class="checkbox" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_tout" value="on" onChange="if (jQuery(this).prop('checked')==true) jQuery(this).parent('div').parent().find('input').prop('checked',true); else jQuery(this).parent('div').parent().find('input').prop('checked',false);"/>
+               <label for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_tout"><:saisies:tout_selectionner:></label>
+       </div>
+]
+
+<BOUCLE_checkbox(POUR){tableau #GET{datas}}>
+<div class="#ENV{choix,choix}[ (#ENV{choix,choix})_#CLE]">[(#SET{id,champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_[(#COMPTEUR_BOUCLE|concat{'-',#CLE}|md5)]})]
+       <input type="checkbox" name="#ENV{nom}[]" class="checkbox" id="#GET{id}"[ (#CLE|in_array{#ENV{valeur_forcee,#GET{valeur,#GET{defaut}}}}|oui)checked="checked"] value="#CLE"[(#CLE|in_array{#GET{disabled}}|oui) disabled="disabled"][ (#ENV*{attributs})] />
+       <label for="#GET{id}"[(#CLE|in_array{#ENV{valeur_forcee,#GET{valeur,#GET{defaut}}}}|oui)class="on"]>#VALEUR</label>
+</div>
+</BOUCLE_checkbox>
+[(#ENV{choix_alternatif}|oui)
+<div class="#ENV{choix,choix} choix_alternatif[ (#ENV{choix,choix})_alternatif]">
+    <input name="#ENV{nom}\[choix_alternatif\]" id="[champ_(#ENV{id,#ENV{nom}}|saisie_nom2classe)_choix_alternatif]" />
+    <label for="[champ_(#ENV{id,#ENV{nom}}|saisie_nom2classe)_choix_alternatif]">
+         [(#ENV{choix_alternatif_label, <:saisies:option_choix_alternatif_label_defaut:>})]
+    </label>
+</div>
+]
+</B_checkbox>
diff --git a/www/plugins/saisies/saisies/checkbox.yaml b/www/plugins/saisies/saisies/checkbox.yaml
new file mode 100644 (file)
index 0000000..533b91a
--- /dev/null
@@ -0,0 +1,135 @@
+
+titre: '<:saisies:saisie_checkbox_titre:>'
+description: '<:saisies:saisie_checkbox_explication:>'
+icone: 'images/saisies_checkbox.png'
+options:
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'description'
+      label: '<:saisies:option_groupe_description:>'
+    saisies:
+      -
+        saisie: 'input'
+        options:
+          nom: 'label'
+          label: '<:saisies:option_label_label:>'
+          explication: '<:saisies:option_label_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'explication'
+          label: '<:saisies:option_explication_label:>'
+          explication: '<:saisies:option_explication_explication:>'
+          size: 50
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'datas'
+          label: '<:saisies:option_datas_label:>'
+          explication: '<:saisies:option_datas_explication:>'
+          rows: 10
+          cols: 50
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'defaut'
+          label: '<:saisies:option_defaut_label:>'
+          rows: 10
+          cols: 50
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'choix_alternatif'
+          label: '<:saisies:option_choix_alternatif_label:>'
+          defaut: '' 
+      -
+        saisie: 'input'
+        options:
+          nom: 'choix_alternatif_label'
+          label: '<:saisies:option_choix_alternatif_label_label:>'
+          defaut: '<:saisies:option_choix_alternatif_label_defaut:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'utilisation'
+      label: '<:saisies:option_groupe_utilisation:>'
+    saisies:
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'disable'
+          label: '<:saisies:option_disable_label:>'
+          explication: '<:saisies:option_disable_explication:>'
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'disable_avec_post'
+          label: '<:saisies:option_disable_avec_post_label:>'
+          explication: '<:saisies:option_disable_avec_post_explication:>'
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'readonly'
+          label: '<:saisies:option_readonly_label:>'
+          explication: '<:saisies:option_readonly_explication:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'affichage'
+      label: '<:saisies:option_groupe_affichage:>'
+    saisies:
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si'
+          label: '<:saisies:option_afficher_si_label:>'
+          explication: '<:saisies:option_afficher_si_explication:>'
+          rows: 5
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si_remplissage'
+          label: '<:saisies:option_afficher_si_remplissage_label:>'
+          explication: '<:saisies:option_afficher_si_remplissage_explication:>'
+          rows: 5
+      -
+        saisie: 'input'
+        options:
+          nom: 'attention'
+          label: '<:saisies:option_attention_label:>'
+          explication: '<:saisies:option_attention_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'info_obligatoire'
+          label: '<:saisies:option_info_obligatoire_label:>'
+          explication: '<:saisies:option_info_obligatoire_explication:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'obligatoire'
+      label: '<:saisies:option_groupe_validation:>'
+    saisies:
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'obligatoire'
+          label: '<:saisies:option_obligatoire_label:>'
+      -
+        saisie: 'input'
+        options:
+          nom: 'erreur_obligatoire'
+          label: '<:saisies:option_erreur_obligatoire_label:>'
+          explication: '<:saisies:option_erreur_obligatoire_explication:>'
+defaut:
+  options:
+    label: '<:saisies:saisie_checkbox_titre:>'
+    datas:
+      choix1: '<:saisies:saisie_radio_defaut_choix1:>'
+      choix2: '<:saisies:saisie_radio_defaut_choix2:>'
+      choix3: '<:saisies:saisie_radio_defaut_choix3:>'
+    # champs extras (definition du champ sql)
+    sql: "text DEFAULT '' NOT NULL"
diff --git a/www/plugins/saisies/saisies/choisir_objet.html b/www/plugins/saisies/saisies/choisir_objet.html
new file mode 100644 (file)
index 0000000..c97e7a7
--- /dev/null
@@ -0,0 +1,23 @@
+[(#REM)
+       Saisie spécifique à SPIP 3
+       qui permet de choisir un objet parmis les
+       objets éditoriaux éditables de SPIP,
+       les affichant dans un sélecteur
+
+
+  Exemple d'appel :
+       [(#SAISIE{choisir_objet, mon_objet,
+               label=<:plugin:quel_objet:>})]
+
+]
+
+[(#REM) Attention, la valeur ou la valeur forcée peut être une chaine vide. On doit donc tester avec is_null. ]
+#SET{valeur,#ENV{valeur_forcee}|is_null|?{#ENV{valeur}|is_null|?{#ENV{defaut},#ENV{valeur}},#ENV{valeur_forcee}}}
+
+<select name="#ENV{nom}" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][ disabled="(#ENV{disable})"][ size="(#ENV{size})"][ (#ENV*{attributs})]>
+       [(#ENV{cacher_option_intro}|non)<option value="">[(#ENV{option_intro})]</option>]
+<BOUCLE_objets(POUR){tableau #REM|lister_tables_objets_sql}{cle !IN #ENV{exclus,''}}>[(#VALEUR{editable}|oui)
+       <option value="#CLE"[(#CLE|=={#GET{valeur}}|oui)selected="selected"]>[(#VALEUR{texte_objets}|_T)]</option>
+]</BOUCLE_objets>
+</select>
+
diff --git a/www/plugins/saisies/saisies/choisir_objets.html b/www/plugins/saisies/saisies/choisir_objets.html
new file mode 100644 (file)
index 0000000..5caa781
--- /dev/null
@@ -0,0 +1,21 @@
+[(#REM)
+       Saisie spécifique à SPIP 3
+       qui permet de choisir parmis des
+       objets éditoriaux éditables de SPIP,
+       les affichant avec des checkbox
+
+
+  Exemple d'appel :
+       [(#SAISIE{choisir_objets, gis_objets,
+               label=<:gis:cfg_lbl_activer_objets:>,
+               exclus=spip_gis})]
+
+]
+
+[(#REM) defaut peut être une chaine (plusieurs valeurs ou pas) qu'on sait décomposer ]
+#SET{defaut, #ENV{defaut}|saisies_chaine2tableau}
+
+[(#REM) valeur doit être un tableau ! ]
+#SET{valeur, #ENV{valeur}|saisies_valeur2tableau}
+
+#INCLURE{fond=formulaires/inc-choisir-objets,name=#ENV{nom},selected=#GET{valeur},exclus=#ENV{exclus}}
diff --git a/www/plugins/saisies/saisies/couleur.html b/www/plugins/saisies/saisies/couleur.html
new file mode 100644 (file)
index 0000000..40b4d2d
--- /dev/null
@@ -0,0 +1,12 @@
+[(#REM)\r
+\r
+    ### /!\ En Couleur si plugin Palette ###\r
+       \r
+       Memes parametres que saisies/input, mais redefinit\r
+       size et class.\r
+]\r
+[(#INCLURE{fond=saisies/input,\r
+                       env,\r
+            type=#HTML5|?{color,text},\r
+            size=7,\r
+            class=[(#ENV{readonly}|?{[(#ENV{class}) ]text,[(#ENV{class}) ]palette})]})]\r
diff --git a/www/plugins/saisies/saisies/date.html b/www/plugins/saisies/saisies/date.html
new file mode 100644 (file)
index 0000000..5583b79
--- /dev/null
@@ -0,0 +1,65 @@
+[(#REM) \r
+       Zone de saisie de date utilsant le dateur de Bonux si présent.\r
+       Sur les sites en HTML5, utilise type="date"\r
+       sur le input, et type="text" par défaut pour les autres.\r
+       Pour tous on utilise class="date" et class="heure" pour activer le dateur.\r
+       \r
+       La valeur fournie peut être :\r
+       - au format spip jj/mm/aaaa (date uniquement)\r
+       - au format SQL aaaa-mm-jj (date uniquement)\r
+       - au format SQL aaaa-mm-jj hh:mm:ss (date et heure)\r
+       - un tableau avec une entrée "date" et une entrée "heure" séparée, au format SQL (date et heure obligatoire)\r
+       \r
+       Pour utiliser les heures, il faut utiliser l'option "horaire=oui".\r
+       \r
+       La date est proposée à l'affichage au format jj/mm/aaaa.\r
+]\r
+\r
+[(#REM) Initialisation de la valeur ]\r
+#SET{valeur,#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}}\r
+\r
+[(#REM) Regex de date SQL ]\r
+#SET{date_sql,"[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]( [0-9][0-9]:[0-9][0-9]:[0-9][0-9])?"}\r
+\r
+[(#REM) Si la valeur est un tableau, on considère que c'est date et heure dans un format déjà reconnu (j/m/a ou SQL) ]\r
+[(#GET{valeur}|is_array|oui)\r
+       #SET{date, #GET{valeur/date}}\r
+       #SET{heure, #GET{valeur/heure}}\r
+]\r
+\r
+[(#REM) Si la valeur est une chaîne, on regarde si SQL ]\r
+[(#GET{valeur}|is_string|oui)\r
+       [(#REM) Par défaut (date uniquement au format SPIP) la date c'est toute la valeur ]\r
+       #SET{date, #GET{valeur}}\r
+       #SET{heure, ''}\r
+       \r
+       [(#REM) Si c'est bien une date SQL ]\r
+       [(#GET{valeur}|match{#GET{date_sql}}|oui)\r
+               [(#REM) Si la date est complètement 0, on met des valeurs vides ]\r
+               [(#GET{valeur}|=={0000-00-00 00:00:00}|oui)\r
+                       #SET{date, ''}\r
+                       #SET{heure, ''}\r
+               ]\r
+               [(#GET{valeur}|=={0000-00-00 00:00:00}|non)\r
+                       #SET{date, #GET{valeur}|affdate{d/m/Y}}\r
+                       #SET{heure, #GET{valeur}|affdate{H:i}}\r
+               ]\r
+       ]\r
+]\r
+\r
+[(#INCLURE{fond=saisies/input,\r
+       env,\r
+       nom=#ENV{nom}[(#ENV{horaire}|?{\[date\]})],\r
+       valeur=#GET{date},\r
+       type=text,\r
+       class=[(#ENV{class}) ]date})]\r
+[(#ENV{horaire}|oui)\r
+[(#INCLURE{fond=saisies/input,\r
+       env,\r
+       nom=#ENV{nom}\[heure\],\r
+       valeur=#GET{heure},\r
+       size=4,\r
+       maxlength=5,\r
+       class=[(#ENV{class}) ]heure})]\r
+]\r
+[(#ENV{disable}|non|et{#ENV{readonly}|non})[(#INCLURE{fond=formulaires/dateur/inc-dateur, heure_pas=#ENV{heure_pas,30}})]]\r
diff --git a/www/plugins/saisies/saisies/date.yaml b/www/plugins/saisies/saisies/date.yaml
new file mode 100644 (file)
index 0000000..bab933d
--- /dev/null
@@ -0,0 +1,146 @@
+\r
+titre: '<:saisies:saisie_date_titre:>'\r
+description: '<:saisies:saisie_date_explication:>'\r
+icone: 'images/saisies_date.png'\r
+options:\r
+  -\r
+    saisie: 'fieldset'\r
+    options:\r
+      nom: 'description'\r
+      label: '<:saisies:option_groupe_description:>'\r
+    saisies:\r
+      -\r
+        saisie: 'case'\r
+        options:\r
+          nom: 'horaire'\r
+          label: '<:saisies:option_horaire_label:>'\r
+          label_case: '<:saisies:option_horaire_label_case:>'\r
+          valeur_oui: 'oui'\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'label'\r
+          label: '<:saisies:option_label_label:>'\r
+          explication: '<:saisies:option_label_explication:>'\r
+          size: 50\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'defaut'\r
+          label: '<:saisies:option_defaut_label:>'\r
+          size: 50\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'explication'\r
+          label: '<:saisies:option_explication_label:>'\r
+          explication: '<:saisies:option_explication_explication:>'\r
+          size: 50\r
+  -\r
+    saisie: 'fieldset'\r
+    options:\r
+      nom: 'utilisation'\r
+      label: '<:saisies:option_groupe_utilisation:>'\r
+    saisies:\r
+      -\r
+        saisie: 'selection'\r
+        options:\r
+          nom: 'heure_pas'\r
+          label: '<:saisies:option_heure_pas_label:>'\r
+          explication: '<:saisies:option_heure_pas_explication:>'\r
+          datas:\r
+            1: '1 minute'\r
+            2: '2 minutes'\r
+            5: '5 minutes'\r
+            15: '15 minutes'\r
+            30: '30 minutes'\r
+          defaut: 30\r
+        verifier:\r
+          type: 'entier'\r
+          options:\r
+            min: 1\r
+            max: 30\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'disable'\r
+          label: '<:saisies:option_disable_label:>'\r
+          explication: '<:saisies:option_disable_explication:>'\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'disable_avec_post'\r
+          label: '<:saisies:option_disable_avec_post_label:>'\r
+          explication: '<:saisies:option_disable_avec_post_explication:>'\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'readonly'\r
+          label: '<:saisies:option_readonly_label:>'\r
+          explication: '<:saisies:option_readonly_explication:>'\r
+  -\r
+    saisie: 'fieldset'\r
+    options:\r
+      nom: 'affichage'\r
+      label: '<:saisies:option_groupe_affichage:>'\r
+    saisies:\r
+      -\r
+        saisie: 'textarea'\r
+        options:\r
+          nom: 'afficher_si'\r
+          label: '<:saisies:option_afficher_si_label:>'\r
+          explication: '<:saisies:option_afficher_si_explication:>'\r
+          rows: 5\r
+      -\r
+        saisie: 'textarea'\r
+        options:\r
+          nom: 'afficher_si_remplissage'\r
+          label: '<:saisies:option_afficher_si_remplissage_label:>'\r
+          explication: '<:saisies:option_afficher_si_remplissage_explication:>'\r
+          rows: 5\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'attention'\r
+          label: '<:saisies:option_attention_label:>'\r
+          explication: '<:saisies:option_attention_explication:>'\r
+          size: 50\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'info_obligatoire'\r
+          label: '<:saisies:option_info_obligatoire_label:>'\r
+          explication: '<:saisies:option_info_obligatoire_explication:>'\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'class'\r
+          label: '<:saisies:option_class_label:>'\r
+          size: 50\r
+  -\r
+    saisie: 'fieldset'\r
+    options:\r
+      nom: 'validation'\r
+      label: '<:saisies:option_groupe_validation:>'\r
+    saisies:\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'obligatoire'\r
+          label: '<:saisies:option_obligatoire_label:>'\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'erreur_obligatoire'\r
+          label: '<:saisies:option_erreur_obligatoire_label:>'\r
+          explication: '<:saisies:option_erreur_obligatoire_explication:>'\r
+defaut:\r
+  options:\r
+    label: '<:saisies:saisie_date_titre:>'\r
+    size: 40\r
+    # champs extras (definition du champ sql)\r
+    sql: "datetime DEFAULT '0000-00-00 00:00:00' NOT NULL"\r
+  verifier:\r
+    type: 'date'\r
+    options:\r
+      normaliser: 'datetime'\r
diff --git a/www/plugins/saisies/saisies/date_jour_mois_annee.html b/www/plugins/saisies/saisies/date_jour_mois_annee.html
new file mode 100644 (file)
index 0000000..b756b4a
--- /dev/null
@@ -0,0 +1,72 @@
+[(#REM)
+
+       /!\ Cette saisie nécessite du javascript pour fonctionner.
+               En absence de JS, seul un champ de saisie texte est affiché.
+       
+       Cette saisie cree 3 champs a partir d'une date au format datetime mysql.
+       A chaque modification, un champ cache est modifie, contenant la date au format datetime.
+
+       Options :
+       - label_(jour|mois|annee)
+       - size_(jour|mois|annee)
+       - maxlength_(jour|mois|annee)
+       - datetime, par défaut oui. Si pas oui, utilisera une date au format aaaa-mm-jj au lieu d'un datetime mysql
+       
+       [(#SAISIE{date_jour_mois_annee, date_naissance,
+               label=Date de naissance
+       })]
+]
+#SET{valeur,#ENV{valeur,#ENV{defaut}}}
+[(#SET{id,[(#ENV**|md5|substr{0,6})]})]
+<input type='text' name="#ENV{nom}" id="#GET{id}" value="[(#GET{valeur}|sinon{0000-00-00[(#ENV{datetime,oui}|=={oui}|oui)00:00:00]})]" class='datetime' />
+
+<script type='text/javascript'>
+function activer_dateur_#GET{id}() {
+       jour = '\
+               <div class="choix">\
+                       [<label for="champ_#ENV{nom}_jour">(#ENV{label_jour,<:saisies:label_jour:>})</label>]\
+                       <input type="text" name="#ENV{nom}_jour" class="text date_jour[ (#ENV{class})]" id="champ_#ENV{nom}_jour"[ value="(#GET{valeur}|journum)"][ size="(#ENV{size_jour, 2})"][ maxlength="(#ENV{maxlength_jour, 2})"][ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][(#ENV{obligatoire}|et{#HTML5}|oui) required="required"] onChange="changer_la_date(this);" />\
+               </div>\
+       ';
+               
+       mois = '\
+               <div class="choix">\
+                       [<label for="champ_#ENV{nom}_mois">(#ENV{label_mois,<:saisies:label_mois:>})</label>]\
+                       <input type="text" name="#ENV{nom}_mois" class="text date_mois[ (#ENV{class})]" id="champ_#ENV{nom}_mois"[ value="(#GET{valeur}|mois)"][ size="(#ENV{size_mois, 2})"][ maxlength="(#ENV{maxlength_mois, 2})"][ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][(#ENV{obligatoire}|et{#HTML5}|oui) required="required"] onChange="changer_la_date(this);" />\
+               </div>\
+       ';
+       
+       annee = '\
+               <div class="choix">\
+                       [<label for="champ_#ENV{nom}_annee">(#ENV{label_annee,<:saisies:label_annee:>})</label>]\
+                       <input type="text" name="#ENV{nom}_annee" class="text date_annee[ (#ENV{class})]" id="champ_#ENV{nom}_annee"[ value="(#GET{valeur}|annee)"][ size="(#ENV{size_annee, 4})"][ maxlength="(#ENV{maxlength_annee, 4})"][ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][(#ENV{obligatoire}|et{#HTML5}|oui) required="required"] onChange="changer_la_date(this);" />\
+               </div>\
+       ';
+
+       if(!$.browser.msie || parseInt($.browser.version) >= 9)
+               jQuery('##GET{id}')[0].type = 'hidden';
+       else
+               jQuery('##GET{id}').css({'height':'0px','width':'0px','border':'none'});
+       jQuery('##GET{id}').after(jour + mois + annee);
+}
+
+function changer_la_date(me) {
+       var li = jQuery(me).parents('li'),
+               jour = jQuery.trim(li.find('.date_jour').val()),
+               mois = jQuery.trim(li.find('.date_mois').val()),
+               annee = jQuery.trim(li.find('.date_annee').val()),
+               date = jQuery.trim(li.find('.datetime').val());
+       while(jour.length < 2) {jour = '0' + jour;}
+       while(mois.length < 2) {mois = '0' + mois;}
+       while(annee.length < 4) {annee = '0' + annee;}
+       [(#ENV{datetime,oui}|=={oui}|oui)
+       date = annee + '-' + mois + '-' + jour + date.substring(10);]
+       [(#ENV{datetime,oui}|=={oui}|non)
+       date = annee + '-' + mois + '-' + jour;]
+       li.find('.datetime').attr('value',date);
+}
+
+jQuery(document).ready(function(){
+       activer_dateur_#GET{id}();
+});
+</script>
diff --git a/www/plugins/saisies/saisies/destinataires.html b/www/plugins/saisies/saisies/destinataires.html
new file mode 100644 (file)
index 0000000..0e56968
--- /dev/null
@@ -0,0 +1,68 @@
+       #SET{type_choix, #ENV{type_choix,tous}}
+#SET{choix_destinataires, #ENV*{choix_destinataires,#ARRAY}}
+#SET{erreurs,#ENV**{erreurs/#ENV{nom}}}
+<BOUCLE_choix(CONDITION){si #GET{choix_destinataires}|count|<{2}|ou{#GET{type_choix}|=={tous}}}>
+<li class="editer editer_[(#ENV{nom})][ (#ENV{obligatoire})][ (#GET{erreurs}|oui)erreur][ (#ENV{li_class})][ saisie_(#ENV{type_saisie})]" [(#ENV{tout_afficher}|!={oui}|?{style="display:none;"})][ data-id="(#ENV{id_saisie})"]>
+       #ENV*{inserer_debut}
+       [<label for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]">(#ENV*{label})[<span class='obligatoire'>(#ENV{obligatoire}|oui)[(#ENV*{info_obligatoire}|is_null|?{<:info_obligatoire_02:>,#ENV*{info_obligatoire}})]</span>]</label>]
+       [<span class='erreur_message'>(#GET{erreurs})</span>]
+       [<p class='explication'>(#ENV*{explication})</p>]
+       [<em class='attention'>(#ENV*{attention})</em>]
+       [(#REM) Si 0 ou 1 destinataire possible ou si c'est tous, le destinataire est défini automatiquement ]
+       <BOUCLE_tous(AUTEURS){tous}{id_auteur IN #GET*{choix_destinataires}}{par num nom,nom}>
+       [(#ENV{tout_afficher}|!={oui}|oui)
+       <input type="hidden" name="#ENV{nom}\[\]" value="#ID_AUTEUR" />
+       ]
+       [(#ENV{tout_afficher}|!={oui}|non)
+       <div class="choix">
+               <input type="checkbox" name="#ENV{nom}\[\]" class="checkbox"
+                       readonly="readonly" checked="checked"
+                       id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_destinataire#ID_AUTEUR"
+                       value="#ID_AUTEUR"[ (#ID_AUTEUR|in_array{#ENV{valeur_forcee,#ENV*{valeur,#ENV*{defaut,#ARRAY}}}}|oui)checked="checked"]
+               />
+               <label for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_destinataire#ID_AUTEUR">#NOM</label>
+       </div>
+       ]
+       </BOUCLE_tous>
+       <input [(#ENV{tout_afficher}|!={oui}|?{type="hidden",type="text" readonly="readonly"})] name="#ENV{nom}[]" value="1" />
+       <//B_tous>
+       #ENV*{inserer_fin}
+</li>
+</BOUCLE_choix>
+
+[(#SET{valeur,[(#ENV*{valeur}|is_array|?{#ENV*{valeur},[(#ENV*{valeur}|explode{','})]})]})]
+<li class="editer editer_[(#ENV{nom})][ (#ENV{obligatoire})][ (#GET{erreurs}|oui)erreur][ (#ENV{li_class})][ saisie_(#ENV{type_saisie})]">
+
+       #ENV*{inserer_debut}
+       [<label[(#GET{type_choix}|=={un}|ou{#GET{type_choix}|=={un_radio}}|oui)for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"]>(#ENV*{label})[<span class='obligatoire'>(#ENV{obligatoire}|oui)[(#ENV*{info_obligatoire}|is_null|?{<:info_obligatoire_02:>,#ENV*{info_obligatoire}})]</span>]</label>]
+       [<span class='erreur_message'>(#GET{erreurs})</span>]
+       [<p class='explication'>(#ENV*{explication})</p>]
+       [<em class='attention'>(#ENV*{attention})</em>]
+       [(#REM) Sinon on propose le choix, en select ou en checkbox suivant l'option "type_choix" ]
+       <B_destinataires>
+               [(#GET{type_choix}|=={un}|oui)
+                       <select name="#ENV{nom}\[\]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]">
+                               [<option value="">(#ENV{option_intro})</option>]
+               ]
+               <BOUCLE_destinataires(AUTEURS){tous}{id_auteur IN #GET*{choix_destinataires}}{par num nom,nom}>
+                       [(#GET{type_choix}|=={plusieurs}|ou{#GET{type_choix}|=={un_radio}}|oui)
+                               <div class="choix">
+                                       <input type="[(#GET{type_choix}|=={plusieurs}|?{checkbox,radio})]" name="#ENV{nom}\[\]" class="checkbox"
+                                               id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_destinataire#ID_AUTEUR"
+                                               value="#ID_AUTEUR"[ (#ID_AUTEUR|in_array{#ENV*{valeur_forcee,#GET*{valeur,#ENV*{defaut,#ARRAY}}}}|oui)checked="checked"]
+                                       />
+                                       <label for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_destinataire#ID_AUTEUR"[(#ID_AUTEUR|in_array{#ENV*{valeur_forcee,#GET*{valeur,#ENV*{defaut,#ARRAY}}}}|oui)class="on"]>#NOM</label>
+                               </div>
+                       ]
+                       [(#GET{type_choix}|=={un}|oui)
+                               <option value="#ID_AUTEUR"[ (#ID_AUTEUR|in_array{#ENV{valeur_forcee,#GET*{valeur,#ENV*{defaut,#ARRAY}}}}|oui)selected="selected"]>#NOM</option>
+                       ]
+
+               </BOUCLE_destinataires>
+               [(#GET{type_choix}|=={un}|oui)
+                       </select>
+               ]
+       </B_destinataires>
+       #ENV*{inserer_fin}
+</li>
+<//B_choix>
diff --git a/www/plugins/saisies/saisies/destinataires.yaml b/www/plugins/saisies/saisies/destinataires.yaml
new file mode 100644 (file)
index 0000000..20a4304
--- /dev/null
@@ -0,0 +1,104 @@
+
+titre: '<:saisies:saisie_destinataires_titre:>'
+description: '<:saisies:saisie_destinataires_explication:>'
+options:
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'description'
+      label: '<:saisies:option_groupe_description:>'
+    saisies:
+      -
+        saisie: 'input'
+        options:
+          nom: 'label'
+          label: '<:saisies:option_label_label:>'
+          explication: '<:saisies:option_label_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'explication'
+          label: '<:saisies:option_explication_label:>'
+          explication: '<:saisies:option_explication_explication:>'
+          size: 50
+      -
+        saisie: 'auteurs'
+        options:
+          nom: 'choix_destinataires'
+          label: '<:saisies:option_choix_destinataires_label:>'
+          explication: '<:saisies:option_choix_destinataires_explication:>'
+          multiple: 'oui'
+          option_statut: 'oui'
+      -
+        saisie: 'radio'
+        options:
+          nom: 'type_choix'
+          datas:
+            tous: '<:saisies:option_type_choix_tous:>'
+            un: '<:saisies:option_type_choix_un:>'
+            un_radio: '<:saisies:option_type_choix_un_radio:>'
+            plusieurs: '<:saisies:option_type_choix_plusieurs:>'
+          defaut: 'tous'
+      -
+        saisie: 'input'
+        options:
+          nom: 'option_intro'
+          label: '<:saisies:option_option_destinataire_intro_label:>'
+          size: 50
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'affichage'
+      label: '<:saisies:option_groupe_affichage:>'
+    saisies:
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si'
+          label: '<:saisies:option_afficher_si_label:>'
+          explication: '<:saisies:option_afficher_si_explication:>'
+          rows: 5
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si_remplissage'
+          label: '<:saisies:option_afficher_si_remplissage_label:>'
+          explication: '<:saisies:option_afficher_si_remplissage_explication:>'
+          rows: 5
+      -
+        saisie: 'input'
+        options:
+          nom: 'attention'
+          label: '<:saisies:option_attention_label:>'
+          explication: '<:saisies:option_attention_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'info_obligatoire'
+          label: '<:saisies:option_info_obligatoire_label:>'
+          explication: '<:saisies:option_info_obligatoire_explication:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'obli'
+      label: '<:saisies:option_groupe_validation:>'
+    saisies:
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'obligatoire'
+          label: '<:saisies:option_obligatoire_label:>'
+      -
+        saisie: 'input'
+        options:
+          nom: 'erreur_obligatoire'
+          label: '<:saisies:option_erreur_obligatoire_label:>'
+          explication: '<:saisies:option_erreur_obligatoire_explication:>'
+defaut:
+  options:
+    label: '<:saisies:saisie_destinataires_titre:>'
+    choix_destinataires: [1,2,3,4]
+    # champs extras (definition du champ sql)
+    sql: "text DEFAULT '' NOT NULL"
diff --git a/www/plugins/saisies/saisies/email.html b/www/plugins/saisies/saisies/email.html
new file mode 100644 (file)
index 0000000..35109c0
--- /dev/null
@@ -0,0 +1,10 @@
+[(#REM) \r
+               \r
+               Zone de saisie d'adresse mail. Sur les sites en HTML5, utilise type="email"\r
+               sur le input, et type="text" par défaut pour les autres.\r
+               Dans ce dernier cas, on renseigne quand même class="email".\r
+               \r
+][(#INCLURE{fond=saisies/input,\r
+                       env,\r
+            type=#HTML5|?{email,text},\r
+            class=[(#HTML5|?{[(#ENV{class}) ],[(#ENV{class}) ]email})]})]\r
diff --git a/www/plugins/saisies/saisies/explication.html b/www/plugins/saisies/saisies/explication.html
new file mode 100644 (file)
index 0000000..868fa0b
--- /dev/null
@@ -0,0 +1,5 @@
+<li class="explication[ explication_(#ENV{nom})][ (#ENV{li_class})][ saisie_(#ENV{type_saisie})]"[ data-id="(#ENV{id_saisie})"]>
+       #ENV*{inserer_debut}
+       [(#ENV*{texte}|propre)]
+       #ENV*{inserer_fin}
+</li>
diff --git a/www/plugins/saisies/saisies/explication.yaml b/www/plugins/saisies/saisies/explication.yaml
new file mode 100644 (file)
index 0000000..45e7de9
--- /dev/null
@@ -0,0 +1,52 @@
+
+titre: '<:saisies:saisie_explication_titre:>'
+description: '<:saisies:saisie_explication_explication:>'
+icone: 'images/saisies_explication.png'
+options:
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'description'
+      label: '<:saisies:option_groupe_description:>'
+    saisies:
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'texte'
+          label: '<:saisies:saisie_explication_titre:>'
+          explication: '<:saisies:saisie_explication_explication:>'
+          class: 'inserer_barre_edition'
+          cols: 40
+          rows: 5
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'affichage'
+      label: '<:saisies:option_groupe_affichage:>'
+    saisies:
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si'
+          label: '<:saisies:option_afficher_si_label:>'
+          explication: '<:saisies:option_afficher_si_explication:>'
+          rows: 5
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si_remplissage'
+          label: '<:saisies:option_afficher_si_remplissage_label:>'
+          explication: '<:saisies:option_afficher_si_remplissage_explication:>'
+          rows: 5
+      -
+        saisie: 'input'
+        options:
+          nom: 'li_class'
+          label: '<:saisies:option_class_label:>'
+          size: 50
+defaut:
+  options:
+    texte: '<:saisies:saisie_explication_titre:>'
+    # champs extras (definition du champ sql)
+    # Non, cette saisie n'a rien à saisir en bdd !
+    # sql: "text DEFAULT '' NOT NULL"
diff --git a/www/plugins/saisies/saisies/fieldset.html b/www/plugins/saisies/saisies/fieldset.html
new file mode 100644 (file)
index 0000000..d246ba0
--- /dev/null
@@ -0,0 +1,34 @@
+#SET{pliable,#ENV{pliable}|et{#ENV{pliable}|!={non}}|?{'pliable', ''}}
+#SET{plie,#ENV{plie}|et{#ENV{plie}|!={non}}|?{'plie', ''}}
+
+[(#REM) S'il y a des erreurs pour au moins un des champs internes, on ne plie pas ! ]
+#SET{champs_internes, #ENV{saisies}|saisies_lister_par_nom}
+#SET{erreurs, #ENV**{erreurs}|sinon{#ARRAY}}
+#SET{erreurs_fieldset, #GET{erreurs}|array_intersect_key{#GET{champs_internes}}}
+[(#GET{erreurs_fieldset}|oui)
+       #SET{plie, ''}
+]
+#SET{erreur_ici,#ENV**{erreurs/#ENV{nom}}}
+
+<li class="fieldset[ fieldset_(#ENV{nom})][ (#ENV{li_class})][ saisie_(#ENV{type_saisie})][ (#GET{pliable})[ (#GET{plie})]]"[ data-id="(#ENV{id_saisie})"]> 
+       #ENV*{inserer_debut}
+       <fieldset>
+               [[(#GLOBALS{debut_intertitre,<h3>}|inserer_attribut{class,legend})]
+                       [(#ENV{icone}|oui)
+                               [(#REM) en SPIP 3 on cherche avant tout dans le chemin des images
+                               ]#SET{icone,''}[(#SPIP_VERSION|version_compare{2.9, >}|oui)#SET{icone,#CHEMIN_IMAGE{#ENV{icone}}]
+                               [<img src="(#GET{icone}|sinon{#CHEMIN{#ENV{icone}}}
+                                       |image_reduire{#ENV{taille_icone}|sinon{16}}
+                                       |extraire_attribut{src})" alt="#ENV*{label}" /> ]
+                       ]
+                       <span>(#ENV*{label})</span>#GLOBALS{fin_intertitre,</h3>}]
+               [<span class='erreur_message'>(#GET{erreur_ici})</span>]
+               [<p class='explication'>(#ENV*{explication})</p>]
+               [(#ENV{saisies}|is_array|oui)
+               <ul>
+                       #INCLURE{fond=#ENV{fond_generer,"inclure/generer_saisies"}, env, saisies=#ENV{saisies}, from_fieldset='on'}
+               </ul>
+               ]
+       </fieldset>
+       #ENV*{inserer_fin}
+</li>
diff --git a/www/plugins/saisies/saisies/fieldset.yaml b/www/plugins/saisies/saisies/fieldset.yaml
new file mode 100644 (file)
index 0000000..f63ed80
--- /dev/null
@@ -0,0 +1,70 @@
+
+titre: '<:saisies:saisie_fieldset_titre:>'
+description: '<:saisies:saisie_fieldset_explication:>'
+icone: 'images/saisies_fieldset.png'
+options:
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'description'
+      label: '<:saisies:option_groupe_description:>'
+    saisies:
+      -
+        saisie: 'input'
+        options:
+          nom: 'label'
+          label: '<:saisies:option_label_label:>'
+          explication: '<:saisies:option_label_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'explication'
+          label: '<:saisies:option_explication_label:>'
+          explication: '<:saisies:option_explication_explication:>'
+          size: 50
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'affichage'
+      label: '<:saisies:option_groupe_affichage:>'
+    saisies:
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si'
+          label: '<:saisies:option_afficher_si_label:>'
+          explication: '<:saisies:option_afficher_si_explication:>'
+          rows: 5
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si_remplissage'
+          label: '<:saisies:option_afficher_si_remplissage_label:>'
+          explication: '<:saisies:option_afficher_si_remplissage_explication:>'
+          rows: 5
+      -
+        saisie: 'case'
+        options:
+          nom: 'pliable'
+          label: '<:saisies:option_pliable_label:>'
+          label_case: '<:saisies:option_pliable_label_case:>'
+      -
+        saisie: 'case'
+        options:
+          nom: 'plie'
+          label: '<:saisies:option_plie_label:>'
+          label_case: '<:saisies:option_plie_label_case:>'
+      -
+        saisie: 'input'
+        options:
+          nom: 'li_class'
+          label: '<:saisies:option_class_label:>'
+          size: 50
+defaut:
+  options:
+    label: '<:saisies:saisie_fieldset_titre:>'
+    # champs extras (definition du champ sql)
+    # Non, cette saisie n'a rien à saisir en bdd !
+    # sql: "text DEFAULT '' NOT NULL"
+  saisies: []
diff --git a/www/plugins/saisies/saisies/groupe_mots.html b/www/plugins/saisies/saisies/groupe_mots.html
new file mode 100644 (file)
index 0000000..a363ee2
--- /dev/null
@@ -0,0 +1,32 @@
+[(#REM) 
+
+  Parametres :
+  - class : classe(s) css ajoutes au select
+  - multiple : si quelquechose est passe, le select est multiple, sinon, c'est un select simple
+       Dans le cas multiple, defaut et valeur doivent être un array, sinon un int
+  - option_intro : chaine de langue de la premiere ligne vide ? (defaut:"")
+    (seulement si non multiple)
+  - cacher_option_intro : pas de premier option vide  (defaut:"")
+  - table_liaison : permet de ne lister que les groupes de mots liés à une table en particulier
+    (champs tables_liees de spip_groupes_mots)
+  - defaut : valeur par defaut si pas présente dans l'environnement
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement
+  - groupes_exclus : tableau de groupes exclus
+  - afficher_id_groupe : afficher le numero du groupe en plus de son titre
+
+  Exemple d'appel :
+       [(#SAISIE{groupe_mots,groupes,
+               label=<:plugin:secteur_region:>,
+               multiple=oui,
+               table_liaison=articles})] 
+]
+<select name="#ENV{nom}[(#ENV{multiple}|?{\[\]})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][(#ENV{multiple}|oui) multiple="multiple" size="#ENV{size,10}"][ disabled="(#ENV{disable})"][ (#ENV*{attributs})]>
+       [(#ENV{cacher_option_intro}|ou{#ENV{multiple}}|non)
+               <option value="0">[(#ENV{option_intro})]</option>]
+       <BOUCLE_groupes(GROUPES_MOTS){id_groupe !IN #ENV{groupes_exclus,#ARRAY}}{par num titre, titre}{tables_liees==#VAL{'(^|,)'}|concat{#ENV{table_liaison,.*},'($|,)'}}{tout}>
+               [(#ENV{multiple}|oui)
+               <option value="#ID_GROUPE"[(#ID_GROUPE|in_array{#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut,#ARRAY}}}}|oui) selected="selected"]>#TITRE[ \(<:info_numero_abbreviation:>(#ENV{afficher_id_groupe,''}|?{#ID_GROUPE})\)]</option>]
+               [(#ENV{multiple}|non)
+               <option value="#ID_GROUPE"[(#ID_GROUPE|=={#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}}|oui) selected="selected"]>#TITRE[ \(<:info_numero_abbreviation:>(#ENV{afficher_id_groupe,''}|?{#ID_GROUPE})\)]</option>]
+       </BOUCLE_groupes>
+</select>
diff --git a/www/plugins/saisies/saisies/hidden.html b/www/plugins/saisies/saisies/hidden.html
new file mode 100644 (file)
index 0000000..866c70a
--- /dev/null
@@ -0,0 +1,12 @@
+<li class="editer editer_[(#ENV{nom})][ (#ENV{li_class})][ saisie_(#ENV{type_saisie})]" [(#ENV{tout_afficher}|!={oui}|?{style="display:none;"})][ data-id="(#ENV{id_saisie})"]>
+       #ENV*{inserer_debut}
+       [(#ENV{tout_afficher}|!={oui}|oui)
+       <input type="hidden" name="#ENV{nom}" id="champ_[(#ENV{nom}|saisie_nom2classe)]" value="#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}"[ (#ENV*{attributs})] />
+       ]
+       [(#ENV{tout_afficher}|!={oui}|non)
+       [<label for="champ_[(#ENV{nom}|saisie_nom2classe)]">(#ENV*{label})[<span class='obligatoire'>(#ENV{obligatoire}|oui)[(#ENV*{info_obligatoire}|is_null|?{<:info_obligatoire_02:>,#ENV*{info_obligatoire}})]</span>]</label>]
+       <input type="text" name="#ENV{nom}" id="champ_[(#ENV{nom}|saisie_nom2classe)]" value="#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}" readonly="readonly" />
+       ]
+       
+       #ENV*{inserer_fin}
+</li>
diff --git a/www/plugins/saisies/saisies/hidden.yaml b/www/plugins/saisies/saisies/hidden.yaml
new file mode 100644 (file)
index 0000000..2120917
--- /dev/null
@@ -0,0 +1,29 @@
+
+titre: '<:saisies:saisie_hidden_titre:>'
+description: '<:saisies:saisie_hidden_explication:>'
+icone: 'images/saisies_hidden.png'
+options:
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'description'
+      label: '<:saisies:option_groupe_description:>'
+    saisies:
+      -
+        saisie: 'input'
+        options:
+          nom: 'label'
+          label: '<:saisies:option_label_label:>'
+          explication: '<:saisies:option_label_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'defaut'
+          label: '<:saisies:option_defaut_label:>'
+          size: 50
+defaut:
+  options:
+    label: '<:saisies:saisie_hidden_titre:>'
+    # champs extras (definition du champ sql)
+    sql: "text DEFAULT '' NOT NULL"
diff --git a/www/plugins/saisies/saisies/input.html b/www/plugins/saisies/saisies/input.html
new file mode 100644 (file)
index 0000000..33ec302
--- /dev/null
@@ -0,0 +1,48 @@
+[(#REM) \r
+\r
+  Parametres supplementaire :\r
+  - ** datas : tableau de donnees indice=>valeur\r
+  - defaut : valeur par defaut du parametre\r
+  - type : type de l'input (defaut: text)\r
+  - class : classe(s) css ajoutes a l'input\r
+  - size : taille du champ\r
+  - maxlength : nombre de caracteres maximum\r
+  - disable : champ insaisissable ? 'oui' (defaut : '')\r
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement\r
+  - autofocus : indique si le champ prend le focus a l'affichage (HTML5 requis)\r
+  \r
+  Exemple d'appel :\r
+       [(#SAISIE{input,couleur_foncee,\r
+               label=<:spa:couleur_foncee:>,\r
+               size=7,\r
+               datas=#ARRAY{\r
+                               0,valeur0,\r
+                               1,valeur1,\r
+                               2,valeur2}})]\r
+]\r
+\r
+#SET{type,#ENV{type,text}}\r
+\r
+[(#REM) datas peut être une chaine qu'on sait décomposer ]\r
+#SET{datas, #ENV*{datas}}\r
+#SET{datas, #GET{datas}|is_string|?{(#GET{datas}|saisies_chaine2tableau), #GET{datas}}}\r
+\r
+[(#REM) Pas de HTML5, pas de datas]\r
+#SET{datas, #HTML5|?{#GET{datas}}}\r
+\r
+[(#REM)  l'attribut autocomplete ne peut avoir pour valeur que on ou off ]\r
+#SET{val_autocomplete, #ARRAY}\r
+#SET{val_autocomplete, #GET{val_autocomplete}|push{on}}\r
+#SET{val_autocomplete, #GET{val_autocomplete}|push{off}}\r
+\r
+[(#REM) permettre de donner un identifiant de list specifique en option de la saisie\r
+]#SET{list_id,#ENV{list}}\r
+<B_selection>\r
+[(#SET{list_id,[(#GET{list_id,[champ_(#ENV{nom})_datas]})]})]\r
+<datalist id="#GET{list_id}">\r
+<BOUCLE_selection(POUR){tableau #GET{datas}}\r
+>[     <option value="(#VALEUR|attribut_html)"></option>\r
+]</BOUCLE_selection>\r
+</datalist>\r
+</B_selection>\r
+<input type="#GET{type}" name="#ENV{nom}" class="#GET{type}[ (#ENV{class})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ list="(#GET{list_id})"][ value="(#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}})"][ size="(#ENV{size})"][ maxlength="(#ENV{maxlength})"][ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][ placeholder="(#ENV{placeholder})"][(#ENV{obligatoire}|et{#ENV{obligatoire}!={non}}|et{#HTML5}|oui) required="required"][(#ENV{autofocus}|et{#ENV{autofocus}!={non}}|et{#HTML5}|oui) autofocus="autofocus"][(#GET{val_autocomplete}|find{#ENV{autocomplete}}|oui) autocomplete="#ENV{autocomplete}"][ (#ENV*{attributs})] />\r
diff --git a/www/plugins/saisies/saisies/input.yaml b/www/plugins/saisies/saisies/input.yaml
new file mode 100644 (file)
index 0000000..62a43ab
--- /dev/null
@@ -0,0 +1,156 @@
+\r
+titre: '<:saisies:saisie_input_titre:>'\r
+description: '<:saisies:saisie_input_explication:>'\r
+icone: 'images/saisies_input.png'\r
+options:\r
+  -\r
+    saisie: 'fieldset'\r
+    options:\r
+      nom: 'description'\r
+      label: '<:saisies:option_groupe_description:>'\r
+    saisies:\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'label'\r
+          label: '<:saisies:option_label_label:>'\r
+          explication: '<:saisies:option_label_explication:>'\r
+          size: 50\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'defaut'\r
+          label: '<:saisies:option_defaut_label:>'\r
+          size: 50\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'explication'\r
+          label: '<:saisies:option_explication_label:>'\r
+          explication: '<:saisies:option_explication_explication:>'\r
+          size: 50\r
+      -\r
+        saisie: 'case'\r
+        options:\r
+          nom: 'type'\r
+          label_case: '<:saisies:option_type_password:>'\r
+          valeur_oui: 'password'\r
+          valeur_non: 'text'\r
+          defaut: 'text'\r
+  -\r
+    saisie: 'fieldset'\r
+    options:\r
+      nom: 'utilisation'\r
+      label: '<:saisies:option_groupe_utilisation:>'\r
+    saisies:\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'maxlength'\r
+          label: '<:saisies:option_maxlength_label:>'\r
+          explication: '<:saisies:option_maxlength_explication:>'\r
+        verifier:\r
+          type: 'entier'\r
+          options:\r
+            min: 1\r
+      -\r
+        saisie: 'case'\r
+        options:\r
+          nom: 'disable'\r
+          label_case: '<:saisies:option_disable_label:>'\r
+          explication: '<:saisies:option_disable_explication:>'\r
+      -\r
+        saisie: 'case'\r
+        options:\r
+          nom: 'disable_avec_post'\r
+          label_case: '<:saisies:option_disable_avec_post_label:>'\r
+          explication: '<:saisies:option_disable_avec_post_explication:>'\r
+      -\r
+        saisie: 'case'\r
+        options:\r
+          nom: 'readonly'\r
+          label_case: '<:saisies:option_readonly_label:>'\r
+          explication: '<:saisies:option_readonly_explication:>'\r
+  -\r
+    saisie: 'fieldset'\r
+    options:\r
+      nom: 'affichage'\r
+      label: '<:saisies:option_groupe_affichage:>'\r
+    saisies:\r
+      -\r
+        saisie: 'textarea'\r
+        options:\r
+          nom: 'afficher_si'\r
+          label: '<:saisies:option_afficher_si_label:>'\r
+          explication: '<:saisies:option_afficher_si_explication:>'\r
+          rows: 5\r
+      -\r
+        saisie: 'textarea'\r
+        options:\r
+          nom: 'afficher_si_remplissage'\r
+          label: '<:saisies:option_afficher_si_remplissage_label:>'\r
+          explication: '<:saisies:option_afficher_si_remplissage_explication:>'\r
+          rows: 5\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'attention'\r
+          label: '<:saisies:option_attention_label:>'\r
+          explication: '<:saisies:option_attention_explication:>'\r
+          size: 50\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'info_obligatoire'\r
+          label: '<:saisies:option_info_obligatoire_label:>'\r
+          explication: '<:saisies:option_info_obligatoire_explication:>'\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'class'\r
+          label: '<:saisies:option_class_label:>'\r
+          size: 50\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'size'\r
+          label: '<:saisies:option_size_label:>'\r
+          explication: '<:saisies:option_size_explication:>'\r
+        verifier:\r
+          type: 'entier'\r
+          options:\r
+            min: 1\r
+      -\r
+        saisie: 'radio'\r
+        options:\r
+          nom: 'autocomplete'\r
+          label: '<:saisies:option_autocomplete_label:>'\r
+          explication: '<:saisies:option_autocomplete_explication:>' \r
+          datas:\r
+            defaut: '<:saisies:option_autocomplete_defaut:>'\r
+            on: '<:saisies:option_autocomplete_on:>'\r
+            off: '<:saisies:option_autocomplete_off:>'\r
+          defaut: 'defaut'\r
+  -\r
+    saisie: 'fieldset'\r
+    options:\r
+      nom: 'validation'\r
+      label: '<:saisies:option_groupe_validation:>'\r
+    saisies:\r
+      -\r
+        saisie: 'case'\r
+        options:\r
+          nom: 'obligatoire'\r
+          label_case: '<:saisies:option_obligatoire_label:>'\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'erreur_obligatoire'\r
+          label: '<:saisies:option_erreur_obligatoire_label:>'\r
+          explication: '<:saisies:option_erreur_obligatoire_explication:>'\r
+defaut:\r
+  options:\r
+    label: '<:saisies:saisie_input_titre:>'\r
+    size: 40\r
+    # champs extras (definition du champ sql)\r
+    sql: "text DEFAULT '' NOT NULL"\r
diff --git a/www/plugins/saisies/saisies/mot.html b/www/plugins/saisies/saisies/mot.html
new file mode 100644 (file)
index 0000000..1765f87
--- /dev/null
@@ -0,0 +1,38 @@
+[(#REM) 
+
+  Parametres :
+  - class : classe(s) css ajoutes au select
+  - option_intro : chaine de langue de la premiere ligne vide ? (defaut:"")
+  - cacher_option_intro : pas de premier option vide  (defaut:"")
+  - id_groupe : groupe des mots clés à afficher
+  - defaut : valeur par defaut si pas présente dans l'environnement
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement
+  
+  Exemple d'appel :
+       [(#SAISIE{mot, en_region,
+               label=<:plugin:en_region:>})]
+]
+<BOUCLE_multiples(GROUPES_MOTS){id_groupe}{unseul!=oui}>
+       [(#REM) defaut peut être une chaine (plusieurs valeurs ou pas) qu'on sait décomposer ]
+       #SET{defaut, #ENV{defaut}|saisies_chaine2tableau}
+       [(#REM) valeur doit être un tableau ! ]
+       #SET{valeur, #ENV{valeur}|saisies_valeur2tableau}
+       [(#REM) valeur doit être un tableau ! ]
+       #SET{valeur_forcee, #ENV{valeur_forcee}|saisies_valeur2tableau}
+       <BOUCLE_checkmots(MOTS){par num titre, titre}{id_groupe}>
+       <div class="choix choix_#ID_MOT[ (#ENV{class})]">[(#SET{id,champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_[(#ID_MOT|concat{'-',#ENV{nom}}|md5)]})]
+               [(#SET{checked,[(#ID_MOT|in_array{#GET{valeur_forcee,#GET{valeur,#GET{defaut}}}}|oui)]})]
+               <input type="checkbox" name="#ENV{nom}[]" class="checkbox" id="#GET{id}"[ (#GET{checked})checked="checked"] value="#ID_MOT"[ (#ENV*{attributs})] />
+               <label for="#GET{id}"[(#GET{checked})class="on"]>#TITRE</label>
+       </div>
+       </BOUCLE_checkmots>
+</BOUCLE_multiples>
+<select name="#ENV{nom}" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][ value="(#ENV{valeur_forcee,#ENV{valeur}})"][ disabled="(#ENV{disable})"][ (#ENV*{attributs})]>
+[(#ENV{cacher_option_intro}|non)
+       <option value="">[(#ENV{option_intro})]</option>]
+<BOUCLE_mots(MOTS){par id_groupe, num titre, titre}{id_groupe ?}>[(#ENV{id_groupe,''}|non)
+       [<optgroup label="(#TYPE|unique|attribut_html)" />]
+       ]<option value="#ID_MOT" [(#ID_MOT|=={#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}}|oui) selected="selected"]>#TITRE</option>
+</BOUCLE_mots>
+</select>
+<//B_multiples>
diff --git a/www/plugins/saisies/saisies/mot.yaml b/www/plugins/saisies/saisies/mot.yaml
new file mode 100644 (file)
index 0000000..6433628
--- /dev/null
@@ -0,0 +1,131 @@
+
+titre: '<:saisies:saisie_mot_titre:>'
+description: '<:saisies:saisie_mot_explication:>'
+icone: 'images/saisies_mot.png'
+options:
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'description'
+      label: '<:saisies:option_groupe_description:>'
+    saisies:
+      -
+        saisie: 'input'
+        options:
+          nom: 'label'
+          label: '<:saisies:option_label_label:>'
+          explication: '<:saisies:option_label_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'explication'
+          label: '<:saisies:option_explication_label:>'
+          explication: '<:saisies:option_explication_explication:>'
+          size: 50
+      -
+        saisie: 'groupe_mots'
+        options:
+          nom: 'id_groupe'
+          label: '<:saisies:option_id_groupe_label:>'
+          afficher_id_groupe: ' '
+      -
+        saisie: 'input'
+        options:
+          nom: 'defaut'
+          label: '<:saisies:option_defaut_label:>'
+          size: 50
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'utilisation'
+      label: '<:saisies:option_groupe_utilisation:>'
+    saisies:
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'disable'
+          label: '<:saisies:option_disable_label:>'
+          explication: '<:saisies:option_disable_explication:>'
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'disable_avec_post'
+          label: '<:saisies:option_disable_avec_post_label:>'
+          explication: '<:saisies:option_disable_avec_post_explication:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'affichage'
+      label: '<:saisies:option_groupe_affichage:>'
+    saisies:
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si'
+          label: '<:saisies:option_afficher_si_label:>'
+          explication: '<:saisies:option_afficher_si_explication:>'
+          rows: 5
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si_remplissage'
+          label: '<:saisies:option_afficher_si_remplissage_label:>'
+          explication: '<:saisies:option_afficher_si_remplissage_explication:>'
+          rows: 5
+      -
+        saisie: 'input'
+        options:
+          nom: 'option_intro'
+          label: '<:saisies:option_option_intro_label:>'
+          size: 50
+      -
+        saisie: 'case'
+        options:
+          nom: 'cacher_option_intro'
+          label_case: '<:saisies:option_cacher_option_intro_label:>'
+      -
+        saisie: 'input'
+        options:
+          nom: 'attention'
+          label: '<:saisies:option_attention_label:>'
+          explication: '<:saisies:option_attention_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'size'
+          label: '<:saisies:option_size_label:>'
+          explication: '<:saisies:option_size_explication:>'
+        verifier:
+          type: 'entier'
+          options:
+            min: 1
+      -
+        saisie: 'input'
+        options:
+          nom: 'info_obligatoire'
+          label: '<:saisies:option_info_obligatoire_label:>'
+          explication: '<:saisies:option_info_obligatoire_explication:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'obligatoire'
+      label: '<:saisies:option_groupe_validation:>'
+    saisies:
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'obligatoire'
+          label: '<:saisies:option_obligatoire_label:>'
+      -
+        saisie: 'input'
+        options:
+          nom: 'erreur_obligatoire'
+          label: '<:saisies:option_erreur_obligatoire_label:>'
+          explication: '<:saisies:option_erreur_obligatoire_explication:>'
+defaut:
+  options:
+    label: '<:saisies:saisie_mot_titre:>'
+    # champs extras (definition du champ sql)
+    sql: "text DEFAULT '' NOT NULL"
diff --git a/www/plugins/saisies/saisies/oui_non.html b/www/plugins/saisies/saisies/oui_non.html
new file mode 100644 (file)
index 0000000..03a33f9
--- /dev/null
@@ -0,0 +1,23 @@
+[(#REM) 
+
+  Action :
+  - Rempli "on" si oui, "" si non.
+  
+  Parametres :
+  - defaut : valeur par defaut si pas présente dans l'environnement
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement
+  
+  Exemple d'appel :
+       [(#SAISIE{oui_non, afficher_liste,
+               label=<:plugin:afficher_liste:>,
+               explication=<:plugin:explication_afficher_liste:>})]
+]
+#SET{valeur,#ENV{valeur_forcee,#ENV{valeur}}|is_null|?{#ENV{defaut},#ENV{valeur_forcee,#ENV{valeur}}}}
+<div class="choix">
+       <input type="radio" name="#ENV{nom}" class="radio" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_oui"[ (#GET{valeur}|=={#ENV{valeur_oui,on}}|oui)checked="checked"] value="#ENV{valeur_oui,on}" [ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"] />
+       <label for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_oui"[(#GET{valeur}|=={#ENV{valeur_oui,on}}|oui)class="on"]><:item_oui:></label>
+</div>
+<div class="choix">
+       <input type="radio" name="#ENV{nom}" class="radio" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_non"[ (#GET{valeur}|=={#ENV{valeur_non,''}}|ou{#GET{valeur}|non})checked="checked"] value="#ENV{valeur_non,''}" [ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"] />
+       <label for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_non"[(#GET{valeur}|=={#ENV{valeur_non,''}}|ou{#GET{valeur}|non})class="on"]><:item_non:></label>
+</div>
diff --git a/www/plugins/saisies/saisies/oui_non.yaml b/www/plugins/saisies/saisies/oui_non.yaml
new file mode 100644 (file)
index 0000000..fe92a2e
--- /dev/null
@@ -0,0 +1,109 @@
+
+titre: '<:saisies:saisie_oui_non_titre:>'
+description: '<:saisies:saisie_oui_non_explication:>'
+icone: 'images/saisies_oui_non.png'
+options:
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'description'
+      label: '<:saisies:option_groupe_description:>'
+    saisies:
+      -
+        saisie: 'input'
+        options:
+          nom: 'label'
+          label: '<:saisies:option_label_label:>'
+          explication: '<:saisies:option_label_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'explication'
+          label: '<:saisies:option_explication_label:>'
+          explication: '<:saisies:option_explication_explication:>'
+          size: 50
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'defaut'
+          label: '<:saisies:option_defaut_label:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'utilisation'
+      label: '<:saisies:option_groupe_utilisation:>'
+    saisies:
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'disable'
+          label: '<:saisies:option_disable_label:>'
+          explication: '<:saisies:option_disable_explication:>'
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'disable_avec_post'
+          label: '<:saisies:option_disable_avec_post_label:>'
+          explication: '<:saisies:option_disable_avec_post_explication:>'
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'readonly'
+          label: '<:saisies:option_readonly_label:>'
+          explication: '<:saisies:option_readonly_explication:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'affichage'
+      label: '<:saisies:option_groupe_affichage:>'
+    saisies:
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si'
+          label: '<:saisies:option_afficher_si_label:>'
+          explication: '<:saisies:option_afficher_si_explication:>'
+          rows: 5
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si_remplissage'
+          label: '<:saisies:option_afficher_si_remplissage_label:>'
+          explication: '<:saisies:option_afficher_si_remplissage_explication:>'
+          rows: 5
+      -
+        saisie: 'input'
+        options:
+          nom: 'attention'
+          label: '<:saisies:option_attention_label:>'
+          explication: '<:saisies:option_attention_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'info_obligatoire'
+          label: '<:saisies:option_info_obligatoire_label:>'
+          explication: '<:saisies:option_info_obligatoire_explication:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'obligatoire'
+      label: '<:saisies:option_groupe_validation:>'
+    saisies:
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'obligatoire'
+          label: '<:saisies:option_obligatoire_label:>'
+      -
+        saisie: 'input'
+        options:
+          nom: 'erreur_obligatoire'
+          label: '<:saisies:option_erreur_obligatoire_label:>'
+          explication: '<:saisies:option_erreur_obligatoire_explication:>'
+defaut:
+  options:
+    label: '<:saisies:saisie_oui_non_titre:>'
+    # champs extras (definition du champ sql)
+    sql: "varchar(3) DEFAULT '' NOT NULL"
diff --git a/www/plugins/saisies/saisies/pays.html b/www/plugins/saisies/saisies/pays.html
new file mode 100644 (file)
index 0000000..c1c85b8
--- /dev/null
@@ -0,0 +1,21 @@
+[(#REM) 
+
+  ### /!\ table GEO_PAYS (geographie) ###
+       
+  Parametres :
+  - class : classe(s) css ajoutes au select
+  - option_intro : chaine de langue de la premiere ligne vide ? (defaut:"")
+  - cacher_option_intro : pas de premier option vide  (defaut:"")
+  - defaut : valeur par defaut si pas présente dans l'environnement
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement
+  
+  Exemple d'appel :
+       [(#SAISIE{pays, pays,
+               label=<:plugin:info_pays:>})] 
+]
+<select name="#ENV{nom}" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][ disabled="(#ENV{disable})"][ (#ENV*{attributs})]>
+[(#ENV{cacher_option_intro}|non)<option value="">[(#ENV{option_intro})]</option>]
+<BOUCLE_pays(GEO_PAYS){par multi nom}>
+       <option value="#ID_PAYS"[(#ID_PAYS|=={#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}}|oui) selected="selected"]>#NOM</option>
+</BOUCLE_pays>
+</select>
diff --git a/www/plugins/saisies/saisies/police.html b/www/plugins/saisies/saisies/police.html
new file mode 100644 (file)
index 0000000..475524e
--- /dev/null
@@ -0,0 +1,22 @@
+[(#REM) 
+
+  ### /!\ boucle POUR ###
+  
+  Parametres :
+  - class : classe(s) css ajoutes au select
+  - option_intro : chaine de langue de la premiere ligne vide ? (defaut:"")
+  - cacher_option_intro : pas de premier option vide  (defaut:"")
+  - defaut : valeur par defaut si pas présente dans l'environnement
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement
+  
+  Exemple d'appel :
+       [(#SAISIE{police, couleur_foncee, 
+               label=<:spa:couleur_foncee:>, 
+               obligatoire=non})] 
+]
+<select name="#ENV{nom}" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][ disabled="(#ENV{disable})"][ (#ENV*{attributs})]>
+       [(#ENV{cacher_option_intro}|non)<option value="">[(#ENV{option_intro})]</option>]
+       <BOUCLE_police(POUR){tableau #VAL{polices/}|find_all_in_path{\w+\.ttf}}{par cle}>
+               <option value="#CLE"[ (#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}|=={#CLE}|oui)selected="selected"]>#CLE</option>
+       </BOUCLE_police>
+</select>
diff --git a/www/plugins/saisies/saisies/position_construire_formulaire.html b/www/plugins/saisies/saisies/position_construire_formulaire.html
new file mode 100644 (file)
index 0000000..1d896d8
--- /dev/null
@@ -0,0 +1,46 @@
+<select name="#ENV{nom}" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]">
+       #SET{tab,#ENV{formulaire}}
+       #SET{tab_par_nom,#ENV{formulaire}|saisies_lister_par_nom}
+       #SET{padding,0}
+       #SET{liste_parents,#ARRAY{0,''}}
+       <BOUCLE_parcours(DATA){source tableau, #GET{tab}}>
+       #SET{saisie,#VALEUR}
+       <option value="#GET{saisie/options/nom}" style="padding-left:#GET{padding}px" [(#ENV{valeur,#ENV{saisie_a_positionner}}|=={#GET{saisie/options/nom}}|oui)selected="selected"]>
+               [(#GET{saisie/options/label}
+                       |sinon{#GET{saisie/options/nom}}
+                       |couper{60})]
+       </option>
+               <BOUCLE_test_enfants(CONDITION){si #GET{saisie/options/nom}|!={#ENV{saisie_a_positionner}}|et{#GET{saisie/saisies}|is_array}}>
+               #SET{tab,#GET{saisie/saisies}}
+               #SET{padding,#GET{padding}|plus{20}}
+               #SET_PUSH{liste_parents,#GET{saisie/options/nom}}
+               <BOUCLE_parcours_recursif(BOUCLE_parcours)/>
+               #SET{padding,#GET{padding}|moins{20}}
+               #SET{liste_parents,#GET{liste_parents}|array_slice{0,-1}}
+               </BOUCLE_test_enfants>
+       </BOUCLE_parcours>
+       #SET{parent, #GET{liste_parents}|table_valeur{#GET{liste_parents}|count|moins{1}}}
+       <option value="[#GET{parent}]" style="padding-left:#GET{padding}px; font-style:italic;">
+               [(#GET{parent}|non)
+                       <:saisies:construire_position_fin_formulaire:>
+               ]
+               [(#GET{parent}|oui)
+                       #SET{groupe,#GET{tab_par_nom/#GET{parent}}}
+                       #SET{groupe,#GET{groupe/options/label}|sinon{#GET{groupe/options/nom}}|couper{60}}
+                       <:saisies:construire_position_fin_groupe{groupe=#GET{groupe}}:>
+               ]
+       </option>
+       </B_parcours>
+       #SET{parent, #GET{liste_parents}|table_valeur{#GET{liste_parents}|count|moins{1}}}
+       <option value="\[#GET{parent}\]" style="padding-left:#GET{padding}px; font-style:italic;">
+               [(#GET{parent}|non)
+                       <:saisies:construire_position_fin_formulaire:>
+               ]
+               [(#GET{parent}|oui)
+                       #SET{groupe,#GET{tab_par_nom/#GET{parent}}}
+                       #SET{groupe,#GET{groupe/options/label}|sinon{#GET{groupe/options/nom}}|couper{60}}
+                       <:saisies:construire_position_fin_groupe{groupe=#GET{groupe}}:>
+               ]
+       </option>
+       <//B_parcours>
+</select>
diff --git a/www/plugins/saisies/saisies/radio.html b/www/plugins/saisies/saisies/radio.html
new file mode 100644 (file)
index 0000000..84bd1ff
--- /dev/null
@@ -0,0 +1,30 @@
+[(#REM) 
+
+  ### /!\ boucle POUR (spip Bonux) ###
+  
+  Parametres :
+  - datas : tableau de donnees cle=>valeur
+  - defaut : valeur par defaut si pas présente dans l'environnement
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement
+  
+  Exemple d'appel :
+       [(#SAISIE{radio, afficher_liste,
+               label=<:plugin:afficher_liste:>,
+               explication=<:plugin:explication_afficher_liste:>,
+               datas=#ARRAY{
+                       cle1,valeur1,
+                       cle2,valeur2,
+                       cle3,valeur3}})]
+]
+
+[(#REM) datas peut être une chaine qu'on sait décomposer ]
+#SET{datas, #ENV*{datas}}
+#SET{datas, #GET{datas}|is_string|?{(#GET{datas}|saisies_chaine2tableau), #GET{datas}}}
+
+<BOUCLE_radio(POUR){tableau #GET{datas}}>
+#SET{disabled, #ENV{disable}|is_string|?{#ENV{disable}, #ENV{disable/#CLE}}}
+<div class="#ENV{choix,choix}[ (#ENV{choix,choix})_#CLE]">
+       <input type="radio" name="#ENV{nom}" class="radio" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_#COMPTEUR_BOUCLE"[ (#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}|=={#CLE}|oui)checked="checked"] value="#CLE"[(#GET{disabled}|oui) disabled="disabled"][ readonly="(#ENV{readonly})"] />
+       <label for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_#COMPTEUR_BOUCLE"[(#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}|=={#CLE}|oui)class="on"]>#VALEUR</label>
+</div>
+</BOUCLE_radio>
diff --git a/www/plugins/saisies/saisies/radio.yaml b/www/plugins/saisies/saisies/radio.yaml
new file mode 100644 (file)
index 0000000..151f001
--- /dev/null
@@ -0,0 +1,122 @@
+
+titre: '<:saisies:saisie_radio_titre:>'
+description: '<:saisies:saisie_radio_explication:>'
+icone: 'images/saisies_radio.png'
+options:
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'description'
+      label: '<:saisies:option_groupe_description:>'
+    saisies:
+      -
+        saisie: 'input'
+        options:
+          nom: 'label'
+          label: '<:saisies:option_label_label:>'
+          explication: '<:saisies:option_label_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'explication'
+          label: '<:saisies:option_explication_label:>'
+          explication: '<:saisies:option_explication_explication:>'
+          size: 50
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'datas'
+          label: '<:saisies:option_datas_label:>'
+          explication: '<:saisies:option_datas_explication:>'
+          rows: 10
+          cols: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'defaut'
+          label: '<:saisies:option_defaut_label:>'
+          size: 50
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'utilisation'
+      label: '<:saisies:option_groupe_utilisation:>'
+    saisies:
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'disable'
+          label: '<:saisies:option_disable_label:>'
+          explication: '<:saisies:option_disable_explication:>'
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'disable_avec_post'
+          label: '<:saisies:option_disable_avec_post_label:>'
+          explication: '<:saisies:option_disable_avec_post_explication:>'
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'readonly'
+          label: '<:saisies:option_readonly_label:>'
+          explication: '<:saisies:option_readonly_explication:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'affichage'
+      label: '<:saisies:option_groupe_affichage:>'
+    saisies:
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si'
+          label: '<:saisies:option_afficher_si_label:>'
+          explication: '<:saisies:option_afficher_si_explication:>'
+          rows: 5
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si_remplissage'
+          label: '<:saisies:option_afficher_si_remplissage_label:>'
+          explication: '<:saisies:option_afficher_si_remplissage_explication:>'
+          rows: 5
+      -
+        saisie: 'input'
+        options:
+          nom: 'attention'
+          label: '<:saisies:option_attention_label:>'
+          explication: '<:saisies:option_attention_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'info_obligatoire'
+          label: '<:saisies:option_info_obligatoire_label:>'
+          explication: '<:saisies:option_info_obligatoire_explication:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'obligatoire'
+      label: '<:saisies:option_groupe_validation:>'
+    saisies:
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'obligatoire'
+          label: '<:saisies:option_obligatoire_label:>'
+      -
+        saisie: 'input'
+        options:
+          nom: 'erreur_obligatoire'
+          label: '<:saisies:option_erreur_obligatoire_label:>'
+          explication: '<:saisies:option_erreur_obligatoire_explication:>'
+defaut:
+  options:
+    label: '<:saisies:saisie_radio_titre:>'
+    datas:
+      choix1: '<:saisies:saisie_radio_defaut_choix1:>'
+      choix2: '<:saisies:saisie_radio_defaut_choix2:>'
+      choix3: '<:saisies:saisie_radio_defaut_choix3:>'
+    # champs extras (definition du champ sql)
+    sql: "text DEFAULT '' NOT NULL"
diff --git a/www/plugins/saisies/saisies/recherche.html b/www/plugins/saisies/saisies/recherche.html
new file mode 100644 (file)
index 0000000..5ccf0d9
--- /dev/null
@@ -0,0 +1,10 @@
+[(#REM) \r
+               Zone de recherche. Sur les sites en HTML5, utilise type="search"\r
+               sur le input, et type="text" par défaut pour les autres.\r
+               Dans ce dernier cas, on renseigne quand même class="search"\r
+               ("recherche" serait mieux, mais la saisie input utilise automatiquement\r
+               la valeur de #ENV{type}).\r
+][(#INCLURE{fond=saisies/input,\r
+                       env,\r
+            type=#HTML5|?{search,text},\r
+            class=[(#HTML5|?{[(#ENV{class})],[(#ENV{class}) ]search})]})]\r
diff --git a/www/plugins/saisies/saisies/secteur.html b/www/plugins/saisies/saisies/secteur.html
new file mode 100644 (file)
index 0000000..adae54b
--- /dev/null
@@ -0,0 +1,30 @@
+[(#REM) \r
+\r
+  Parametres :\r
+  - class : classe(s) css ajoutes au select\r
+  - multiple : si quelquechose est passe, le select est multiple, sinon, c'est un select simple\r
+       Dans le cas multiple, defaut et valeur doivent etre un array, sinon un int\r
+  - recursif : si oui liste les rubriques de facon recursive, et pas uniquement les secteurs\r
+  - option_intro : chaine de langue de la premiere ligne vide ? (defaut:"")\r
+    (seulement si non multiple)\r
+  - cacher_option_intro : pas de premier option vide  (defaut:"")\r
+  - defaut : valeur par defaut si pas présente dans l'environnement\r
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement\r
+        \r
+  Exemple d'appel :\r
+       [(#SAISIE{secteur,secteur_region,\r
+               label=<:plugin:secteur_region:>,\r
+               multiple=oui})] \r
+]\r
+<select name="#ENV{nom}[(#ENV{multiple}|?{\[\]})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][(#ENV{multiple}|oui) multiple="multiple"][ disabled="(#ENV{disable})"][ (#ENV*{attributs})]>\r
+       [(#ENV{cacher_option_intro}|ou{#ENV{multiple}}|non)\r
+               <option value="">[(#ENV{option_intro})]</option>]\r
+       <BOUCLE_secteurs(RUBRIQUES){racine}{par num titre, titre}{tout}>\r
+               [(#ENV{multiple}|oui)\r
+               <option value="#ID_RUBRIQUE"[(#ID_RUBRIQUE|in_array{#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut,#ARRAY}}}}|oui) selected="selected"]>#TITRE</option>]\r
+               [(#ENV{multiple}|non)\r
+               <option value="#ID_RUBRIQUE"[(#ID_RUBRIQUE|=={#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}}|oui) selected="selected"]>#TITRE</option>]\r
+               [(#ENV{recursif}|oui)\r
+               <INCLURE{fond=prive/listes/rubriques_recursives,valeur=#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}},id_rubrique=#ID_RUBRIQUE,multiple} />]\r
+       </BOUCLE_secteurs>\r
+</select>\r
diff --git a/www/plugins/saisies/saisies/selecteur.html b/www/plugins/saisies/saisies/selecteur.html
new file mode 100644 (file)
index 0000000..01d8e89
--- /dev/null
@@ -0,0 +1,16 @@
+
+[(#REM) Attention ! Nécessite SPIP 3 ! ]
+
+[(#SET{val,#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}})]
+
+[(#INCLURE{fond=formulaires/selecteur/generique,
+       selected=#GET{val},
+       name=#ENV{nom},
+       afficher_langue=#ENV{afficher_langue,''},
+       select=[(#ENV{multiple}|?{'',' '})],
+       whitelist=#ENV{whitelist,#ARRAY},
+       blacklist=#ENV{blacklist,#ARRAY},
+       racine=#ENV{racine},
+       objet=#ENV{objet,racine},
+       id_objet=#ENV{id_objet,0},
+       env})]
diff --git a/www/plugins/saisies/saisies/selecteur_article.html b/www/plugins/saisies/saisies/selecteur_article.html
new file mode 100644 (file)
index 0000000..dc4a183
--- /dev/null
@@ -0,0 +1,41 @@
+[(#REM) \r
+         \r
+  ### /!\ selecteur (spip Bonux) ###\r
+         Attention, ce qui est retourne est un tableau :\r
+         _request($name) = array('article|3', 'article|9', 'rubrique|10');\r
+         Une fonction de SPIP Bonux permet de récupérer un tableau d'identifiants par type d'objet "picker_selected" :\r
+         (picker_selected est dans prive/formulaires/selecteur/generique_fonctions.php de SPIP 3)\r
+         Cette fonction peut etre pratique dans une boucle en utilisant le critere IN\r
+         \r
+  Parametres :\r
+  - multiple : si oui, on peut selectionner plusieurs articles\r
+  - afficher_langue : si oui, on affiche la langue de l'objet selectionne\r
+  - afficher_art_dans_langue_interface : si oui, on n'affiche que les articles de la langue de l'interface\r
+  - limite_branche : branche dans laquelle on limite le selecteur\r
+  - defaut : valeur par defaut si pas présente dans l'environnement\r
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement\r
+        \r
+  Exemple d'appel :\r
+       [(#SAISIE{selecteur_article,id_article,\r
+               label=<:plugin:article_en_une:>})] \r
+]\r
+[(#SET{val,#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}})]\r
+\r
+[(#SET{filtrer_langue_article,[(#ENV{afficher_art_dans_langue_interface,0}|oui) #ENV{lang}]})]\r
+\r
+[(#REM) input necessaire au tag label for \r
+       Mais si on le garde, la saisie enregistree en base, n'est pas correcte\r
+   On pourrait rajouter l'id dans le input cree par Bonux, mais du coup manque la chaine "champ_"\r
+   Comme ca a l'air un peu tordu tout ca, tant pis pour l'erreur HTML\r
+   Yffic\r
+]\r
+[(#REM) Donner a manger a CFG ou CVT-configurer si aucune valeur definie ]\r
+<!--keepme: <input type="hidden" name="[(#ENV{nom,id_item})][]" value="" />-->\r
+[(#INCLURE{fond=formulaires/selecteur/articles,\r
+       selected=#GET{val},\r
+       name=#ENV{nom},\r
+       afficher_langue=#ENV{afficher_langue,''},\r
+       filtrer_langue_article=#GET{filtrer_langue_article,''},\r
+       select=[(#ENV{multiple}|?{0,1})],\r
+       limite_branche=#ENV{limite_branche,''},\r
+       rubriques=0})]\r
diff --git a/www/plugins/saisies/saisies/selecteur_article.yaml b/www/plugins/saisies/saisies/selecteur_article.yaml
new file mode 100644 (file)
index 0000000..029e908
--- /dev/null
@@ -0,0 +1,122 @@
+\r
+titre: '<:saisies:saisie_selecteur_article_titre:>'\r
+description: '<:saisies:saisie_selecteur_article:>'\r
+icone: 'images/saisies_selecteur_article.png'\r
+options:\r
+  -\r
+    saisie: 'fieldset'\r
+    options:\r
+      nom: 'description'\r
+      label: '<:saisies:option_groupe_description:>'\r
+    saisies:\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'label'\r
+          label: '<:saisies:option_label_label:>'\r
+          explication: '<:saisies:option_label_explication:>'\r
+          size: 50\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'explication'\r
+          label: '<:saisies:option_explication_label:>'\r
+          explication: '<:saisies:option_explication_explication:>'\r
+          size: 50\r
+  -\r
+    saisie: 'fieldset'\r
+    options:\r
+      nom: 'utilisation'\r
+      label: '<:saisies:option_groupe_utilisation:>'\r
+    saisies:\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'multiple'\r
+          label: '<:saisies:option_multiple_label:>'\r
+          explication: '<:saisies:option_multiple_explication:>'\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'afficher_langue'\r
+          label: '<:saisies:option_aff_langue_label:>'\r
+          explication: '<:saisies:option_aff_langue_explication:>'\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'afficher_art_dans_langue_interface'\r
+          label: '<:saisies:option_aff_art_interface_label:>'\r
+          explication: '<:saisies:option_aff_art_interface_explication:>'\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'disable'\r
+          label: '<:saisies:option_disable_label:>'\r
+          explication: '<:saisies:option_disable_explication:>'\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'disable_avec_post'\r
+          label: '<:saisies:option_disable_avec_post_label:>'\r
+          explication: '<:saisies:option_disable_avec_post_explication:>'\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'readonly'\r
+          label: '<:saisies:option_readonly_label:>'\r
+          explication: '<:saisies:option_readonly_explication:>'\r
+  -\r
+    saisie: 'fieldset'\r
+    options:\r
+      nom: 'affichage'\r
+      label: '<:saisies:option_groupe_affichage:>'\r
+    saisies:\r
+      -\r
+        saisie: 'textarea'\r
+        options:\r
+          nom: 'afficher_si'\r
+          label: '<:saisies:option_afficher_si_label:>'\r
+          explication: '<:saisies:option_afficher_si_explication:>'\r
+          rows: 5\r
+      -\r
+        saisie: 'textarea'\r
+        options:\r
+          nom: 'afficher_si_remplissage'\r
+          label: '<:saisies:option_afficher_si_remplissage_label:>'\r
+          explication: '<:saisies:option_afficher_si_remplissage_explication:>'\r
+          rows: 5\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'attention'\r
+          label: '<:saisies:option_attention_label:>'\r
+          explication: '<:saisies:option_attention_explication:>'\r
+          size: 50\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'info_obligatoire'\r
+          label: '<:saisies:option_info_obligatoire_label:>'\r
+          explication: '<:saisies:option_info_obligatoire_explication:>'\r
+  -\r
+    saisie: 'fieldset'\r
+    options:\r
+      nom: 'obligatoire'\r
+      label: '<:saisies:option_groupe_validation:>'\r
+    saisies:\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'obligatoire'\r
+          label: '<:saisies:option_obligatoire_label:>'\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'erreur_obligatoire'\r
+          label: '<:saisies:option_erreur_obligatoire_label:>'\r
+          explication: '<:saisies:option_erreur_obligatoire_explication:>'\r
+defaut:\r
+  options:\r
+    label: '<:saisies:saisie_selecteur_article_titre:>'\r
+    # champs extras (definition du champ sql)\r
+    sql: "text DEFAULT '' NOT NULL"\r
diff --git a/www/plugins/saisies/saisies/selecteur_document.html b/www/plugins/saisies/saisies/selecteur_document.html
new file mode 100644 (file)
index 0000000..afe19e5
--- /dev/null
@@ -0,0 +1,84 @@
+[(#REM) 
+
+  Parametres supplementaire :
+  - defaut : valeur par defaut du parametre
+  - class : classe(s) css ajoutes a l'input
+  - size : taille du champ
+  - maxlength : nombre de caracteres maximum
+  - disable : champ insaisissable ? 'oui' (defaut : '')
+  - media : pour restreindre la modalboxe a un type de media particulier
+  - extension : pour restreindre a un type de fichier
+  - defaut : valeur par defaut si pas présente dans l'environnement
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement
+  
+  Note : dans les options, il faut definir 'env' a 'oui' pour que le selecteur puisse recuperer id_article, id_rubrique ou id_breve.
+  Note 2 : si on appelle le selecteur depuis une modalbox, il faut alors lui passer _modalbox_retour qui correspond a l'url de retour
+  apres selection du document. L'id du document sera alors passe par URL.
+]
+
+<input type="text" name="#ENV{nom}" class="text[ (#ENV{class})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ value="(#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}})"][ size="(#ENV{size})"][ maxlength="(#ENV{maxlength})"][ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][(#ENV{obligatoire}|et{#HTML5}|oui) required="required"] />
+
+[(#REM) Si la mediatheque est presente ]
+[(#PLUGIN{medias}|oui)
+
+[(#SET{exec,[(#PLUGIN{medias,version}|<{2}|?{'portfolio','popin-choisir_document'})]})]
+[(#SET{zajax,[(#PLUGIN{medias,version}|<{2}|?{'','var_zajax=contenu&'})]})]
+[(#SET{mod_fn,#ENV{_modalbox_retour}|?{'2','1'}})]
+
+[(#REM) Parcourir les documents de l'article ]
+[(#ENV{id_article}|oui)
+<p class='boutons'><input class='submit' type="button" name="parcourir" value="<:saisies:bouton_parcourir_docs_article:>"
+       onclick="jQuery.modalboxload(
+               '[(#URL_ECRIRE{#GET{exec},#GET{zajax}selectfunc=mediaselect#GET{mod_fn}_#ENV{nom}&id_article=#ENV{id_article}[&media=(#ENV{media})][&extension=(#ENV{extension})]})]',
+               {autoResize: true}
+       );"
+/></p>
+]
+
+[(#REM) Parcourir les documents de la rubrique ]
+[(#ENV{id_rubrique}|oui)
+<p class='boutons'><input class='submit' type="button" name="parcourir" value="<:saisies:bouton_parcourir_docs_rubrique:>"
+       onclick="jQuery.modalboxload(
+               '[(#URL_ECRIRE{#GET{exec},#GET{zajax}selectfunc=mediaselect#GET{mod_fn}_#ENV{nom}&id_rubrique=#ENV{id_rubrique}[&media=(#ENV{media})][&extension=(#ENV{extension})]})]',
+               {autoResize: true}
+       );"
+/></p>
+]
+
+[(#REM) Parcourir les documents de la breve ]
+[(#ENV{id_breve}|oui)
+<p class='boutons'><input class='submit' type="button" name="parcourir" value="<:saisies:bouton_parcourir_docs_breve:>"
+       onclick="jQuery.modalboxload(
+               '[(#URL_ECRIRE{#GET{exec},#GET{zajax}selectfunc=mediaselect#GET{mod_fn}_#ENV{nom}&id_breve=#ENV{id_breve}[&media=(#ENV{media})][&extension=(#ENV{extension})]})]',
+               {autoResize: true}
+       );"
+/></p>
+]
+
+[(#REM) Parcourir toute la mediatheque ]
+<p class='boutons'><input class='submit' type="button" name="parcourir" value="<:saisies:bouton_parcourir_mediatheque:>"
+       onclick="jQuery.modalboxload(
+               '[(#URL_ECRIRE{#GET{exec},#GET{zajax}selectfunc=mediaselect#GET{mod_fn}_#ENV{nom}[&media=(#ENV{media})][&extension=(#ENV{extension})]})]',
+               {autoResize: true}
+       );"
+/></p>
+
+
+[(#ENV{_modalbox_retour}|non)
+<script type="text/javascript">
+       function mediaselect1_#ENV{nom}(id){
+               jQuery.modalboxclose();
+               jQuery("#champ_#ENV{id,#ENV{nom}}").attr('value',id).focus();
+       };
+</script>
+]
+
+[(#ENV{_modalbox_retour}|oui)
+<script type="text/javascript">
+       function mediaselect2_#ENV{nom}(id){
+               jQuery.modalboxload('#ENV**{_modalbox_retour}&#ENV{nom}='+id);
+       };
+</script>
+]
+
+]
diff --git a/www/plugins/saisies/saisies/selecteur_langue.html b/www/plugins/saisies/saisies/selecteur_langue.html
new file mode 100644 (file)
index 0000000..fe57d42
--- /dev/null
@@ -0,0 +1,20 @@
+[(#REM) 
+       
+  Parametres :
+  - class : classe(s) css ajoutes au select
+  - defaut : valeur par defaut si pas présente dans l'environnement
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement
+  
+  Exemple d'appel :
+       [(#SAISIE{selecteur_langue,lang,
+               label=<:plugin:info_langue:>})] 
+]
+[(#SET{valeur,[(#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut,''}}})]})]
+[(#SET{langues,[(#VAL{changer_lang}|liste_options_langues{#GET{valeur}})]})]
+<select name="#ENV{nom}" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][ disabled="(#ENV{disable})"][ (#ENV*{attributs})]>
+<BOUCLE_si_spip2(CONDITION){si (#GET{langues}|is_array|non)}>
+#GET{langues}
+</BOUCLE_si_spip2>
+[(#INCLURE{fond=prive/formulaires/inc-options-langues,name=changer_lang,default=#GET{valeur,#LANG}})]
+<//B_si_spip2>
+</select>
diff --git a/www/plugins/saisies/saisies/selecteur_rubrique.html b/www/plugins/saisies/saisies/selecteur_rubrique.html
new file mode 100644 (file)
index 0000000..1590ab2
--- /dev/null
@@ -0,0 +1,40 @@
+[(#REM) \r
+         \r
+  ### /!\ selecteur (spip Bonux) ###\r
+         Attention, ce qui est retourne est un tableau :\r
+         _request($name) = array('article|3', 'article|9', 'rubrique|10');\r
+         Une fonction de SPIP Bonux permet de récupérer un tableau d'identifiants par type d'objet :\r
+         [(#CHAMP|picker_selected{article})]\r
+         Cette fonction peut etre pratique dans une boucle en utilisant le critere IN\r
+         \r
+  Parametres :\r
+  - multiple : si oui, on peut selectionner plusieurs rubriques\r
+  - afficher_langue : si oui, on affiche la langue de l'objet selectionne\r
+  - afficher_rub_dans_langue_interface : si oui, on n'affiche que les rubriques de la langue de l'interface\r
+  - defaut : valeur par defaut si pas présente dans l'environnement\r
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement\r
+  \r
+  Exemple d'appel :\r
+       [(#SAISIE{selecteur_rubrique,rubriques_menu,\r
+               multiple=oui,\r
+               label=<:plugin:article_en_une:>})] \r
+]\r
+\r
+[(#SET{val,#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}})]\r
+\r
+[(#SET{filtrer_langue_rubrique,[(#ENV{afficher_rub_dans_langue_interface,0}|oui) #ENV{lang}]})]\r
+\r
+[(#REM) input necessaire au tag label for \r
+       Mais si on le garde, la saisie enregistree en base, n'est pas correcte\r
+   On pourrait rajouter l'id dans le input cree par Bonux, mais du coup manque la chaine "champ_"\r
+   Comme ca a l'air un peu tordu tout ca, tant pis pour l'erreur HTML\r
+   Yffic\r
+]\r
+[(#REM) Donner a manger a CFG ou CVT-configurer si aucune valeur definie ]\r
+<!--keepme: <input type="hidden" name="[(#ENV{nom,id_item})][]" id="[champ_(#ENV{nom,id_item})]" value="" />-->\r
+[(#INCLURE{fond=formulaires/selecteur/rubriques,\r
+       selected=#GET{val},\r
+       name=#ENV{nom},\r
+       afficher_langue=#ENV{afficher_langue,''},\r
+       filtrer_langue_rubrique=#GET{filtrer_langue_rubrique,''},\r
+       select=[(#ENV{multiple}|?{0,1})]})]\r
diff --git a/www/plugins/saisies/saisies/selecteur_rubrique.yaml b/www/plugins/saisies/saisies/selecteur_rubrique.yaml
new file mode 100644 (file)
index 0000000..c95f355
--- /dev/null
@@ -0,0 +1,122 @@
+\r
+titre: '<:saisies:saisie_selecteur_rubrique_titre:>'\r
+description: '<:saisies:saisie_selecteur_rubrique:>'\r
+icone: 'images/saisies_selecteur_rubrique.png'\r
+options:\r
+  -\r
+    saisie: 'fieldset'\r
+    options:\r
+      nom: 'description'\r
+      label: '<:saisies:option_groupe_description:>'\r
+    saisies:\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'label'\r
+          label: '<:saisies:option_label_label:>'\r
+          explication: '<:saisies:option_label_explication:>'\r
+          size: 50\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'explication'\r
+          label: '<:saisies:option_explication_label:>'\r
+          explication: '<:saisies:option_explication_explication:>'\r
+          size: 50\r
+  -\r
+    saisie: 'fieldset'\r
+    options:\r
+      nom: 'utilisation'\r
+      label: '<:saisies:option_groupe_utilisation:>'\r
+    saisies:\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'multiple'\r
+          label: '<:saisies:option_multiple_label:>'\r
+          explication: '<:saisies:option_multiple_explication:>'\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'afficher_langue'\r
+          label: '<:saisies:option_aff_langue_label:>'\r
+          explication: '<:saisies:option_aff_langue_explication:>'\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'afficher_rub_dans_langue_interface'\r
+          label: '<:saisies:option_aff_rub_interface_label:>'\r
+          explication: '<:saisies:option_aff_rub_interface_explication:>'\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'disable'\r
+          label: '<:saisies:option_disable_label:>'\r
+          explication: '<:saisies:option_disable_explication:>'\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'disable_avec_post'\r
+          label: '<:saisies:option_disable_avec_post_label:>'\r
+          explication: '<:saisies:option_disable_avec_post_explication:>'\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'readonly'\r
+          label: '<:saisies:option_readonly_label:>'\r
+          explication: '<:saisies:option_readonly_explication:>'\r
+  -\r
+    saisie: 'fieldset'\r
+    options:\r
+      nom: 'affichage'\r
+      label: '<:saisies:option_groupe_affichage:>'\r
+    saisies:\r
+      -\r
+        saisie: 'textarea'\r
+        options:\r
+          nom: 'afficher_si'\r
+          label: '<:saisies:option_afficher_si_label:>'\r
+          explication: '<:saisies:option_afficher_si_explication:>'\r
+          rows: 5\r
+      -\r
+        saisie: 'textarea'\r
+        options:\r
+          nom: 'afficher_si_remplissage'\r
+          label: '<:saisies:option_afficher_si_remplissage_label:>'\r
+          explication: '<:saisies:option_afficher_si_remplissage_explication:>'\r
+          rows: 5\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'attention'\r
+          label: '<:saisies:option_attention_label:>'\r
+          explication: '<:saisies:option_attention_explication:>'\r
+          size: 50\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'info_obligatoire'\r
+          label: '<:saisies:option_info_obligatoire_label:>'\r
+          explication: '<:saisies:option_info_obligatoire_explication:>'\r
+  -\r
+    saisie: 'fieldset'\r
+    options:\r
+      nom: 'obligatoire'\r
+      label: '<:saisies:option_groupe_validation:>'\r
+    saisies:\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'obligatoire'\r
+          label: '<:saisies:option_obligatoire_label:>'\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'erreur_obligatoire'\r
+          label: '<:saisies:option_erreur_obligatoire_label:>'\r
+          explication: '<:saisies:option_erreur_obligatoire_explication:>'\r
+defaut:\r
+  options:\r
+    label: '<:saisies:saisie_selecteur_rubrique_titre:>'\r
+    # champs extras (definition du champ sql)\r
+    sql: "text DEFAULT '' NOT NULL"\r
diff --git a/www/plugins/saisies/saisies/selecteur_rubrique_article.html b/www/plugins/saisies/saisies/selecteur_rubrique_article.html
new file mode 100644 (file)
index 0000000..16607d9
--- /dev/null
@@ -0,0 +1,45 @@
+[(#REM) \r
+         \r
+  ### /!\ selecteur (spip Bonux) ###\r
+         Attention, ce qui est retourne est un tableau :\r
+         _request($name) = array('article|3', 'article|9', 'rubrique|10');\r
+         Une fonction de SPIP Bonux permet de récupérer un tableau d'identifiants par type d'objet :\r
+         [(#CHAMP|picker_selected{article})]\r
+         Cette fonction peut etre pratique dans une boucle en utilisant le critere IN\r
+         \r
+  Parametres :\r
+  - multiple : si oui, on peut selectionner plusieurs rubriques\r
+  - afficher_langue : si oui, on affiche la langue de l'objet selectionne\r
+  - afficher_art_dans_langue_interface : si oui, on n'affiche que les articles de la langue de l'interface\r
+  - afficher_rub_dans_langue_interface : si oui, on n'affiche que les rubriques de la langue de l'interface\r
+  - defaut : valeur par defaut si pas présente dans l'environnement\r
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement\r
+        \r
+  Exemple d'appel :\r
+       [(#SAISIE{selecteur_rubrique_article,menu,\r
+               multiple=1,\r
+               label=<:plugin:choisir_le_menu:>})] \r
+]\r
+[(#SET{val,#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}})]\r
+\r
+[(#SET{filtrer_langue_article,[(#ENV{afficher_art_dans_langue_interface,0}|oui) #ENV{lang}]})]\r
+[(#SET{filtrer_langue_rubrique,[(#ENV{afficher_rub_dans_langue_interface,0}|oui) #ENV{lang}]})]\r
+\r
+[(#REM) input necessaire au tag label for \r
+       Mais si on le garde, la saisie enregistree en base, n'est pas correcte\r
+   On pourrait rajouter l'id dans le input cree par Bonux, mais du coup manque la chaine "champ_"\r
+   Comme ca a l'air un peu tordu tout ca, tant pis pour l'erreur HTML\r
+   Yffic\r
+]\r
+[(#REM) Donner a manger a CFG ou CVT-configurer si aucune valeur definie ]\r
+<!--keepme: <input type="hidden" name="[(#ENV{nom,id_item})][]" id="[champ_(#ENV{nom,id_item})]" value="" /> -->\r
+\r
+[(#INCLURE{fond=formulaires/selecteur/articles,\r
+       selected=#GET{val},\r
+       name=#ENV{nom},\r
+       afficher_langue=#ENV{afficher_langue,''},\r
+       filtrer_langue_article=#GET{filtrer_langue_article,''},\r
+       filtrer_langue_rubrique=#GET{filtrer_langue_rubrique,''},\r
+       select=[(#ENV{multiple}|?{0,1})],\r
+       rubriques=1})]\r
+\r
diff --git a/www/plugins/saisies/saisies/selecteur_rubrique_article.yaml b/www/plugins/saisies/saisies/selecteur_rubrique_article.yaml
new file mode 100644 (file)
index 0000000..5559bd1
--- /dev/null
@@ -0,0 +1,128 @@
+\r
+titre: '<:saisies:saisie_selecteur_rubrique_article_titre:>'\r
+description: '<:saisies:saisie_selecteur_rubrique_article:>'\r
+icone: 'images/saisies_selecteur_rubrique_article.png'\r
+options:\r
+  -\r
+    saisie: 'fieldset'\r
+    options:\r
+      nom: 'description'\r
+      label: '<:saisies:option_groupe_description:>'\r
+    saisies:\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'label'\r
+          label: '<:saisies:option_label_label:>'\r
+          explication: '<:saisies:option_label_explication:>'\r
+          size: 50\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'explication'\r
+          label: '<:saisies:option_explication_label:>'\r
+          explication: '<:saisies:option_explication_explication:>'\r
+          size: 50\r
+  -\r
+    saisie: 'fieldset'\r
+    options:\r
+      nom: 'utilisation'\r
+      label: '<:saisies:option_groupe_utilisation:>'\r
+    saisies:\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'multiple'\r
+          label: '<:saisies:option_multiple_label:>'\r
+          explication: '<:saisies:option_multiple_explication:>'\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'afficher_langue'\r
+          label: '<:saisies:option_aff_langue_label:>'\r
+          explication: '<:saisies:option_aff_langue_explication:>'\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'afficher_art_dans_langue_interface'\r
+          label: '<:saisies:option_aff_art_interface_label:>'\r
+          explication: '<:saisies:option_aff_art_interface_explication:>'\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'afficher_rub_dans_langue_interface'\r
+          label: '<:saisies:option_aff_rub_interface_label:>'\r
+          explication: '<:saisies:option_aff_rub_interface_explication:>'\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'disable'\r
+          label: '<:saisies:option_disable_label:>'\r
+          explication: '<:saisies:option_disable_explication:>'\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'disable_avec_post'\r
+          label: '<:saisies:option_disable_avec_post_label:>'\r
+          explication: '<:saisies:option_disable_avec_post_explication:>'\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'readonly'\r
+          label: '<:saisies:option_readonly_label:>'\r
+          explication: '<:saisies:option_readonly_explication:>'\r
+  -\r
+    saisie: 'fieldset'\r
+    options:\r
+      nom: 'affichage'\r
+      label: '<:saisies:option_groupe_affichage:>'\r
+    saisies:\r
+      -\r
+        saisie: 'textarea'\r
+        options:\r
+          nom: 'afficher_si'\r
+          label: '<:saisies:option_afficher_si_label:>'\r
+          explication: '<:saisies:option_afficher_si_explication:>'\r
+          rows: 5\r
+      -\r
+        saisie: 'textarea'\r
+        options:\r
+          nom: 'afficher_si_remplissage'\r
+          label: '<:saisies:option_afficher_si_remplissage_label:>'\r
+          explication: '<:saisies:option_afficher_si_remplissage_explication:>'\r
+          rows: 5\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'attention'\r
+          label: '<:saisies:option_attention_label:>'\r
+          explication: '<:saisies:option_attention_explication:>'\r
+          size: 50\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'info_obligatoire'\r
+          label: '<:saisies:option_info_obligatoire_label:>'\r
+          explication: '<:saisies:option_info_obligatoire_explication:>'\r
+  -\r
+    saisie: 'fieldset'\r
+    options:\r
+      nom: 'obligatoire'\r
+      label: '<:saisies:option_groupe_validation:>'\r
+    saisies:\r
+      -\r
+        saisie: 'oui_non'\r
+        options:\r
+          nom: 'obligatoire'\r
+          label: '<:saisies:option_obligatoire_label:>'\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'erreur_obligatoire'\r
+          label: '<:saisies:option_erreur_obligatoire_label:>'\r
+          explication: '<:saisies:option_erreur_obligatoire_explication:>'\r
+defaut:\r
+  options:\r
+    label: '<:saisies:saisie_selecteur_rubrique_article_titre:>'\r
+    # champs extras (definition du champ sql)\r
+    sql: "text DEFAULT '' NOT NULL"\r
diff --git a/www/plugins/saisies/saisies/selecteur_site.html b/www/plugins/saisies/saisies/selecteur_site.html
new file mode 100644 (file)
index 0000000..3787e45
--- /dev/null
@@ -0,0 +1,33 @@
+[(#REM)\r
+\r
+Saisies qui liste les sites syndiques\r
+Par défaut ne liste que ceux des rubriques à la racine (secteurs)\r
+\r
+  Parametres :\r
+  - class : classe(s) css ajoutes au select\r
+  - multiple : si quelquechose est passe, le select est multiple, sinon, c'est un select simple\r
+    Dans le cas multiple, defaut et valeur doivent etre un array, sinon un int\r
+  - option_intro : chaine de langue de la premiere ligne vide ? (defaut:"")\r
+    (seulement si non multiple)\r
+  - cacher_option_intro : pas de premier option vide  (defaut:"")\r
+  - syndication : 'oui' ou 'non' (sélectionner uniquement les sites avec ou sans syndication)\r
+  - defaut : valeur par defaut si pas présente dans l'environnement\r
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement\r
+\r
+]\r
+<select name="#ENV{nom}[(#ENV{multiple}|?{\[\]})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][(#ENV{multiple}|oui) multiple="multiple" size="#ENV{size,10}"][ (#ENV*{attributs})]>\r
+       [(#ENV{cacher_option_intro}|ou{#ENV{multiple}}|non)\r
+               <option value="0">[(#ENV{option_intro})]</option>]\r
+       <BOUCLE_secteurs(RUBRIQUES){racine}{par num titre, titre}{tout}>\r
+       <B_sites>\r
+       <optgroup label="#TITRE">\r
+               <BOUCLE_sites(SITES){id_secteur}{par nom_site}{syndication ?}>\r
+               [(#ENV{multiple}|oui)\r
+               <option value="#ID_SYNDIC"[(#ID_SYNDIC|in_array{#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut,#ARRAY}}}}|oui) selected="selected"]>#NOM_SITE</option>]\r
+               [(#ENV{multiple}|non)\r
+               <option value="#ID_SYNDIC"[(#ID_SYNDIC|=={#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}}|oui) selected="selected"]>#NOM_SITE</option>]\r
+               </BOUCLE_sites>\r
+       </optgroup>\r
+       </B_sites>\r
+       </BOUCLE_secteurs>\r
+</select>\r
diff --git a/www/plugins/saisies/saisies/selection.html b/www/plugins/saisies/saisies/selection.html
new file mode 100644 (file)
index 0000000..423515d
--- /dev/null
@@ -0,0 +1,54 @@
+[(#REM) 
+
+  ### /!\ boucle POUR (spip Bonux) ###
+       
+  Parametres :
+  - ** datas : tableau de donnees
+               liste simple : cle=>valeur
+               liste avec groupes :  cle=> tableau (cle=>valeur)
+  - option_intro : chaine de langue de la premiere ligne vide ? (defaut:"")
+  - cacher_option_intro : pas de premier option vide  (defaut:"")
+  - class : classe(s) css ajoutes au select
+  - defaut : valeur par defaut si pas présente dans l'environnement
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement
+  
+  Exemples d'appels
+  pour une liste simple :
+       [(#SAISIE{selection,produits,
+               label=<:plugin:info_produits:>,
+               datas=#ARRAY{
+                       cle1,valeur1,
+                       cle2,valeur2,
+                       cle3,valeur3}})]
+  pour une liste avec groupes :
+       [(#SAISIE{selection,produits,
+               label=<:plugin:info_produits:>,
+               datas=#ARRAY{
+                       cle1,#ARRAY{
+                               cle1,valeur1,
+                               cle2,valeur2},
+                       cle2,#ARRAY{
+                               cle1,valeur1,
+                               cle2,valeur2}}})] 
+]
+
+[(#REM) datas peut être une chaine qu'on sait décomposer ]
+#SET{datas, #ENV*{datas}|saisies_chaine2tableau}
+
+[(#REM) Attention, la valeur ou la valeur forcée peut être une chaine vide. On doit donc tester avec is_null. ]
+#SET{valeur,#ENV{valeur_forcee}|is_null|?{#ENV{valeur}|is_null|?{#ENV{defaut},#ENV{valeur}},#ENV{valeur_forcee}}}
+
+<select name="#ENV{nom}" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][ disabled="(#ENV{disable})"][ size="(#ENV{size})"][ (#ENV*{attributs})]>
+[(#ENV{cacher_option_intro}|non)<option value="">[(#ENV{option_intro})]</option>]
+<BOUCLE_selection(POUR){tableau #GET{datas}}>
+       <B_cond>
+       <optgroup label="#CLE">
+       <BOUCLE_cond(CONDITION){si #VALEUR|is_array}><BOUCLE_optgroup(POUR){tableau #VALEUR}>
+               <option value="#CLE"[(#CLE|=={#GET{valeur}}|oui)selected="selected"]>#VALEUR</option>
+       </BOUCLE_optgroup></BOUCLE_cond>
+       </optgroup>
+       </B_cond>
+       <option value="#CLE"[(#CLE|=={#GET{valeur}}|oui)selected="selected"]>#VALEUR</option>
+       <//B_cond>
+</BOUCLE_selection>
+</select>
diff --git a/www/plugins/saisies/saisies/selection.yaml b/www/plugins/saisies/saisies/selection.yaml
new file mode 100644 (file)
index 0000000..2b3590d
--- /dev/null
@@ -0,0 +1,137 @@
+
+titre: '<:saisies:saisie_selection_titre:>'
+description: '<:saisies:saisie_selection_explication:>'
+icone: 'images/saisies_selection.png'
+options:
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'description'
+      label: '<:saisies:option_groupe_description:>'
+    saisies:
+      -
+        saisie: 'input'
+        options:
+          nom: 'label'
+          label: '<:saisies:option_label_label:>'
+          explication: '<:saisies:option_label_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'explication'
+          label: '<:saisies:option_explication_label:>'
+          explication: '<:saisies:option_explication_explication:>'
+          size: 50
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'datas'
+          label: '<:saisies:option_datas_label:>'
+          explication: '<:saisies:option_datas_sous_groupe_explication:>'
+          rows: 10
+          cols: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'defaut'
+          label: '<:saisies:option_defaut_label:>'
+          size: 50
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'utilisation'
+      label: '<:saisies:option_groupe_utilisation:>'
+    saisies:
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'disable'
+          label: '<:saisies:option_disable_label:>'
+          explication: '<:saisies:option_disable_explication:>'
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'disable_avec_post'
+          label: '<:saisies:option_disable_avec_post_label:>'
+          explication: '<:saisies:option_disable_avec_post_explication:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'affichage'
+      label: '<:saisies:option_groupe_affichage:>'
+    saisies:
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si'
+          label: '<:saisies:option_afficher_si_label:>'
+          explication: '<:saisies:option_afficher_si_explication:>'
+          rows: 5
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si_remplissage'
+          label: '<:saisies:option_afficher_si_remplissage_label:>'
+          explication: '<:saisies:option_afficher_si_remplissage_explication:>'
+          rows: 5
+      -
+        saisie: 'input'
+        options:
+          nom: 'option_intro'
+          label: '<:saisies:option_option_intro_label:>'
+          size: 50
+      -
+        saisie: 'case'
+        options:
+          nom: 'cacher_option_intro'
+          label_case: '<:saisies:option_cacher_option_intro_label:>'
+      -
+        saisie: 'input'
+        options:
+          nom: 'attention'
+          label: '<:saisies:option_attention_label:>'
+          explication: '<:saisies:option_attention_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'size'
+          label: '<:saisies:option_size_label:>'
+          explication: '<:saisies:option_size_explication:>'
+        verifier:
+          type: 'entier'
+          options:
+            min: 1
+      -
+        saisie: 'input'
+        options:
+          nom: 'info_obligatoire'
+          label: '<:saisies:option_info_obligatoire_label:>'
+          explication: '<:saisies:option_info_obligatoire_explication:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'obligatoire'
+      label: '<:saisies:option_groupe_validation:>'
+    saisies:
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'obligatoire'
+          label: '<:saisies:option_obligatoire_label:>'
+      -
+        saisie: 'input'
+        options:
+          nom: 'erreur_obligatoire'
+          label: '<:saisies:option_erreur_obligatoire_label:>'
+          explication: '<:saisies:option_erreur_obligatoire_explication:>'
+defaut:
+  options:
+    label: '<:saisies:saisie_selection_titre:>'
+    datas:
+      choix1: '<:saisies:saisie_radio_defaut_choix1:>'
+      choix2: '<:saisies:saisie_radio_defaut_choix2:>'
+      choix3: '<:saisies:saisie_radio_defaut_choix3:>'
+    # champs extras (definition du champ sql)
+    sql: "text DEFAULT '' NOT NULL"
diff --git a/www/plugins/saisies/saisies/selection_multiple.html b/www/plugins/saisies/saisies/selection_multiple.html
new file mode 100755 (executable)
index 0000000..6cd36b3
--- /dev/null
@@ -0,0 +1,44 @@
+[(#REM) 
+
+  ### /!\ boucle POUR (spip Bonux) ###
+       
+  Parametres :
+  - datas : tableau de donnees cle=>valeur
+  - option_intro : chaine de langue de la premiere ligne vide ? (defaut:"")
+  - cacher_option_intro : pas de premier option vide  (defaut:"")
+  - class : classe(s) css ajoutes au select
+  - defaut : valeur par defaut si pas présente dans l'environnement
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement
+  
+  Exemple d'appel :
+       [(#SAISIE{selection_multiple,produits,
+               label=<:plugin:info_produits:>,
+               datas=#ARRAY{
+                       cle1,valeur1,
+                       cle2,valeur2,
+                       cle3,valeur3}})] 
+]
+
+[(#REM) datas peut être une chaine qu'on sait décomposer ]
+#SET{datas, #ENV{datas}|saisies_chaine2tableau}
+
+[(#REM) defaut peut être une chaine (plusieurs valeurs ou pas) qu'on sait décomposer ]
+#SET{defaut, #ENV{defaut}|saisies_chaine2tableau}
+
+[(#REM) valeur peut être une chaine (plusieurs valeurs ou pas) qu'on sait décomposer ]
+#SET{valeur, #ENV{valeur}|saisies_valeur2tableau}
+
+<select name="#ENV{nom}[]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]" multiple="multiple"[ class="(#ENV{class})"][ disabled="(#ENV{disable})"][ size="(#ENV{size,10})"][ (#ENV*{attributs})]>
+[(#ENV{cacher_option_intro}|non)<option value="">[(#ENV{option_intro})]</option>]
+<BOUCLE_selection(POUR){tableau #GET{datas}}>
+       <B_cond>
+       <optgroup label="#CLE">
+       <BOUCLE_cond(CONDITION){si #VALEUR|is_array}><BOUCLE_optgroup(POUR){tableau #VALEUR}>
+               <option value="#CLE" [(#CLE|in_array{#ENV{valeur_forcee,#GET{valeur,#GET{defaut}}}}|oui) selected="selected"]>#VALEUR</option>
+       </BOUCLE_optgroup></BOUCLE_cond>
+       </optgroup>
+       </B_cond>
+       <option value="#CLE" [(#CLE|in_array{#ENV{valeur_forcee,#GET{valeur,#GET{defaut}}}}|oui) selected="selected"]>#VALEUR</option>
+       <//B_cond>
+</BOUCLE_selection>
+</select>
diff --git a/www/plugins/saisies/saisies/selection_multiple.yaml b/www/plugins/saisies/saisies/selection_multiple.yaml
new file mode 100644 (file)
index 0000000..494caf2
--- /dev/null
@@ -0,0 +1,139 @@
+
+titre: '<:saisies:saisie_selection_multiple_titre:>'
+description: '<:saisies:saisie_selection_multiple_explication:>'
+icone: 'images/saisies_selection_multiple.png'
+options:
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'description'
+      label: '<:saisies:option_groupe_description:>'
+    saisies:
+      -
+        saisie: 'input'
+        options:
+          nom: 'label'
+          label: '<:saisies:option_label_label:>'
+          explication: '<:saisies:option_label_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'explication'
+          label: '<:saisies:option_explication_label:>'
+          explication: '<:saisies:option_explication_explication:>'
+          size: 50
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'datas'
+          label: '<:saisies:option_datas_label:>'
+          explication: '<:saisies:option_datas_sous_groupe_explication:>'
+          rows: 10
+          cols: 50
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'defaut'
+          label: '<:saisies:option_defaut_label:>'
+          rows: 10
+          cols: 50
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'utilisation'
+      label: '<:saisies:option_groupe_utilisation:>'
+    saisies:
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'disable'
+          label: '<:saisies:option_disable_label:>'
+          explication: '<:saisies:option_disable_explication:>'
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'disable_avec_post'
+          label: '<:saisies:option_disable_avec_post_label:>'
+          explication: '<:saisies:option_disable_avec_post_explication:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'affichage'
+      label: '<:saisies:option_groupe_affichage:>'
+    saisies:
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si'
+          label: '<:saisies:option_afficher_si_label:>'
+          explication: '<:saisies:option_afficher_si_explication:>'
+          rows: 5
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si_remplissage'
+          label: '<:saisies:option_afficher_si_remplissage_label:>'
+          explication: '<:saisies:option_afficher_si_remplissage_explication:>'
+          rows: 5
+      -
+        saisie: 'input'
+        options:
+          nom: 'option_intro'
+          label: '<:saisies:option_option_intro_label:>'
+          size: 50
+      -
+        saisie: 'case'
+        options:
+          nom: 'cacher_option_intro'
+          label_case: '<:saisies:option_cacher_option_intro_label:>'
+          defaut: 'on'
+      -
+        saisie: 'input'
+        options:
+          nom: 'attention'
+          label: '<:saisies:option_attention_label:>'
+          explication: '<:saisies:option_attention_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'size'
+          label: '<:saisies:option_size_label:>'
+          explication: '<:saisies:option_size_explication:>'
+        verifier:
+          type: 'entier'
+          options:
+            min: 1
+      -
+        saisie: 'input'
+        options:
+          nom: 'info_obligatoire'
+          label: '<:saisies:option_info_obligatoire_label:>'
+          explication: '<:saisies:option_info_obligatoire_explication:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'obligatoire'
+      label: '<:saisies:option_groupe_validation:>'
+    saisies:
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'obligatoire'
+          label: '<:saisies:option_obligatoire_label:>'
+      -
+        saisie: 'input'
+        options:
+          nom: 'erreur_obligatoire'
+          label: '<:saisies:option_erreur_obligatoire_label:>'
+          explication: '<:saisies:option_erreur_obligatoire_explication:>'
+defaut:
+  options:
+    label: '<:saisies:saisie_selection_multiple_titre:>'
+    datas:
+      choix1: '<:saisies:saisie_radio_defaut_choix1:>'
+      choix2: '<:saisies:saisie_radio_defaut_choix2:>'
+      choix3: '<:saisies:saisie_radio_defaut_choix3:>'
+    # champs extras (definition du champ sql)
+    sql: "text DEFAULT '' NOT NULL"
diff --git a/www/plugins/saisies/saisies/selection_par_groupe.html b/www/plugins/saisies/saisies/selection_par_groupe.html
new file mode 100644 (file)
index 0000000..750612d
--- /dev/null
@@ -0,0 +1,35 @@
+[(#REM) 
+
+  ### /!\ boucle POUR (spip Bonux) ###
+       
+  Parametres :
+  - ** datas : tableau de donnees groupe=>array(cle=>valeur)
+  - option_intro : chaine de langue de la premiere ligne vide ? (defaut:"")
+  - cacher_option_intro : pas de premier option vide  (defaut:"")
+  - class : classe(s) css ajoutes au select
+  - defaut : valeur par defaut si pas présente dans l'environnement
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement
+  
+  Exemple d'appel :
+       [(#SAISIE{selection_par_groupe,produits,
+               label=<:plugin:info_produits:>,
+               datas=#ARRAY{
+                       groupe 1,#ARRAY{
+                               cle1,valeur1,
+                               cle2,valeur2,
+                               cle3,valeur3},
+                       groupe 2,#ARRAY{
+                               cle4,valeur1,
+                               cle5,valeur2,
+                               cle6,valeur3}}})] 
+]
+<select name="#ENV{nom}" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][ disabled="(#ENV{disable})"][ (#ENV*{attributs})]>
+[(#ENV{cacher_option_intro}|non)<option value="">[(#ENV{option_intro})]</option>]
+<BOUCLE_groupes(POUR){tableau #ENV{datas}}>
+       <optgroup label="[(#CLE|attribut_html)]">
+               <BOUCLE_selection(POUR){tableau #VALEUR}>
+                       <option value="#CLE" [(#CLE|=={#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}}|oui) selected="selected"]>#VALEUR</option>
+               </BOUCLE_selection>
+       </optgroup>
+</BOUCLE_groupes>
+</select>
diff --git a/www/plugins/saisies/saisies/statuts_auteurs.html b/www/plugins/saisies/saisies/statuts_auteurs.html
new file mode 100644 (file)
index 0000000..5534881
--- /dev/null
@@ -0,0 +1,43 @@
+[(#REM)
+
+  Parametres :
+  - class : classe(s) css ajoutes au select
+  - multiple : si quelquechose est passe, le select est multiple, sinon, c'est un select simple
+       Dans le cas multiple, defaut et valeur doivent être un array, sinon un int
+  - option_intro : chaine de langue de la premiere ligne vide ? (defaut:"")
+    (seulement si non multiple)
+  - cacher_option_intro : pas de premier option vide  (defaut:"")
+  - afficher_tous : permet d'afficher une option de valeur "tous" correspondant à tous les utilisateurs
+  - poubelle : permet d'afficher le statut poubelle
+  - defaut : valeur par defaut si pas présente dans l'environnement
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement
+  - size : dans le cas d'un select multiple, limite le nombre de lignes au chiffre passé
+  
+  Exemple d'appel :
+       [(#SAISIE{statuts_auteurs,statuts,
+               label=<:plugin:statuts_des_auteurs:>,
+               multiple=oui})]
+]
+
+<select name="#ENV{nom}[(#ENV{multiple}|?{\[\]})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][(#ENV{multiple}|oui) multiple="multiple" size="[(#ENV{size,#ENV{afficher_tous}|?{4,3}})]"][ disabled="(#ENV{disable})"][ (#ENV*{attributs})]>
+       [(#ENV{cacher_option_intro}|ou{#ENV{multiple}}|non)
+               <option value="">[(#ENV{option_intro})]</option>]
+       [(#ENV{multiple}|oui)
+               <option value="0minirezo"[(#VAL{0minirezo}|in_array{#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut,#ARRAY}}}}|oui) selected="selected"]><:info_administrateurs:></option>
+               <option value="1comite"[(#VAL{1comite}|in_array{#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut,#ARRAY}}}}|oui) selected="selected"]><:info_redacteurs:></option>
+               <option value="6forum"[(#VAL{6forum}|in_array{#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut,#ARRAY}}}}|oui) selected="selected"]><:info_visiteurs:></option>
+               [(#ENV{poubelle}|oui)
+               <option value="5poubelle"[(#VAL{5poubelle}|in_array{#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut,#ARRAY}}}}|oui) selected="selected"]><:texte_statut_poubelle:></option>]
+               [(#ENV{afficher_tous}|oui)
+               <option value="tous"[(#VAL{tous}|in_array{#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut,#ARRAY}}}}|oui) selected="selected"]><:saisies:tous_visiteurs:></option>]  ]
+       [(#ENV{multiple}|non)
+               <option value="0minirezo"[(#VAL{0minirezo}|=={#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}}|oui) selected="selected"]><:info_administrateurs:></option>
+               <option value="1comite"[(#VAL{1comite}|=={#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}}|oui) selected="selected"]><:info_redacteurs:></option>
+               <option value="6forum"[(#VAL{6forum}|=={#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}}|oui) selected="selected"]><:info_visiteurs:></option>
+               [(#ENV{poubelle}|oui)
+                       <option value="5poubelle"[(#VAL{5poubelle}|=={#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}}|oui) selected="selected"]><:texte_statut_poubelle:></option>]
+               [(#ENV{afficher_tous}|oui)
+               <option value="tous"[(#VAL{tous}|in_array{#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut,#ARRAY}}}}|oui) selected="selected"]><:saisies:tous_visiteurs:></option>]
+       ]
+</select>
+
diff --git a/www/plugins/saisies/saisies/telephone.html b/www/plugins/saisies/saisies/telephone.html
new file mode 100644 (file)
index 0000000..2c52797
--- /dev/null
@@ -0,0 +1,12 @@
+[(#REM) \r
+\r
+               Zone de saisie de numero de telephone. Sur les sites en HTML5,\r
+               utilise type="tel" sur le input (et donc class="tel"); et\r
+               type="text" par défaut pour les autres (et donc class="text").\r
+               Dans ce dernier cas, on renseigne quand même class="tel" pour\r
+               avoir une classe cohérente avec ou sans HTML5.\r
+\r
+][(#INCLURE{fond=saisies/input,\r
+                       env,\r
+            type=#HTML5|?{tel,text},\r
+            class=[(#HTML5|?{[(#ENV{class})],[(#ENV{class}) ]tel})]})]\r
diff --git a/www/plugins/saisies/saisies/textarea.html b/www/plugins/saisies/saisies/textarea.html
new file mode 100644 (file)
index 0000000..6353221
--- /dev/null
@@ -0,0 +1,22 @@
+[(#REM)
+
+  Parametres :
+  - class : classe(s) css ajoutes au textarea
+  - rows : nombre de ligne, par defaut : 20 
+  - cols : nombre de caracteres de large, par defaut : 40 (cela depend aussi et surtout du CSS)
+  - inserer_barre : barre d'outils du porte plume à insérer (forum ou edition par défaut)
+  - previsualisation : si égale à 'oui', ajoute l'onglet de prévisualisation
+  - defaut : valeur par defaut si pas présente dans l'environnement
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement
+
+  Exemple d'appel :
+       [(#SAISIE{textarea, couleur_foncee,
+               label=<:spa:couleur_foncee:>,
+               obligatoire=non})]
+]
+
+[(#REM) Si la valeur est un tableau, le plugin sait le transformer en chaine, plutôt que d'afficher "Array" ]
+#SET{valeur, #ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}}
+#SET{valeur, #GET{valeur}|is_array|?{(#GET{valeur}|saisies_tableau2chaine), #GET{valeur}}}
+<textarea name="#ENV{nom}" class="[(#ENV{class}) ][inserer_barre_(#ENV{inserer_barre}) ][(#ENV{previsualisation}|oui)inserer_previsualisation]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]" rows="[(#ENV{rows,20})]" cols="[(#ENV{cols,40})]"[ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][(#ENV{obligatoire}|et{#ENV{obligatoire}!={non}}|et{#HTML5}|oui) required="required"][ (#ENV*{attributs})]>
+#GET{valeur}</textarea>
diff --git a/www/plugins/saisies/saisies/textarea.yaml b/www/plugins/saisies/saisies/textarea.yaml
new file mode 100644 (file)
index 0000000..35ab465
--- /dev/null
@@ -0,0 +1,154 @@
+
+titre: '<:saisies:saisie_textarea_titre:>'
+description: '<:saisies:saisie_textarea_explication:>'
+icone: 'images/saisies_textarea.png'
+options:
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'description'
+      label: '<:saisies:option_groupe_description:>'
+    saisies:
+      -
+        saisie: 'input'
+        options:
+          nom: 'label'
+          label: '<:saisies:option_label_label:>'
+          explication: '<:saisies:option_label_explication:>'
+          size: 50
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'defaut'
+          label: '<:saisies:option_defaut_label:>'
+          rows: 4
+      -
+        saisie: 'input'
+        options:
+          nom: 'explication'
+          label: '<:saisies:option_explication_label:>'
+          explication: '<:saisies:option_explication_explication:>'
+          size: 50
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'utilisation'
+      label: '<:saisies:option_groupe_utilisation:>'
+    saisies:
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'disable'
+          label: '<:saisies:option_disable_label:>'
+          explication: '<:saisies:option_disable_explication:>'
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'disable_avec_post'
+          label: '<:saisies:option_disable_avec_post_label:>'
+          explication: '<:saisies:option_disable_avec_post_explication:>'
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'readonly'
+          label: '<:saisies:option_readonly_label:>'
+          explication: '<:saisies:option_readonly_explication:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'affichage'
+      label: '<:saisies:option_groupe_affichage:>'
+    saisies:
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si'
+          label: '<:saisies:option_afficher_si_label:>'
+          explication: '<:saisies:option_afficher_si_explication:>'
+          rows: 5
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'afficher_si_remplissage'
+          label: '<:saisies:option_afficher_si_remplissage_label:>'
+          explication: '<:saisies:option_afficher_si_remplissage_explication:>'
+          rows: 5
+      -
+        saisie: 'input'
+        options:
+          nom: 'attention'
+          label: '<:saisies:option_attention_label:>'
+          explication: '<:saisies:option_attention_explication:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'info_obligatoire'
+          label: '<:saisies:option_info_obligatoire_label:>'
+          explication: '<:saisies:option_info_obligatoire_explication:>'
+      -
+        saisie: 'input'
+        options:
+          nom: 'class'
+          label: '<:saisies:option_class_label:>'
+          size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'rows'
+          label: '<:saisies:option_rows_label:>'
+          explication: '<:saisies:option_rows_explication:>'
+        verifier:
+          type: 'entier'
+          options:
+            min: 1
+      -
+        saisie: 'input'
+        options:
+          nom: 'cols'
+          label: '<:saisies:option_cols_label:>'
+          explication: '<:saisies:option_cols_explication:>'
+        verifier:
+          type: 'entier'
+          options:
+            min: 1
+      -
+        saisie: 'selection'
+        options:
+          nom: 'inserer_barre'
+          label: '<:saisies:option_inserer_barre_label:>'
+          explication: '<:saisies:option_inserer_barre_explication:>'
+          option_intro: <:item_non:>
+          datas:
+            edition: '<:saisies:option_inserer_barre_choix_edition:>'
+            forum: '<:saisies:option_inserer_barre_choix_forum:>'
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'previsualisation'
+          label: '<:saisies:option_previsualisation_label:>'
+          explication: '<:saisies:option_previsualisation_explication:>'
+  -
+    saisie: 'fieldset'
+    options:
+      nom: 'validation'
+      label: '<:saisies:option_groupe_validation:>'
+    saisies:
+      -
+        saisie: 'oui_non'
+        options:
+          nom: 'obligatoire'
+          label: '<:saisies:option_obligatoire_label:>'
+      -
+        saisie: 'input'
+        options:
+          nom: 'erreur_obligatoire'
+          label: '<:saisies:option_erreur_obligatoire_label:>'
+          explication: '<:saisies:option_erreur_obligatoire_explication:>'
+defaut:
+  options:
+    label: '<:saisies:saisie_textarea_titre:>'
+    cols: 40
+    rows: 5
+    # champs extras (definition du champ sql)
+    sql: "text DEFAULT '' NOT NULL"
diff --git a/www/plugins/saisies/saisies/true_false.html b/www/plugins/saisies/saisies/true_false.html
new file mode 100644 (file)
index 0000000..365eb74
--- /dev/null
@@ -0,0 +1,23 @@
+[(#REM) 
+
+  Action :
+  - Rempli "true" si oui, "false" si non.
+  
+  Parametres :
+  - defaut : valeur par defaut si pas présente dans l'environnement
+  - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement
+  
+  Exemple d'appel :
+       [(#SAISIE{true_false, afficher_liste,
+               label=<:plugin:afficher_liste:>,
+               explication=<:plugin:explication_afficher_liste:>})]
+]
+#SET{valeur,#ENV{valeur_forcee,#ENV{valeur}}|is_null|?{#ENV{defaut},#ENV{valeur_forcee,#ENV{valeur}}}}
+<div class="choix">
+       <input type="radio" name="#ENV{nom}" class="radio" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_true"[ (#GET{valeur}|=={true}|oui)checked="checked"] value="true" [ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"] />
+       <label for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_true"[(#GET{valeur}|=={true}|oui)class="on"]><:item_oui:></label>
+</div>
+<div class="choix">
+       <input type="radio" name="#ENV{nom}" class="radio" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_false"[ (#GET{valeur}|=={false}|oui)checked="checked"] value="false" [ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"] />
+       <label for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_false"[(#GET{valeur}|=={false}|oui)class="on"]><:item_non:></label>
+</div>
diff --git a/www/plugins/saisies/saisies/url.html b/www/plugins/saisies/saisies/url.html
new file mode 100644 (file)
index 0000000..6ec595a
--- /dev/null
@@ -0,0 +1,12 @@
+[(#REM) \r
+               \r
+               Zone de saisie d'URL. Sur les sites en HTML5, utilise type="url"\r
+               sur le input (et donc class="url"), et type="text" par défaut\r
+               pour les autres (et donc class="text"). Dans ce dernier cas, on\r
+               renseigne quand même class="url" pour avoir une classe cohérente\r
+               avec ou sans HTML5.\r
+               \r
+][(#INCLURE{fond=saisies/input,\r
+                       env,\r
+            type=#HTML5|?{url,text},\r
+            class=[(#HTML5|?{[(#ENV{class}) ],[(#ENV{class}) ]url})]})]\r
diff --git a/www/plugins/saisies/saisies_fonctions.php b/www/plugins/saisies/saisies_fonctions.php
new file mode 100644 (file)
index 0000000..60df1a3
--- /dev/null
@@ -0,0 +1,134 @@
+<?php
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+include_spip('inc/saisies');
+include_spip('balise/saisie');
+// picker_selected (spip 3)
+include_spip('formulaires/selecteur/generique_fonctions');
+
+/**
+ * Traiter la valeur de la vue en fonction du env
+ * si un traitement a ete fait en amont (champs extra) ne rien faire
+ * si pas de traitement defini (formidable) passer typo ou propre selon le type du champ
+ *
+ * @param string $valeur
+ * @param string|array $env
+ * @return string
+ */
+function saisie_traitement_vue($valeur,$env){
+       if (is_string($env))
+               $env = unserialize($env);
+       if (!function_exists('propre'))
+               include_spip('inc/texte');
+
+       $valeur = trim($valeur);
+
+       // si traitement est renseigne, alors le champ est deja mis en forme
+       // (saisies)
+       // sinon on fait une mise en forme smart
+       if ($valeur and !isset($env['traitements'])) {
+               if (in_array($env['type_saisie'], array('textarea'))) {
+                       $valeur = propre($valeur);
+               }
+               else {
+                       $valeur = "<p>" . typo($valeur) . "</p>";
+               }
+       }
+
+       return $valeur;
+}
+
+/**
+ * Passer un nom en une valeur compatible avec une classe css
+ * toto => toto,
+ * toto/truc => toto_truc,
+ * toto[truc] => toto_truc,
+**/
+function saisie_nom2classe($nom) {
+       return str_replace(array('/', '[', ']', '&#91;', '&#93;'), array('_', '_', '', '_', ''), $nom);
+}
+
+/**
+ * Passer un nom en une valeur compatible avec un name de formulaire
+ * toto => toto,
+ * toto/truc => toto[truc],
+ * toto[truc] => toto[truc],
+**/
+function saisie_nom2name($nom) {
+       if (false === strpos($nom, '/')) {
+               return $nom;
+       }
+       $nom = explode('/', $nom);
+       $premier = array_shift($nom);
+       $nom = implode('][', $nom);
+       return $premier . '[' . $nom . ']';
+}
+
+/**
+ * Balise beurk #GLOBALS{debut_intertitre}
+ * qui retourne la globale PHP du même nom si elle existe
+ *
+ * @param array $p
+ *             Pile au niveau de la balise
+ * @return array
+ *             Pile complétée du code php de la balise.
+**/
+function balise_GLOBALS_dist($p) {
+       if (function_exists('balise_ENV'))
+               return balise_ENV($p, '$GLOBALS');
+       else
+               return balise_ENV_dist($p, '$GLOBALS');
+}
+
+/**
+ * Liste les éléments du sélecteur générique triés
+ *
+ * Les éléments sont triés par objets puis par identifiants
+ * 
+ * @example
+ *     L'entrée :
+ *     'rubrique|3,rubrique|5,article|2'
+ *     Retourne :
+ *     array(
+ *        0 => array('objet'=>'article', 'id_objet' => 2),
+ *        1 => array('objet'=>'rubrique', 'id_objet' => 3),
+ *        2 => array('objet'=>'rubrique', 'id_objet' => 5),
+ *     )
+ *
+ * @param string $selected
+ *     Liste des objets sélectionnés
+ * @return array
+ *     Liste des objets triés
+**/
+function picker_selected_par_objet($selected) {
+       $res = array();
+       $liste = picker_selected($selected);
+       // $liste : la sortie dans le désordre
+       if (!$liste) {
+               return $res;
+       }
+
+       foreach ($liste as $l) {
+               if (!isset($res[ $l['objet'] ])) {
+                       $res[ $l['objet'] ] = array();
+               }
+               $res[$l['objet']][] = $l['id_objet'];
+       }
+       // $res est trié par objet, puis par identifiant
+       ksort($res);
+       foreach ($res as $objet => $ids) {
+               sort($res[$objet]);
+       }
+
+       // on remet tout en file
+       $liste = array();
+       foreach ($res as $objet=>$ids) {
+               foreach ($ids as $id) {
+                       $liste[] = array('objet' => $objet, 'id_objet' => $id);
+               }
+       }
+
+       return $liste;
+}
+?>
diff --git a/www/plugins/saisies/saisies_options.php b/www/plugins/saisies/saisies_options.php
new file mode 100644 (file)
index 0000000..fa2590f
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+// Sécurité
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+/*
+ * une fonction qui regarde si $texte est une chaine de langue
+ * de la forme <:qqch:>
+ * si oui applique _T()
+ * si non applique typo() suivant le mode choisi
+ *
+ * @param mixed $valeur
+ *     Une valeur à tester. Si c'est un tableau, la fonction s'appliquera récursivement dessus.
+ * @param string $mode_typo
+ *     Le mode d'application de la fonction typo(), avec trois valeurs possibles "toujours", "jamais" ou "multi".
+ * @return mixed
+ *     Retourne la valeur éventuellement modifiée.
+ */
+if (!function_exists('_T_ou_typo')){
+       function _T_ou_typo($valeur, $mode_typo='toujours') {
+               // Si la valeur est bien une chaine (et pas non plus un entier déguisé)
+               if (is_string($valeur) and !intval($valeur)){
+                       // Si la chaine est du type <:truc:> on passe à _T()
+                       if (preg_match('/^\<:(.*?):\>$/', $valeur, $match)) 
+                               $valeur = _T($match[1]);
+                       // Sinon on la passe a typo()
+                       else {
+                               if (!in_array($mode_typo, array('toujours', 'multi', 'jamais')))
+                                       $mode_typo = 'toujours';
+                       
+                               if ($mode_typo == 'toujours' or ($mode_typo == 'multi' and strpos($valeur, '<multi>') !== false)){
+                                       include_spip('inc/texte');
+                                       $valeur = typo($valeur);
+                               }
+                       }
+               }
+               // Si c'est un tableau, on reapplique la fonction récursivement
+               elseif (is_array($valeur)){
+                       foreach ($valeur as $cle => $valeur2){
+                               $valeur[$cle] = _T_ou_typo($valeur2, $mode_typo);
+                       }
+               }
+
+               return $valeur;
+       }
+}
+
+?>
diff --git a/www/plugins/saisies/saisies_pipelines.php b/www/plugins/saisies/saisies_pipelines.php
new file mode 100644 (file)
index 0000000..61c364a
--- /dev/null
@@ -0,0 +1,107 @@
+<?php\r
+\r
+// Sécurité\r
+if (!defined("_ECRIRE_INC_VERSION")) return;\r
+\r
+function saisies_header_prive($flux){\r
+       $js = find_in_path('javascript/saisies.js');\r
+       $flux .= "\n<script type='text/javascript' src='$js'></script>\n";\r
+       $css = generer_url_public('saisies.css');\r
+       $flux .= "\n<link rel='stylesheet' href='$css' type='text/css' media='all' />\n";\r
+       $css_constructeur = find_in_path('css/formulaires_constructeur.css');\r
+       $flux .= "\n<link rel='stylesheet' href='$css_constructeur' type='text/css' />\n";\r
+       return $flux;\r
+}\r
+\r
+function saisies_affichage_final($flux){\r
+       if (($p = strpos($flux,"<!--!inserer_saisie_editer-->"))!==false){\r
+               // On insère la CSS devant le premier <link> trouvé\r
+               if (!$pi = strpos($flux, "<link") AND !$pi=strpos($flux, '</head'))\r
+                       $pi = $p; // si pas de <link inserer comme un goret entre 2 <li> de saisies\r
+               $css = generer_url_public('saisies.css');\r
+               $ins_css = "\n<link rel='stylesheet' href='$css' type='text/css' media='all' />\n";\r
+\r
+               if (strpos($flux,"saisie_date")!==false){//si on a une saisie de type date, on va charger les css de jquery_ui\r
+                   include_spip("jqueryui_pipelines");\r
+                       if (function_exists("jqueryui_dependances")){\r
+                               $ui_plugins = jqueryui_dependances(array("jquery.ui.datepicker"));\r
+\r
+                               array_push($ui_plugins,"jquery.ui.theme");\r
+                               foreach ($ui_plugins as $ui_plug){\r
+                                       $ui_plug_css = find_in_path("css/$ui_plug.css");\r
+                                       if (strpos($flux,"css/$ui_plug.css")===false){// si pas déjà chargé\r
+                                       $ins_css .= "\n<link rel='stylesheet' href='$ui_plug_css' type='text/css' media='all' />\n";\r
+                                       }\r
+                               }\r
+                       }\r
+               }\r
+\r
+               $flux = substr_replace($flux, $ins_css, $pi, 0);\r
+               // On insère le JS à la fin du <head>\r
+               $pos_head = strpos($flux, '</head');\r
+               $js = find_in_path('javascript/saisies.js');\r
+               $ins_js = "\n<script type='text/javascript' src='$js'></script>\n";\r
+               $flux = substr_replace($flux, $ins_js, $pos_head, 0);\r
+       }\r
+       return $flux;\r
+}\r
+\r
+// Déclaration des pipelines\r
+function saisies_saisies_autonomes($flux) { return $flux; }\r
+function saisies_formulaire_saisies($flux) { return $flux; }\r
+\r
+// Déclarer automatiquement les champs d'un CVT si on les trouve dans un tableau de saisies et s'ils ne sont pas déjà déclarés\r
+function saisies_formulaire_charger($flux){\r
+       // Si le flux data est inexistant, on quitte : Le CVT d'origine a décidé de ne pas continuer\r
+       if (!is_array($flux['data'])){\r
+               return $flux;\r
+       }\r
+\r
+       // Il faut que la fonction existe et qu'elle retourne bien un tableau\r
+       if (include_spip('inc/saisies')\r
+               and $saisies = saisies_chercher_formulaire($flux['args']['form'], $flux['args']['args'])\r
+       ){\r
+               // On ajoute au contexte les champs à déclarer\r
+               $contexte = saisies_lister_valeurs_defaut($saisies);\r
+               $flux['data'] = array_merge($contexte, $flux['data']);\r
+\r
+               // On ajoute le tableau complet des saisies\r
+               $flux['data']['_saisies'] = $saisies;\r
+       }\r
+       return $flux;\r
+}\r
+\r
+// Aiguiller CVT vers un squelette propre à Saisies lorsqu'on a déclaré des saisies et qu'il n'y a pas déjà un HTML\r
+function saisies_styliser($flux){\r
+       // Si on cherche un squelette de formulaire\r
+       if (strncmp($flux['args']['fond'],'formulaires/',12)==0\r
+               // Et qu'il y a des saisies dans le contexte\r
+               and isset($flux['args']['contexte']['_saisies'])\r
+               // Et que le fichier choisi est vide ou n'existe pas\r
+               and include_spip('inc/flock')\r
+               and $ext = $flux['args']['ext']\r
+               and lire_fichier($flux['data'].'.'.$ext, $contenu_squelette)\r
+               and !trim($contenu_squelette)\r
+       ){\r
+               $flux['data'] = preg_replace("/\.$ext$/", '', find_in_path("formulaires/inc-saisies-cvt.$ext"));\r
+       }\r
+\r
+       return $flux;\r
+}\r
+\r
+// Ajouter les vérifications déclarées dans la fonction "saisies" du CVT\r
+function saisies_formulaire_verifier($flux){\r
+       // Il faut que la fonction existe et qu'elle retourne bien un tableau\r
+       if (include_spip('inc/saisies') and $saisies = saisies_chercher_formulaire($flux['args']['form'], $flux['args']['args'])){\r
+               // On ajoute au contexte les champs à déclarer\r
+               $erreurs = saisies_verifier($saisies);\r
+               if ($erreurs and !isset($erreurs['message_erreur']))\r
+                       $erreurs['message_erreur'] = _T('saisies:erreur_generique');\r
+               $flux['data'] = array_merge($erreurs, $flux['data']);\r
+       }\r
+\r
+\r
+       return $flux;\r
+}\r
+\r
+?>\r
diff --git a/www/plugins/saisies/svn.revision b/www/plugins/saisies/svn.revision
new file mode 100644 (file)
index 0000000..23b8933
--- /dev/null
@@ -0,0 +1,10 @@
+<svn_revision>
+<text_version>
+Origine: file:///home/svn/repository/spip-zone/_plugins_/saisies/trunk
+Revision: 86956
+Dernier commit: 2015-01-02 15:00:02 +0100 
+</text_version>
+<origine>file:///home/svn/repository/spip-zone/_plugins_/saisies/trunk</origine>
+<revision>86956</revision>
+<commit>2015-01-02 15:00:02 +0100 </commit>
+</svn_revision>
\ No newline at end of file
diff --git a/www/plugins/saisies/test/configurer_saisie.html b/www/plugins/saisies/test/configurer_saisie.html
new file mode 100644 (file)
index 0000000..aa3cb37
--- /dev/null
@@ -0,0 +1,51 @@
+<html>
+<head>
+       <title>Test de configuration de saisie</title>
+       [<link rel="stylesheet" href="(#CHEMIN{spip_formulaires.css})" type="text/css" media="all" title="formulaires" charset="utf-8" />]
+       <style type="text/css">
+               .formulaire_spip{
+                       width:40%;
+                       border:1px solid black;
+                       -moz-border-radius:10px;
+                       -webkit-border-radius:10px;
+                       border-radius:10px;
+               }
+               .formulaire_spip ul li{
+                       padding:0.5em;
+               }
+               .formulaire_spip li.fieldset{
+                       padding:0;
+               }
+               .formulaire_spip li.obligatoire{
+                       background:#ffcfcf;
+               }
+               .formulaire_spip .fieldset .legend{
+                       margin:0;
+                       font-style:italic;
+               }
+       </style>
+</head>
+<body>
+       <h1>Test pour générer le formulaire de configuration d'une saisie</h1>
+       
+       <h2>Par defaut, sans configuration du nom du champ</h2>
+       <form class="formulaire_spip" action="#SELF" method="post">
+               <ul>
+                       #CONFIGURER_SAISIE{#ENV{saisie, input}}
+                       <li class="boutons">
+                               <input type="submit" class="submit" />
+                       </li>
+               </ul>
+       </form>
+       
+       <h2>En forçant la configuration du nom</h2>
+       <form class="formulaire_spip" action="#SELF" method="post">
+               <ul>
+                       #CONFIGURER_SAISIE{#ENV{saisie, input}, avec_nom=oui}
+                       <li class="boutons">
+                               <input type="submit" class="submit" />
+                       </li>
+               </ul>
+       </form>
+</body>
+</html>
diff --git a/www/plugins/saisies/test/generer_saisies.html b/www/plugins/saisies/test/generer_saisies.html
new file mode 100644 (file)
index 0000000..4398eb6
--- /dev/null
@@ -0,0 +1,152 @@
+<html>
+<head>
+       <title>Test de génération de saisies</title>
+       [<link rel="stylesheet" href="(#CHEMIN{spip_formulaires.css})" type="text/css" media="all" title="formulaires" charset="utf-8" />]
+       <style type="text/css">
+               .formulaire_spip{
+                       width:40%;
+                       border:1px solid black;
+                       -moz-border-radius:10px;
+                       -webkit-border-radius:10px;
+                       border-radius:10px;
+               }
+               .formulaire_spip ul li{
+                       padding:0.5em;
+               }
+               .formulaire_spip li.fieldset{
+                       padding:0;
+               }
+               .formulaire_spip li.obligatoire{
+                       background:#ffcfcf;
+               }
+               .formulaire_spip .fieldset .legend{
+                       margin:0;
+                       font-style:italic;
+               }
+       </style>
+</head>
+<body>
+       <h1>Test pour générer des saisies à partir d'une description</h1>
+
+       <h2>Génération d'une seule saisie</h2>
+       #SET{champ,
+               #ARRAY{
+                       saisie, input,
+                       options, #ARRAY{
+                               nom, test,
+                               label, Une sorte de titre,
+                               explication, Un sorte d'explication,
+                               obligatoire, oui
+                       }
+               }
+       }
+       <form class="formulaire_spip" action="#SELF" method="post">
+               <ul>
+                       [(#GET{champ}|saisies_generer_html{#ENV**|unserialize})]
+                       <li class="boutons">
+                               <input type="submit" class="submit" />
+                       </li>
+               </ul>
+       </form>
+
+       <h2>Génération complète du contenu (l'intérieur) d'un formulaire</h2>
+       #SET{saisies,
+               #ARRAY{
+                       0,#ARRAY{
+                               saisie, destinataires,
+                               options, #ARRAY{
+                                       nom, destinataires,
+                                       label, Destinataires,
+                                       choix_destinataires, #ARRAY{0,1,1,2},
+                                       type_choix, plusieurs,
+                                       obligatoire, oui
+                               }
+                       },
+                       1,#ARRAY{
+                               saisie, input,
+                               options, #ARRAY{
+                                       nom, prenom,
+                                       label, Prénom,
+                               }
+                       },
+                       2,#ARRAY{
+                               saisie, input,
+                               options, #ARRAY{
+                                       nom, nom,
+                                       label, Nom,
+                                       obligatoire, oui
+                               }
+                       },
+                       3,#ARRAY{
+                               saisie, input,
+                               options, #ARRAY{
+                                       nom, courriel,
+                                       label, Courriel,
+                                       obligatoire, oui
+                               },
+                               verifier, #ARRAY{
+                                       type, email
+                               }
+                       },
+                       4,#ARRAY{
+                               saisie, case,
+                               options, #ARRAY{
+                                       nom, case,
+                                       label, Une sorte de case à cocher,
+                                       label_case, Check la vibes
+                               }
+                       },
+                       5,#ARRAY{
+                               saisie, fieldset,
+                               options, #ARRAY{
+                                       nom, adresse,
+                                       label, Adresse
+                               },
+                               saisies, #ARRAY{
+                                       1,#ARRAY{
+                                               saisie, textarea,
+                                               options, #ARRAY{
+                                                       nom, voie,
+                                                       label, Voie,
+                                                       obligatoire, non,
+                                               }
+                                       },
+                                       2,#ARRAY{
+                                               saisie, input,
+                                               options, #ARRAY{
+                                                       nom, code_postal,
+                                                       label, Code postal,
+                                                       obligatoire, oui
+                                               }
+                                       },
+                                       3,#ARRAY{
+                                               saisie, input,
+                                               options, #ARRAY{
+                                                       nom, ville,
+                                                       label, Ville,
+                                                       obligatoire, oui
+                                               }
+                                       }
+                               }
+                       },
+                       6,#ARRAY{
+                               saisie, oui_non,
+                               options, #ARRAY{
+                                       nom, peutetre,
+                                       label, Tu veux ou tu veux pas ?,
+                                       obligatoire, oui,
+                                       info_obligatoire, " / obligatoire"
+                               }
+                       },
+               }
+       }
+       <form class="formulaire_spip" action="#SELF" method="post">
+               <ul>
+                       #GENERER_SAISIES{#GET{saisies}}
+                       <li class="boutons">
+                               <input type="submit" class="submit" />
+                       </li>
+               </ul>
+       </form>
+</body>
+</html>
diff --git a/www/plugins/saisies/test/saisie.html b/www/plugins/saisies/test/saisie.html
new file mode 100644 (file)
index 0000000..899d81a
--- /dev/null
@@ -0,0 +1,11 @@
+Version PHP : <?php echo phpversion(); ?>
+
+
+<h1>Input</h1>
+[(#SAISIE{input,titre})]
+
+<h1>Textarea</h1>
+[(#SAISIE{textarea,texte})]
+
+<h1>Input obligatoire et label</h1>
+[(#SAISIE{input,titre,obligatoire=oui,label=Un vrai titre})]
diff --git a/www/plugins/saisies/test/voir_saisie.html b/www/plugins/saisies/test/voir_saisie.html
new file mode 100644 (file)
index 0000000..d72f830
--- /dev/null
@@ -0,0 +1,10 @@
+<h1>Input et label</h1>
+[(#VOIR_SAISIE{input,titre,label=Un vrai titre,valeur=TRUC})]
+
+<h1>Textarea</h1>
+[(#VOIR_SAISIE{textarea,texte,valeur=Un super long texte<br/>sur plusieurs ligne})]
+
+<h1>Destinataires</h1>
+[(#VOIR_SAISIE{destinataires,destinataires, label=Destinataires,valeur=#ARRAY{0,1,1,2}})]
+
+
diff --git a/www/plugins/saisies/test/voir_saisies.html b/www/plugins/saisies/test/voir_saisies.html
new file mode 100644 (file)
index 0000000..3d7c50f
--- /dev/null
@@ -0,0 +1,147 @@
+<html>
+<head>
+       <title>Test de génération des vues de saisies</title>
+       [<link rel="stylesheet" href="(#CHEMIN{spip_formulaires.css})" type="text/css" media="all" title="formulaires" charset="utf-8" />]
+       <style type="text/css">
+               .formulaire_spip{
+                       width:40%;
+                       border:1px solid black;
+                       -moz-border-radius:10px;
+                       -webkit-border-radius:10px;
+                       border-radius:10px;
+               }
+               .formulaire_spip ul li{
+                       padding:0.5em;
+               }
+               .formulaire_spip li.fieldset{
+                       padding:0;
+               }
+               .formulaire_spip li.obligatoire{
+                       background:#ffcfcf;
+               }
+               .formulaire_spip .fieldset .legend{
+                       margin:0;
+                       font-style:italic;
+               }
+       </style>
+</head>
+<body>
+       <h1>Générer des vues de saisie</h1>
+
+       #SET{saisies,
+               #ARRAY{
+                       0,#ARRAY{
+                               saisie, destinataires,
+                               options, #ARRAY{
+                                       nom, destinataires,
+                                       label, Destinataires,
+                                       choix_destinataires, #ARRAY{0,1,1,2},
+                                       type_choix, plusieurs,
+                                       obligatoire, oui
+                               }
+                       },
+                       1,#ARRAY{
+                               saisie, input,
+                               options, #ARRAY{
+                                       nom, prenom,
+                                       label, Prénom,
+                               }
+                       },
+                       2,#ARRAY{
+                               saisie, input,
+                               options, #ARRAY{
+                                       nom, nom,
+                                       label, Nom,
+                                       obligatoire, oui
+                               }
+                       },
+                       3,#ARRAY{
+                               saisie, input,
+                               options, #ARRAY{
+                                       nom, courriel,
+                                       label, Courriel,
+                                       obligatoire, oui
+                               },
+                               verifier, #ARRAY{
+                                       type, email
+                               }
+                       },
+                       4,#ARRAY{
+                               saisie, case,
+                               options, #ARRAY{
+                                       nom, case,
+                                       label, Une sorte de case à cocher,
+                                       label_case, Check la vibes
+                               }
+                       },
+                       5,#ARRAY{
+                               saisie, fieldset,
+                               options, #ARRAY{
+                                       nom, adresse,
+                                       label, Adresse
+                               },
+                               saisies, #ARRAY{
+                                       1,#ARRAY{
+                                               saisie, textarea,
+                                               options, #ARRAY{
+                                                       nom, voie,
+                                                       label, Voie,
+                                                       obligatoire, non,
+                                               }
+                                       },
+                                       2,#ARRAY{
+                                               saisie, input,
+                                               options, #ARRAY{
+                                                       nom, code_postal,
+                                                       label, Code postal,
+                                                       obligatoire, oui
+                                               }
+                                       },
+                                       3,#ARRAY{
+                                               saisie, input,
+                                               options, #ARRAY{
+                                                       nom, ville,
+                                                       label, Ville,
+                                                       obligatoire, oui
+                                               }
+                                       }
+                               }
+                       },
+                       6,#ARRAY{
+                               saisie, oui_non,
+                               options, #ARRAY{
+                                       nom, peutetre,
+                                       label, Tu veux ou tu veux pas ?,
+                                       obligatoire, oui,
+                                       info_obligatoire, " / obligatoire"
+                               }
+                       },
+               }
+       }
+
+       <h2>Formulaire auquel on doit répondre</h2>
+       <div class="formulaire_spip">
+       <ul>
+       #GENERER_SAISIES{#GET{saisies}}
+       </ul>
+       </div>
+
+
+       #SET{valeurs,
+               #ARRAY{
+                       case, on,
+                       peutetre, '',
+                       prenom, Jean-Paul,
+                       code_postal, 22222,
+                       nom, Fitousi,
+                       ville, Deuville,
+                       courriel, robert@menard.com,
+                       destinataires, #ARRAY{1,1},
+                       voie, 2 rue du Deux,
+               }
+       }
+
+       <h2>Réponse à ce formulaire</h2>
+       #VOIR_SAISIES{#GET{saisies}, #GET{valeurs}}
+</body>
+</html>
diff --git a/www/plugins/seminaire/agenda.json.html b/www/plugins/seminaire/agenda.json.html
new file mode 100644 (file)
index 0000000..67f7889
--- /dev/null
@@ -0,0 +1,15 @@
+#HTTP_HEADER{Content-type:text/javascript;}
+[<BOUCLE_evenements(EVENEMENTS){', '}{par date_debut}  
+{agendafull date_debut,date_fin, periode,
+       #VAL{Y}|date{#ENV{start}}, #VAL{m}|date{#ENV{start}},     #VAL{d}|date{#ENV{start}},
+       #VAL{Y}|date{#ENV{end}}, #VAL{m}|date{#ENV{end}}, #VAL{d}|date{#ENV{end}}}
+       >
+                                       [(#ARRAY{id,#ID_EVENEMENT,
+title,[(#TITRE|html2unicode|unicode2charset)],
+allDay,[(#HORAIRE|=={oui}|non)],
+start,#DATE_DEBUT,
+end,#DATE_FIN,
+url,#URL_EVENEMENT*,
+className,calendrier-couleur5,
+description,[(#DESCRIPTIF|html2unicode|unicode2charset)]}|json_encode)]
+</BOUCLE_evenements>]
\ No newline at end of file
diff --git a/www/plugins/seminaire/agenda/inc-agenda.html b/www/plugins/seminaire/agenda/inc-agenda.html
new file mode 100644 (file)
index 0000000..563c82f
--- /dev/null
@@ -0,0 +1,5 @@
+<h2 class="menu-titre"><a href="[(#URL_PAGE{calendrier}|parametre_url{date_debut,#DATE|affdate{Y-m-01}})]"><:seminaire:titre_agenda:></a></h2>
+[(#CALENDRIER_MINI{#ENV{date},'date',#URL_PAGE{jour},#URL_PAGE{calendrier_mini_event.json}})]
+<div class="espace"></div>
+<!--connexion-->
+#MODELE{connexion}
\ No newline at end of file
diff --git a/www/plugins/seminaire/article_corps.html b/www/plugins/seminaire/article_corps.html
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/www/plugins/seminaire/base/seminaire.php b/www/plugins/seminaire/base/seminaire.php
new file mode 100644 (file)
index 0000000..5e5b673
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+function seminaire_declarer_champs_extras($champs = array()){
+       $champs['spip_evenements']['attendee'] = array(
+               'saisie' => 'input',// type de saisie
+               'options' => array(
+                       'nom' => 'attendee',
+                       'label' => _T('seminaire:attendee'), 
+                       'sql' => "varchar(256) NOT NULL DEFAULT ''", // declaration sql
+                       'rechercher'=>true,
+                       'defaut' => '', 
+       ));
+       $champs['spip_evenements']['origin'] = array(
+               'saisie' => 'input',
+               'options' => array(
+                       'nom' => 'origin', // nom sql
+                       'label' => _T('seminaire:origin'), 
+                       'sql' => "varchar(256) NOT NULL DEFAULT ''", // declaration sql
+                       'rechercher'=>true,
+                       'defaut' => '', 
+       ));
+       $champs['spip_evenements']['notes'] = array(
+               'saisie' => 'textarea',
+               'options' => array(
+                       'nom' => 'notes', // nom sql
+                       'label' => _T('seminaire:notes'), 
+                       'sql' => "text NOT NULL DEFAULT ''", // declaration sql
+                       'rechercher'=>true,
+                       'defaut' => '', 
+                       'rows' => 4,
+                       'traitements' => '_TRAITEMENT_RACCOURCIS',
+                       'class' =>'inserer_barre_edition',
+       ));
+       
+       return $champs;
+}
+?>
\ No newline at end of file
diff --git a/www/plugins/seminaire/calendrier.html b/www/plugins/seminaire/calendrier.html
new file mode 100644 (file)
index 0000000..13db95d
--- /dev/null
@@ -0,0 +1,3 @@
+[(#PLUGIN{kitcnrs}|oui)
+<INCLURE{fond=css/charte-#CONFIG{kitcnrs/charte,1}/structure, type_page=calendrier, env}>]
+[(#PLUGIN{Z}|oui) <INCLURE{fond=structure}{env}{type=page}{composition=agenda} />]
\ No newline at end of file
diff --git a/www/plugins/seminaire/contenu/calendrier.html b/www/plugins/seminaire/contenu/calendrier.html
new file mode 100644 (file)
index 0000000..811f4bc
--- /dev/null
@@ -0,0 +1,72 @@
+<div id="loading" style="position:relative;top:20px;left:350px;padding:5px 30px;display:none;border:1px solid #ddd;background:#eee;z-index:100;"><:organiseur:loading:></div>
+<div id="calendrier"></div>
+<link rel='stylesheet' type='text/css' href='#CHEMIN{lib/fullcalendar/fullcalendar.css}' />
+<link rel='stylesheet' type='text/css' href='#CHEMIN{calendrier.css}' />
+<script type='text/javascript' src='#CHEMIN{lib/fullcalendar/fullcalendar.js}'></script>
+<script type="text/javascript">
+jQuery(document).ready(function() {
+
+       // page is now ready, initialize the calendar...
+
+       $('#calendrier').fullCalendar({
+                       weekends: true, // will hide Saturdays and Sundays
+                       editable: false,
+                       events: "[(#URL_PAGE{agenda.json})]",
+                       header: {
+                               left: [(#LANG_DIR|=={ltr}|?{"'prevYear,prev,next,nextYear today'","'month,agendaWeek,agendaDay'"})],
+                               center: 'title',
+                               right: [(#LANG_DIR|=={ltr}|?{"'agendaDay,agendaWeek,month'","'today nextYear,next,prev,prevYear'"})]
+                       },
+                       firstDay: 1,
+                       monthNames:['<:date_mois_1|attribut_html:>','<:date_mois_2|attribut_html:>','<:date_mois_3|attribut_html:>','<:date_mois_4|attribut_html:>','<:date_mois_5|attribut_html:>','<:date_mois_6|attribut_html:>','<:date_mois_7|attribut_html:>','<:date_mois_8|attribut_html:>','<:date_mois_9|attribut_html:>','<:date_mois_10|attribut_html:>','<:date_mois_11|attribut_html:>','<:date_mois_12|attribut_html:>'],
+                       monthNamesShort:['<:date_mois_1_abbr|attribut_html:>','<:date_mois_2_abbr|attribut_html:>','<:date_mois_3_abbr|attribut_html:>','<:date_mois_4_abbr|attribut_html:>','<:date_mois_5_abbr|attribut_html:>','<:date_mois_6_abbr|attribut_html:>','<:date_mois_7_abbr|attribut_html:>','<:date_mois_8_abbr|attribut_html:>','<:date_mois_9_abbr|attribut_html:>','<:date_mois_10_abbr|attribut_html:>','<:date_mois_11_abbr|attribut_html:>','<:date_mois_12_abbr|attribut_html:>'],
+                       dayNames:['<:date_jour_1|attribut_html:>','<:date_jour_2|attribut_html:>','<:date_jour_3|attribut_html:>','<:date_jour_4|attribut_html:>','<:date_jour_5|attribut_html:>','<:date_jour_6|attribut_html:>','<:date_jour_7|attribut_html:>'],
+                       dayNamesShort:['<:date_jour_1_abbr|attribut_html:>','<:date_jour_2_abbr|attribut_html:>','<:date_jour_3_abbr|attribut_html:>','<:date_jour_4_abbr|attribut_html:>','<:date_jour_5_abbr|attribut_html:>','<:date_jour_6_abbr|attribut_html:>','<:date_jour_7_abbr|attribut_html:>'],
+                       buttonText: {
+                                       today: '<:date_aujourdhui|attribut_html:>',
+                                       month: '<:organiseur:cal_par_mois|attribut_html:>',
+                                       day: '<:organiseur:cal_par_jour|attribut_html:>',
+                                       week: '<:organiseur:cal_par_semaine|attribut_html:>'
+                       },
+                       weekMode : 'liquid',
+                       loading: function(bool) {
+                               if (bool) $('#loading').show();
+                               else $('#loading').hide();
+                       },
+                       firstHour: 8,
+                       minTime: 8,
+                       maxTime: 18,
+                       timeFormat: {
+                               // for agendaWeek and agendaDay
+                               agenda: "H'h'mm{ - H'h'mm}", // 5h00 - 6h30
+                               // for all other views
+                               '': "H'h'(mm)" // 19h
+                       },
+                       axisFormat: "H'h'(mm)",
+                       allDayText:'<:organiseur:cal_jour_entier|attribut_html:>',
+                       columnFormat: {
+                               month: 'ddd',    // Mon
+                               week: 'ddd d/M', // Mon 9/7
+                               day: 'dddd d/M'  // Monday 9/7
+                       },
+                       titleFormat: {
+                               month: 'MMMM yyyy',                             // September 2009
+                               week: "d [ MMM] [ yyyy]{ '&#8212;' d MMM yyyy}", // Sep 7 - 13 2009
+                               day: 'dddd d MMM yyyy'                  // Tuesday, Sep 8, 2009
+                       },
+                       dayClick: function(date, allDay, jsEvent, view) {
+                               if(view.name=='month'){
+                                       $('.fc-button-agendaWeek').click();
+                                       $('#calendrier').fullCalendar( 'gotoDate', date );
+                               }
+                               else
+                                       if(view.name=='agendaWeek'){
+                                               $('.fc-button-agendaDay').click();
+                                               $('#calendrier').fullCalendar( 'gotoDate', date );
+                                       }
+                       },
+                       isRTL : [(#LANG_DIR|=={rtl}|?{true,false})]
+       })
+
+});
+</script>
\ No newline at end of file
diff --git a/www/plugins/seminaire/contenu/page-agenda.html b/www/plugins/seminaire/contenu/page-agenda.html
new file mode 100644 (file)
index 0000000..eb2ac0a
--- /dev/null
@@ -0,0 +1,49 @@
+[(#REM)
+
+  Squelette
+  (c) 2009 xxx
+  Distribue sous licence GPL
+
+]
+[(#REM) Fil d'Ariane pour les squelettes Z ]
+[(#PLUGIN{Z}|oui) <p id="hierarchie"><a href="#URL_SITE_SPIP"><:accueil_site:></a> &gt; <strong class="on"><:seminaire:titre_agenda:></strong></p>]
+  
+<div id="seminaire">[(#REM) ce div est là pour hériter des styles du calendrier sans polluer le css de kitcnrs dont on récupère tout de même les css]
+  <INCLURE{fond=contenu/calendrier,env} />
+
+  <h2>Les événements de [(#DATE_DEBUT|affdate_mois_annee)]</h2>
+
+  <div class='plier_deplier'><a href='#' onclick='jQuery(".renseignements").show();return false;' class='noajax'><:seminaire:lien_tout_deplier:></a> | <a href='#' onclick='jQuery(".renseignements").hide();return false;' class='noajax'><:seminaire:lien_tout_replier:></a></div>
+
+  <BOUCLE_mot_evenement(MOTS){type=Type}>
+    <BOUCLE_mot_articles(MOTS){type=Catégorie}>
+      <BOUCLE_article_evenement(ARTICLES){titre_mot=#TITRE}{par num titre}{inverse}>
+        <B_ev>
+          <div class="type[ mot(#_mot_evenement:ID_MOT)]">
+            [<h3 class="agenda">(#_mot_evenement:TITRE|entites_html|unique)</h3>]
+            [<div class="descriptif">(#_mot_evenement:DESCRIPTIF|entites_html|unique)</div>]
+            <ul>
+              <li><h4>#_mot_articles:TITRE</h4></li>
+              <ul>
+        <BOUCLE_ev(EVENEMENTS){!evenement_passe #ENV{date_debut,#ENV{date}}}{!evenement_a_venir #ENV{date_debut,#ENV{date}}|agenda_moisdecal{1,'Y-m-d H:i:00'}}{par date_debut}{id_article}{id_evenement_source?}{titre_mot=#_mot_evenement:TITRE}>
+                <li>#MODELE{evenement_vevent}
+                <p class='renseignements'>
+                <span class='lien_article'><a href='#URL_ARTICLE'>Article</a></span>
+                </p>
+                </li>
+        </BOUCLE_ev>
+              </ul>
+            </ul>
+          </div>
+        </B_ev>
+        </BOUCLE_article_evenement>
+    </BOUCLE_mot_articles>
+  </BOUCLE_mot_evenement>
+
+  <span class="bouton">
+    <a href="#URL_ECRIRE{evenement_edit,new=oui&id_article=#ID_ARTICLE}" class='noajax creer'><:seminaire:creer_evenement:></a>
+  </span>
+</div>
+
+<script type="text/javascript" src="#CHEMIN{javascript/toggle.js}"></script>
+<INCLURE{fond=javascript/rechargement,env} />
diff --git a/www/plugins/seminaire/contenu/page-evenement.html b/www/plugins/seminaire/contenu/page-evenement.html
new file mode 100644 (file)
index 0000000..f2b9f99
--- /dev/null
@@ -0,0 +1,21 @@
+
+<BOUCLE_contenu(spip_evenements){id_evenement}>
+
+[(#REM) Fil d'Ariane et conteneur pour les squelettes Z ]
+[(#PLUGIN{Z}|oui)<p id='hierarchie'><a href='#URL_SITE_SPIP/'><:accueil_site:></a>[ &gt; <strong class='on'>(#TITRE|couper{80})</strong>]</p>
+
+<div class="contenu-principal">]
+
+       <div class='cartouche'>
+               <h1 class='h1 #EDIT{titre}'>#TITRE</h1>
+       </div>
+       
+       #MODELE{evenement_vevent}
+                       
+       <p>Pour en savoir plus sur cet événement, consultez l'article 
+               <a href="#URL_ARTICLE{#ID_ARTICLE}">#INFO_TITRE{article, #ID_ARTICLE}</a>
+       </p>
+
+[<div class='notes surlignable'><h2 class='h2 pas_surlignable'><:info_notes:></h2>(#NOTES)</div>]
+[(#PLUGIN{Z}|oui)</div>]
+</BOUCLE_contenu>
\ No newline at end of file
diff --git a/www/plugins/seminaire/contenu/page-jour.html b/www/plugins/seminaire/contenu/page-jour.html
new file mode 100644 (file)
index 0000000..de633fc
--- /dev/null
@@ -0,0 +1,31 @@
+[(#REM)
+
+  Squelette pour affichage de la liste des evenements d'une journee
+  (c) 2012 xxx
+  Distribue sous licence GPL
+
+]
+[(#PLUGIN{kitcnrs}|non)
+       <p id="chemin">
+               <a href="#URL_SITE_SPIP"><:accueil_site:></a> &gt; <:seminaire:aujour:>
+       </p>
+]
+
+<BOUCLE_mot_evenements(MOTS){type=Type}>
+
+<B_ev>
+       [<h1>[(#ENV{date}|affdate): ](#TOTAL_BOUCLE|singulier_ou_pluriel{agenda:info_un_evenement,agenda:info_nombre_evenements}{doublons})</h1>]
+
+<h2 class="agenda">[(#DESCRIPTIF|attribut_html)]</h2>
+       <ul class="liste-items evenements">
+       <BOUCLE_ev(EVENEMENTS){date_debut<=#ENV{date}|replace{00:00:00, 23:59:59}}{date_fin>=#ENV{date}}{par date_debut}{id_mot}>
+               <li class="item">
+                       <h3>#INFO_TITRE{article,#ID_ARTICLE}</h3>
+                       #MODELE{evenement_vevent}
+                       <a href="#URL_ARTICLE{#ID_ARTICLE}"><:seminaire:plus:> : #INFO_TITRE{article, #ID_ARTICLE}</a>
+               </li>
+       </BOUCLE_ev>
+       </ul>
+
+</B_ev>
+</BOUCLE_mot_evenements>
\ No newline at end of file
diff --git a/www/plugins/seminaire/evenement.html b/www/plugins/seminaire/evenement.html
new file mode 100644 (file)
index 0000000..7e59168
--- /dev/null
@@ -0,0 +1,3 @@
+[(#PLUGIN{kitcnrs}|oui)
+<INCLURE{fond=css/charte-#CONFIG{kitcnrs/charte,1}/structure, type_page=evenement, env}>]
+[(#PLUGIN{Z}|oui) <INCLURE{fond=structure}{env}{type=page}{composition=evenement} />]
\ No newline at end of file
diff --git a/www/plugins/seminaire/export_xml.html b/www/plugins/seminaire/export_xml.html
new file mode 100644 (file)
index 0000000..234e225
--- /dev/null
@@ -0,0 +1,30 @@
+#HTTP_HEADER{Content-type:text/xml; charset=#CHARSET}
+#CACHE{0}
+<?xml version="1.0" encoding="utf-8"?>
+<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0">
+  <vcalendar>
+     <properties>
+      <calscale>
+        <text>GREGORIAN</text>
+      </calscale>
+      <prodid>
+       <text>-//SPIP//NONSGML v1.0//FR</text>
+      </prodid>
+      <version>
+        <text>2.0</text>
+      </version>
+     </properties>
+ [(#REM)mettre ici l'inclure  pour l'appel du fichier contenant les components]
+<BOUCLE_evenement2(EVENEMENTS){branche ?}{id_article ?}{statut=publie}{par date_fin}{age_fin<=0}{0,50}{doublons}>
+       [(#INCLURE{fond=lure/evenement-xml}{evtitre=#TITRE}{id_evenement}{date_debut}{horaire}{date_fin}{id_article}{descriptif}{lieu}{name}{abstract})]
+</BOUCLE_evenement2> 
+<BOUCLE_evenement3(EVENEMENTS){branche ?}{id_article ?}{statut=publie}{par date_fin}{age_fin>=0}{inverse}{0,50}{doublons}>
+       [(#INCLURE{fond=inclure/evenement-xml}{evtitre=#TITRE}{id_evenement}{date_debut}{horaire}{date_fin}{id_article}{descriptif}{lieu}{name}{abstract})]
+</BOUCLE_evenement3>
+<BOUCLE_evenement(EVENEMENTS){branche ?}{id_article ?}{statut=publie}{par date_fin}{inverse}{0,50}{doublons}>
+       [(#INCLURE{fond=inclure/evenement-xml}{evtitre=#TITRE}{id_evenement}{date_debut}{horaire}{date_fin}{id_article}{descriptif}{lieu}{name}{abstract})]
+</BOUCLE_evenement>
+  </vcalendar>
+</icalendar>
\ No newline at end of file
diff --git a/www/plugins/seminaire/extra/page-agenda.html b/www/plugins/seminaire/extra/page-agenda.html
new file mode 100644 (file)
index 0000000..a217dc9
--- /dev/null
@@ -0,0 +1,17 @@
+[(#REM)
+
+  Squelette
+  (c) 2009 xxx
+  Distribue sous licence GPL
+
+]
+[(#REM)#CALENDRIER_MINI{#ENV{date_debut},date_debut,#SELF,#URL_PAGE{calendrier_mini_event.json}} pas de minicalendrier dans la page agenda]
+<B_mois>
+#ANCRE_PAGINATION
+<ul class="liste-items">
+<BOUCLE_mois(EVENEMENTS){!par date_debut}{evenement_passe #ENV{date}}{id_article?}{id_rubrique?}{id_mot?}{id_evenement_source?}{fusion_par_mois date_debut}{pagination 11}>
+<li class="item">[(#SELF|parametre_url{date_debut,#DATE_DEBUT|affdate{Y-m-01}}|lien_ou_expose{#DATE_DEBUT|affdate_mois_annee{},#ENV{date_debut,''}|=={#DATE_DEBUT|affdate{Y-m-01}}})]</li>
+</BOUCLE_mois>
+</ul>
+[<p class="pagination">(#PAGINATION)</p>]
+</B_mois>
\ No newline at end of file
diff --git a/www/plugins/seminaire/fabrique_seminaire.php b/www/plugins/seminaire/fabrique_seminaire.php
new file mode 100644 (file)
index 0000000..06e549b
--- /dev/null
@@ -0,0 +1,141 @@
+<?php
+
+/**
+ *  Fichier généré par la Fabrique de plugin v5
+ *   le 2012-04-20 18:19:48
+ *
+ *  Ce fichier de sauvegarde peut servir à recréer
+ *  votre plugin avec le plugin «Fabrique» qui a servi à le créer.
+ *
+ *  Bien évidemment, les modifications apportées ultérieurement
+ *  par vos soins dans le code de ce plugin généré
+ *  NE SERONT PAS connues du plugin «Fabrique» et ne pourront pas
+ *  être recréées par lui !
+ *
+ *  La «Fabrique» ne pourra que régénerer le code de base du plugin
+ *  avec les informations dont il dispose.
+ *
+**/
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+$data = array (
+  'fabrique' => 
+  array (
+    'version' => 5,
+  ),
+  'paquet' => 
+  array (
+    'nom' => 'Séminaire LATP',
+    'slogan' => 'G&#233;rer les &#233;v&#233;nements d\\\'un laboratoire de recherche',
+    'description' => 'Chaque &#233;v&#233;nement est associ&#233; à des mots-cl&#233;s qui permettent, d’une part, de fixer son type, s&#233;minaire, colloque, groupe de travail... et d’autre part, de d&#233;finir le laboratoire ou l’organisme responsable. La gestion des &#233;v&#233;nements est assur&#233;e par le plugin Agenda.',
+    'prefixe' => 'seminaire',
+    'version' => '2.0.0',
+    'auteur' => 'Amaury Adon',
+    'auteur_lien' => 'http://www.spip-contrib.net/Amaury-Adon',
+    'licence' => 'GNU/GPL',
+    'categorie' => 'date',
+    'etat' => 'dev',
+    'compatibilite' => '[3.0.0-rc;3.0.*]',
+    'documentation' => 'http://www.spip-contrib.net/Seminaire-LATP',
+    'administrations' => 'on',
+    'schema' => '1.0.0',
+    'formulaire_config' => 'on',
+    'formulaire_config_titre' => 'seminaire',
+    'inserer' => 
+    array (
+      'paquet' => '    <necessite nom="agenda" compatibilite="[3.5.1;]" />\r
+       <necessite nom="cextras" compatibilite="[3.0.5;[" />\r
+       <utilise nom="kitcnrs" compatibilite="[4.0.10;]" />\r
+       <utilise nom="Z" compatibilite="[1.7.17;]" />\r
+       <pipeline nom="declarer_champs_extras" inclure="base/seminaire.php" />',
+      'administrations' => 
+      array (
+        'maj' => 'cextras_api_upgrade(seminaire_declarer_champs_extras(), $maj[\'create\']);\r
+',
+        'desinstallation' => '',
+        'fin' => '',
+      ),
+      'base' => 
+      array (
+        'tables' => 
+        array (
+          'fin' => 'if (!defined("_ECRIRE_INC_VERSION")) return;\r
+\r
+function seminaire_declarer_champs_extras($champs = array()){\r
+       $champs[\'spip_evenements\'][\'name\'] = array(\r
+               \'saisie\' => \'input\',// type de saisie\r
+               \'options\' => array(\r
+                       \'nom\' => \'name\',\r
+                       \'label\' => _T(\'seminaire:name\'), \r
+                       \'sql\' => "varchar(256) NOT NULL DEFAULT \'\'", // declaration sql\r
+                       \'rechercher\'=>true,\r
+                       \'defaut\' => \'\',     \r
+       ));\r
+       $champs[\'spip_evenements\'][\'origin\'] = array(\r
+               \'saisie\' => \'input\',\r
+               \'options\' => array(\r
+                       \'nom\' => \'origin\', // nom sql\r
+                       \'label\' => _T(\'seminaire:origin\'), \r
+                       \'sql\' => "varchar(256) NOT NULL DEFAULT \'\'", // declaration sql\r
+                       \'rechercher\'=>true,\r
+                       \'defaut\' => \'\',     \r
+       ));\r
+       $champs[\'spip_evenements\'][\'abstract\'] = array(\r
+               \'saisie\' => \'textarea\',\r
+               \'options\' => array(\r
+                       \'nom\' => \'abstract\', // nom sql\r
+                       \'label\' => _T(\'seminaire:abstract\'), \r
+                       \'sql\' => "text NOT NULL DEFAULT \'\'", // declaration sql\r
+                       \'rechercher\'=>true,\r
+                       \'defaut\' => \'\',     \r
+                       \'rows\' => 4,\r
+                       \'traitements\' => \'_TRAITEMENT_RACCOURCIS\',\r
+                       \'class\'       =>\'\',\r
+       ));     \r
+       $champs[\'spip_evenements\'][\'notes\'] = array(\r
+               \'saisie\' => \'textarea\',\r
+               \'options\' => array(\r
+                       \'nom\' => \'notes\', // nom sql\r
+                       \'label\' => _T(\'seminaire:notes\'), \r
+                       \'sql\' => "text NOT NULL DEFAULT \'\'", // declaration sql\r
+                       \'rechercher\'=>true,\r
+                       \'defaut\' => \'\',     \r
+                       \'rows\' => 4,\r
+                       \'traitements\' => \'_TRAITEMENT_RACCOURCIS\',\r
+       ));\r
+       \r
+       return $champs;',
+        ),
+      ),
+    ),
+    'scripts' => 
+    array (
+      'pre_copie' => '',
+      'post_creation' => '',
+    ),
+    'exemples' => '',
+  ),
+  'objets' => 
+  array (
+  ),
+  'images' => 
+  array (
+    'paquet' => 
+    array (
+      'logo' => 
+      array (
+        0 => 
+        array (
+          'extension' => 'png',
+          'contenu' => 'iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAYKSURBVGiB7ZlbaBxVGMd/Zy672TTppqYratLaqrSguLVeUhRFpeIN1IqCVizqi4j0zZeKqPgmiK8+iT60qMT7hYrgg23AW4OlLZamWk1iEnWbS5Pd7Ca7M+fzYWaT3SSbnuluqIIfHGb3zOX7/7/rmTNKRPgvi3W+AdQr/xM43+I06kE7d+5YZ7uJ1zzPfxAEpZQW0JayRCFaRGmllFbgo+TD4qx+sbu7+3S9ehtGwImtenPTFRu3d113re04NiKC1oKIIAhaNKIFz/f58dDhpwaG/rgI2FG33gZgB6BUKt1+Y9cN9rabbiORaK55XX46RyIej721t//eRuhtGAGttes4NhNjpzn8+8ma123YuIlYLIbW2m2E3oYRKEsmM4LWuvb5v0caqq/uKqQCcQG075OdnET7fs2RnQrOh/e6SilVl36TTvzKK8pK9tn3u667p1QqdgEKQAARGPYu46/4Vh5/5CFjxd0ffkLLzEk2cATLEipYiOvGfiyVSq9ObvY/e/llqe1ODEMo2Wff39yyem/6+ltbOjo3YNk2IuD5mk8PTTA5VOLum29ktjhjTOCeu7bz7feryagrefLWdloTDkqB9n01PNS/7Wjvgb30Te0CPqmbgOu6e7Z03d7S0Xkpf+XiHB2/hHxRMfjHAFNTFk/s2sXqZJsx+LJ0rruUL/Z/yev7J7hmy3WsikN6zQid6zeiLKult2f/noYQKJWKXR3rLke8PMcmNnLfg4+SSqV49713OPzT9/z+2wmUFT2dRGvakqtwYs3s3r2bM2fO8PH7+7gjcYKOdZfzXRCuy4ppFVKW7eB7MD2rSKVS9Pf3s/Waazlw4Bu++KonMviyxOMxnnn6WXK5HG1tbUzPBvOW7QCcNcHNy6iqtrCI0NTUxPN7XkApNTcsy6r6bzoWPtsAO1BHGTUBEvVZFTNIowkIiqVK7rlY+2ykBUEMjWHuAaGKQCMsXouIiARNxkDMPaAsROaVNNLii3QJK+8BEymWfDLj05Q8HYlY4AEzAsZVaGEOmAA68svfZPNFkq0Jtl3VYUw8iKCGV6Glk3jJK0MCf47lyOWLjGSmonvAkICxBzQWugK/CZBcvoSInDXulyJg6oFI7wNRy2g2P0v5lkhlNEKuRejEqqqymVSUbL64CIxRCKEWdf66CYhSCNGSOJcvorVE7tQrE0KiiLoLmSsEBMCM8DwBaHgSC6qqO5p6wPfPYe9VxLQRR8wBAxdUEnjrpR2IgG2brVDn8a/IatQsBypl35dHeeC5d3n7s5+MSM8RAMQQWt2NbDmLfn6wj9Nnpnn/658jlVG9Eh4QrMir0Ye2X0mqrZnH7tlSNW8UQoYVK2Iji7YaffTOq9l5V/qsFl+sx3wt1PAkXvrWpa3teR7ZbJaZmRkKhQKDg4M4jh1kWuM9UN0HTDxQM8a1plAoMDY2xujoKIVCgampKYaHh2m1c5H6wDlXoZpXGRDyfZ9MJsPx48fJZrOk02lc18W1NJtih0M9dRJQFRJMWFUeqLX7YCKO45BMJnFdl6GhIU6dOsXgwACb3UNYSJjEVhWOms9aCJqA+sIx90KTcDTj4+O0t7cbga0lqVSKdDpNf38/vb29rOcoTWomMFJ1rrmAKKU05e3YimScI6CUsgg8YofzTvhbBTehEOi0+/ioex8zXv2f17TWOI5DOwOsjU3M4RZR5YonQAugAQ/wAU8p5ZeJOCF4FYJ3gSYgDsTC/7aPc2w8M5hubutgbTbDWo7VDb4s8yV/9dxcvPVCxjOD+DjHoHhBCL4Yjpnw6CmlcCrCxq4gkAhHC7Cm54T3TWvbe5u33PJwfM2Fm8Gqa0t/edHCxOgQR3o+mP3hV28/cDEwCRQIjKwJPKEBXauMlr2RBNZ/fkTnJ6ZHDg6NvLF1TbO/dgXhI8BE3h7t6fO+O3hSTgPtwGw4FokK31kXeiDOfBiVPbEqPOdiWuPODb8fgp0G8uGYZT505kIIkLkvNMsksRUCLh9X0gFlEhCESJlQVRKHRxERUQsWaLXK6EqDriVScVyyjNb8RrZc8zgfIjWAGn3k+zfLP9MZwT/6dvKXAAAAAElFTkSuQmCC',
+        ),
+      ),
+    ),
+    'objets' => 
+    array (
+    ),
+  ),
+);
+
+?>
\ No newline at end of file
diff --git a/www/plugins/seminaire/formulaires/configurer_seminaire.html b/www/plugins/seminaire/formulaires/configurer_seminaire.html
new file mode 100644 (file)
index 0000000..c8d4ba3
--- /dev/null
@@ -0,0 +1,20 @@
+<div class="formulaire_spip formulaire_configurer formulaire_#FORM">
+
+       <h3 class="titrem"><:seminaire:cfg_titre_parametrages:></h3>
+
+       [<p class="reponse_formulaire reponse_formulaire_ok">(#ENV*{message_ok})</p>]
+       [<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV*{message_erreur})</p>]
+
+       <form method="post" action="#ENV{action}">
+               #ACTION_FORMULAIRE{#ENV{action}}
+       <div>
+       <ul>
+               #SAISIE{input, prodid, label=PRODID pour le calendrier (moins de 50 caractères), obligatoire=oui}      
+       
+       </ul>
+
+               <input type="hidden" name="_meta_casier" value="seminaire" />
+               <p class="boutons"><span class="image_loading">&nbsp;</span><input type="submit" class="submit" value="<:bouton_enregistrer:>" /></p>
+       </div>
+       </form>
+</div>
\ No newline at end of file
diff --git a/www/plugins/seminaire/formulaires/editer_evenement.html b/www/plugins/seminaire/formulaires/editer_evenement.html
new file mode 100644 (file)
index 0000000..adb966f
--- /dev/null
@@ -0,0 +1,135 @@
+<div class="formulaire_spip formulaire_editer formulaire_editer_evenement formulaire_editer_evenemnt-#ENV{id_evenement,nouveau}">
+       [<p class="reponse_formulaire reponse_formulaire_ok">(#ENV**{message_ok})</p>]
+       [<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV**{message_erreur})</p>]
+
+
+<BOUCLE_recup_id_mot(spip_mots_liens){id_objet=#ID_EVENEMENT}{objet=evenement}>
+       #SET{id_mot_recupere,#ID_MOT}
+</BOUCLE_recup_id_mot>
+
+       <form method='post' action='#ENV{action}' enctype='multipart/form-data'><div>
+               [(#REM) declarer les hidden qui declencheront le service du formulaire 
+               parametre : url d'action ]
+               #ACTION_FORMULAIRE{#ENV{action}}
+               <input type='hidden' name='id_evenement' value='#ID_EVENEMENT' />
+               <input type='hidden' name='id_parent' value='#ENV{id_parent}' />
+
+         <ul>
+                       <li class="editer_titre obligatoire[ (#ENV**{erreurs}|table_valeur{titre}|oui)erreur]">
+                               <label for="titre"><:agenda:evenement_titre:></label>[
+                               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{titre})</span>
+                               ]<input type='text' class='text' name='titre' id='titre' value="#ENV{titre}" />
+                       </li>
+               <li class='editer_parent[ (#ENV**{erreurs}|table_valeur{id_parent}|oui)erreur]'>
+               <label for="id_parent"><:agenda:evenement_article:></label>[
+               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{id_parent})</span>
+               ]<INCLURE{fond=formulaires/selecteur/articles}{selected=#ENV{parents_id}}{id_article=#ENV{id_parent}}{name=parents_id}{select=1}{rubriques=0}>
+               </li>
+
+               <li class='editer_mot obligatoire[ (#ENV**{erreurs}|table_valeur{id_mot}|oui)erreur]'>
+                       <label for="id_mot"><:seminaire:choix_mot:></label>[
+               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{id_mot})</span>
+               ]
+                       <p class="explication"><:seminaire:type_evenement:></p>
+                       <select name="id_mot" id="id_mot">
+                               <option></option>
+                       <BOUCLE_groupe2(GROUPES_MOTS){titre=Type}>
+                               <BOUCLE_mots2(MOTS){id_groupe}>
+                               <option value="#ID_MOT" [(#ID_MOT|=={#GET{id_mot_recupere}}|?{' selected="selected"',''})]>#TITRE</option>
+                               </BOUCLE_mots2> 
+                       </BOUCLE_groupe2>       
+                       </select>
+               </li>
+
+           <li class='editer_date fieldset'><fieldset><legend><:agenda:evenement_date:></legend>
+                               <ul>
+                                       <li class="editer_horaire[ (#ENV**{erreurs}|table_valeur{horaire}|oui)erreur]">
+                                               <label for="horaire"><:agenda:evenement_horaire:></label>[
+                                               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{horaire})</span>
+                                               ]<input type='checkbox' name='horaire' id='horaire' value='non' [(#ENV{horaire}|=={oui}|non)checked='checked']
+                                                       onclick="if (this.checked==false) { $('span.afficher_horaire').show();} else {$('span.afficher_horaire').hide();}" />
+                                       </li>
+                                       <li class="editer_date_debut_fin[ (#ENV**{erreurs}|table_valeur{date_debut}|oui)erreur][ (#ENV**{erreurs}|table_valeur{date_fin}|oui)erreur]">
+                                               [
+                                               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{date_debut})</span>][
+                                               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{date_fin})</span>
+                                               ]<label for="date_debut"><:agenda:evenement_date_de:></label><input type='text' class='text date' name='date_debut' id='date_debut' size='10' maxlength='10' value="[(#ENV{date_debut})]" />
+                                               <span class='afficher_horaire[(#ENV{horaire}|=={oui}|non)none]'>
+                                               <label for='heure_debut' class='heure'><:agenda:evenement_date_a_immediat:></label>
+                                               <input type='text' class='text heure' name='heure_debut' id='heure_debut' size='4' maxlength='5' value="[(#ENV{heure_debut})]"
+                                               /></span>
+                                               <label for="date_fin" class='date_fin'><:agenda:evenement_date_a:></label>
+                                               <span class='afficher_horaire[(#ENV{horaire}|=={oui}|non)none]'><label for='heure_fin' class='heure'><:agenda:evenement_date_a_immediat:></label>
+                                               <input type='text' class='text heure' name='heure_fin' id='heure_fin' size='4' maxlength='5' value="[(#ENV{heure_fin})]"
+                                               /></span><input type='text' class='text date' name='date_fin' id='date_fin' size='10' maxlength='10' value="[(#ENV{date_fin})]" />
+                                       </li>
+                               </ul>
+           </fieldset></li>
+               <li class="editer_repetitions[ (#ENV**{erreurs}|table_valeur{repetitions}|oui)erreur]">
+                               <label for="repetitions"><:agenda:evenement_repetitions:></label>[
+                               (#ENV{repetitions}|non)<a href='#' onclick="$(this).hide().next().show('fast');return false;"><:agenda:ajouter_repetition:></a>
+                               <div class='ajouter_repetitions none'>][
+                               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{repetitions})</span>
+                               ]<div id='repetitions_picker'></div>
+                               <textarea name='repetitions' id='repetitions' readonly="readonly">#ENV{repetitions}</textarea>[
+                               (#ENV{repetitions}|non)</div>]
+                       </li>
+         [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ]
+         <!--extra-->
+                       <li class="editer_descriptif[ (#ENV**{erreurs}|table_valeur{descriptif}|oui)erreur]">
+                               <label for="descriptif"><:seminaire:abstract:></label>[
+                               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{descriptif})</span>
+                               ]<textarea name='descriptif' rows='5' id='descriptif' class="inserer_barre_edition">[(#ENV{descriptif})]</textarea>
+               </li>
+           [
+               (#ENV{affiche_inscription,oui}|=={oui}|oui)
+           <li class="editer_inscription[ (#ENV**{erreurs}|table_valeur{inscription}|oui)erreur]">
+                   <div class='choix inscription'>
+                                       [<span class='erreur_message'>(#ENV**{erreurs}|table_valeur{inscription})</span>]
+                                       <input type='checkbox' class='checkbox' name='inscription' id='inscription' value="1"[ (#ENV{inscription}|oui)checked="checked"] />
+                                       <label for="inscription"><:agenda:label_inscription:></label>
+                               </div><div class='choix places'>
+                               <label for="places"><:agenda:label_places:></label>[
+                               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{places})</span>
+                               ]<input type='text' class='text' name='places' id='places' value="[(#ENV{places})]" />
+                               </div>
+           </li>]
+           <li class="editer_lieu[ (#ENV**{erreurs}|table_valeur{lieu}|oui)erreur]">
+                               <label for="lieu"><:agenda:evenement_lieu:></label>[
+                               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{lieu})</span>
+                               ]<input type='text' class='text' name='lieu' id='lieu' value="[(#ENV{lieu})]" />
+           </li>
+           <li class="editer_adresse[ (#ENV**{erreurs}|table_valeur{adresse}|oui)erreur]">
+                               <label for="adresse"><:agenda:evenement_adresse:></label>[
+                               <span class='erreur_message'>(#ENV**{erreurs}|table_valeur{adresse})</span>
+                               ]<textarea name='adresse' rows='3' id='adresse'>[(#ENV{adresse})]</textarea>
+           </li>
+         </ul>
+         <p class='boutons'><input type='submit' class='submit' value='[(#ENV{id_evenement}|?{<:bouton_enregistrer:>,<:bouton_ajouter:>})]' /></p>
+       </div></form>
+       #INCLURE{fond=formulaires/dateur/inc-dateur}
+
+</div>
+<script type="text/javascript">
+       var repetitions_done = false;
+       jQuery(document).bind('datePickerLoaded',function(){
+               if (!repetitions_done){
+                       repetitions_done = true;
+                       jQuery.getScript('#CHEMIN{javascript/jquery-ui.multidatespicker.js}',function(){
+                               var multidate_picker_options = {altField: '#repetitions'};
+                               /**
+                                * Multidatepicker n'aime pas un array vide apparemment
+                                */
+                               if(jQuery('#repetitions').html() != ""){
+                                       multidate_picker_options.addDates = jQuery('#repetitions').html().split(',');
+                               }
+                               jQuery('#repetitions_picker')
+                                       .multiDatesPicker(jQuery.extend(
+                                                               date_picker_options(),
+                                                               multidate_picker_options
+                                       ))
+                                       .addClass('.pickable'); // une seule fois;
+                       });
+               }
+       });
+</script>
\ No newline at end of file
diff --git a/www/plugins/seminaire/formulaires/editer_evenement.php b/www/plugins/seminaire/formulaires/editer_evenement.php
new file mode 100644 (file)
index 0000000..81c7131
--- /dev/null
@@ -0,0 +1,150 @@
+<?php
+/**
+ * Plugin Agenda 4 pour Spip 3.0
+ * Licence GPL 3
+ *
+ * 2006-2011
+ * Auteurs : cf paquet.xml
+ */
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+include_spip('inc/actions');
+include_spip('inc/editer');
+include_spip('inc/autoriser');
+
+function formulaires_editer_evenement_charger_dist($id_evenement='new', $id_article=0, $retour='', $lier_trad = 0, $config_fonc='evenements_edit_config', $row=array(), $hidden=''){
+
+       $valeurs = formulaires_editer_objet_charger('evenement',$id_evenement,$id_article,0,$retour,$config_fonc,$row,$hidden);
+
+       if (!$valeurs['id_article'])
+               $valeurs['id_article'] = $id_article;
+       if (!$valeurs['titre'])
+               $valeurs['titre'] = sql_getfetsel('titre','spip_articles','id_article='.intval($valeurs['id_article']));
+       $valeurs['id_parent'] = $valeurs['id_article'];
+       unset($valeurs['id_article']);
+       // pour le selecteur d'article(s) optionnel
+       $valeurs['parents_id'] = array("article|".$valeurs['id_parent']);
+
+       // fixer la date par defaut en cas de creation d'evenement
+       if (!intval($id_evenement)){
+               $t=time();
+               $valeurs["date_debut"] = date('Y-m-d H:i:00',$t);
+               $valeurs["date_fin"] = date('Y-m-d H:i:00',$t+3600);
+               $valeurs['horaire'] = 'oui';
+       }
+
+       // les repetitions
+       $valeurs['repetitions'] = array();
+       if (intval($id_evenement)){
+               $repetitons = sql_allfetsel("date_debut","spip_evenements","id_evenement_source=".intval($id_evenement),'','date_debut');
+               foreach($repetitons as $d)
+                       $valeurs['repetitions'][] = date('d/m/Y',strtotime($d['date_debut']));
+       }
+       $valeurs['repetitions'] = implode(',',$valeurs['repetitions']);
+
+       // dispatcher date et heure
+       list($valeurs["date_debut"],$valeurs["heure_debut"]) = explode(' ',date('d/m/Y H:i',strtotime($valeurs["date_debut"])));
+       list($valeurs["date_fin"],$valeurs["heure_fin"]) = explode(' ',date('d/m/Y H:i',strtotime($valeurs["date_fin"])));
+
+       // traiter specifiquement l'horaire qui est une checkbox
+       if (_request('date_debut') AND !_request('horaire'))
+               $valeurs['horaire'] = 'oui';
+
+       // Pouvoir interdire l'affichage de l'inscription (puisque ce n'est pas traite' par le plugin)
+       $valeurs['affiche_inscription'] = $GLOBALS['agenda_affiche_inscription'];
+
+       $valeurs['places'] = intval($valeurs['places']);
+
+       $valeurs['id_mot']=_request('id_mot');
+
+       return $valeurs;
+}
+
+/**
+ * Identifier le formulaire en faisant abstraction des parametres qui
+ * ne representent pas l'objet edite
+ */
+function formulaires_editer_evenement_identifier_dist($id_evenement='new', $id_article=0, $retour='', $lier_trad = 0, $config_fonc='evenements_edit_config', $row=array(), $hidden=''){
+       return serialize(array(intval($id_evenement),$lier_trad));
+}
+
+
+function evenements_edit_config(){
+       return array();
+}
+
+function formulaires_editer_evenement_verifier_dist($id_evenement='new', $id_article=0, $retour='', $lier_trad = 0, $config_fonc='evenements_edit_config', $row=array(), $hidden=''){
+       $erreurs = formulaires_editer_objet_verifier('evenement',$id_evenement,array('titre','date_debut','date_fin','id_mot'));
+
+       include_spip('inc/date_gestion');
+
+       $id_mot = _request('id_mot');
+       $horaire = _request('horaire')=='non'?false:true;
+       $date_debut = verifier_corriger_date_saisie('debut',$horaire,$erreurs);
+       $date_fin = verifier_corriger_date_saisie('fin',$horaire,$erreurs);
+
+       if ($date_debut AND $date_fin AND $date_fin<$date_debut)
+               $erreurs['date_fin'] = _T('agenda:erreur_date_avant_apres');
+
+       include_spip('formulaires/selecteur/selecteur_fonctions');
+       if (count($id = picker_selected(_request('parents_id'),'article'))
+         AND $id = reset($id)
+         AND $id = sql_getfetsel('id_article','spip_articles','id_article='.intval($id))){
+         // reinjecter dans id_parent
+         set_request('id_parent',$id);
+       }
+
+       if (!$id_parent = intval(_request('id_parent')))
+               $erreurs['id_parent'] = _T('agenda:erreur_article_manquant');
+       else {
+               if (!autoriser('creerevenementdans','article',$id_parent))
+                       $erreurs['id_parent'] = _T('agenda:erreur_article_interdit');
+       }
+
+       //if (!isset($id_mot))
+       //      $erreurs['id_mot'] = _T('seminaire:mot_obligatoire');
+       #if (!count($erreurs))
+       #       $erreurs['message_erreur'] = 'ok?';
+       return $erreurs;
+}
+
+function formulaires_editer_evenement_traiter_dist($id_evenement='new', $id_article=0, $retour='', $lier_trad = 0, $config_fonc='evenements_edit_config', $row=array(), $hidden=''){
+       set_request('horaire',_request('horaire')=='non'?'non':'oui');
+       set_request('inscription',_request('inscription')?1:0);
+       include_spip('inc/date_gestion');
+       $erreurs = array();
+       $date_debut = verifier_corriger_date_saisie('debut',_request('horaire')=='oui',$erreurs);
+       $date_fin = verifier_corriger_date_saisie('fin',_request('horaire')=='oui',$erreurs);
+       set_request('date_debut',date('Y-m-d H:i:s',$date_debut));
+       set_request('date_fin',date('Y-m-d H:i:s',$date_fin));
+
+       $res = formulaires_editer_objet_traiter('evenement',$id_evenement,$id_article,0,$retour,$config_fonc,$row,$hidden);
+       // si c'est une creation dans un article publie, passer l'evenement en publie
+       // l'article peut être renseigné/modifié par l'utilisateur dans le formulaire. On le retrouve.
+       if (!intval($id_evenement)
+         AND $id_article = sql_getfetsel('id_article', 'spip_evenements', 'id_evenement='.$res['id_evenement'])
+         AND objet_test_si_publie('article',$id_article)){
+               // sera refuse si auteur pas autorise
+               evenement_modifier($res['id_evenement'],array('statut'=>'publie'));
+       }
+       // a la creation, documenter la date de creation
+       if (!intval($id_evenement))
+               evenement_modifier($res['id_evenement'],array('date_creation'=>date('Y-m-d H:i:s')));
+
+
+       $id_evenement = $res['id_evenement'];
+       if ($res['redirect']) {
+               if (strpos($res['redirect'],'article')!==false){
+                       $id_article = sql_getfetsel('id_article','spip_evenements','id_evenement='.intval($id_evenement));
+                       $res['redirect'] = parametre_url($res['redirect'],'id_article',$id_article);
+               }
+       }
+       //Saisir un mot clé
+       $id_mot=_request('id_mot');
+       $id=sql_insertq('spip_mots_liens',array('id_mot'=>$id_mot,'id_objet'=>$id_evenement,'objet'=>'evenement'));
+       $res['id_mot']=$id_mot;
+       return $res;
+}
+
+?>
diff --git a/www/plugins/seminaire/head/page-agenda.html b/www/plugins/seminaire/head/page-agenda.html
new file mode 100644 (file)
index 0000000..580388a
--- /dev/null
@@ -0,0 +1,2 @@
+<title><:seminaire:titre_agenda:> - [(#NOM_SITE_SPIP|textebrut)]</title>
+
diff --git a/www/plugins/seminaire/images/calendrier.png b/www/plugins/seminaire/images/calendrier.png
new file mode 100644 (file)
index 0000000..cade87d
Binary files /dev/null and b/www/plugins/seminaire/images/calendrier.png differ
diff --git a/www/plugins/seminaire/images/logo_seminaire.png b/www/plugins/seminaire/images/logo_seminaire.png
new file mode 100644 (file)
index 0000000..54f00fa
Binary files /dev/null and b/www/plugins/seminaire/images/logo_seminaire.png differ
diff --git a/www/plugins/seminaire/inc/inc-p-calendrier.html b/www/plugins/seminaire/inc/inc-p-calendrier.html
new file mode 100755 (executable)
index 0000000..c1e9bd8
--- /dev/null
@@ -0,0 +1,10 @@
+[(#REM) Contenu de page calendrier\r
+--\r
+  Paramètres:\r
+  - type_page=calendrier\r
+  - env : toutes les variables d'environnement\r
+--\r
+  Utilisé par: squelette principal calendrier.html, via structure.html de la charte active (kitcnrs)\r
+]\r
+\r
+<INCLURE{fond=contenu/page-agenda}{env}>\r
diff --git a/www/plugins/seminaire/inc/inc-p-evenement.html b/www/plugins/seminaire/inc/inc-p-evenement.html
new file mode 100755 (executable)
index 0000000..81bb3b4
--- /dev/null
@@ -0,0 +1,10 @@
+[(#REM) Contenu de page evenement\r
+--\r
+  Paramètres:\r
+  - type_page=evenement\r
+  - env : toutes les variables d'environnement\r
+--\r
+  Utilisé par: squelette principal evenement.html, via structure.html de la charte active (kitcnrs)\r
+]\r
+\r
+<INCLURE{fond=contenu/page-evenement}{env}>\r
diff --git a/www/plugins/seminaire/inc/inc-p-jour.html b/www/plugins/seminaire/inc/inc-p-jour.html
new file mode 100755 (executable)
index 0000000..44e7ef4
--- /dev/null
@@ -0,0 +1,10 @@
+[(#REM) Contenu de page jour\r
+--\r
+  Paramètres:\r
+  - type_page=jour\r
+  - env : toutes les variables d'environnement\r
+--\r
+  Utilisé par: squelette principal jour.html, via structure.html de la charte active (kitcnrs)\r
+]\r
+\r
+<INCLURE{fond=contenu/page-jour}{env}>\r
diff --git a/www/plugins/seminaire/inclure/agenda_article.html b/www/plugins/seminaire/inclure/agenda_article.html
new file mode 100644 (file)
index 0000000..2cce941
--- /dev/null
@@ -0,0 +1,134 @@
+<BOUCLE_compte(EVENEMENTS){id_article}{0,1}>[(#REM)pour vérifier qu'un événment est bein associé à l'article]
+</BOUCLE_compte>
+ <div id="seminaire">
+<BOUCLE_seminaire(ARTICLES){id_article}>
+[(#REM) Se placer au debut du mois en cours par defaut]
+#SET{date_debut,#VAL{Y-m-}|date|concat{01}}
+<BOUCLE_un(EVENEMENTS){id_evenement}{id_article?}{id_rubrique?}{id_mot?}{id_evenement_source?}>
+[(#REM) Si un evenement passe, on commence par le jour de cet evenement]
+#SET{date_debut,#DATE_DEBUT|affdate{Y-m-d}}
+</BOUCLE_un>
+       #SET{date_debut,#ENV{date}|affdate{Y-m-d}}
+       #SET{date_debut,#ENV{date_debut,#GET{date_debut}}}
+<//B_un>
+
+#SET{self,#SELF|parametre_url{date_debut|id_evenement|debut_agenda,''}}#SET{yena,''}
+<h2><:agenda:agenda:></h2>
+<B_mots>
+<div class='pagination'>
+<div class='plier_deplier'><a href='#' onclick='jQuery(".renseignements").show();return false;' class='noajax'><:seminaire:lien_tout_deplier:></a> | <a href='#' onclick='jQuery(".renseignements").hide();return false;' class='noajax'><:seminaire:lien_tout_replier:></a></div>
+[(#GET{self}|parametre_url{date_debut,1900-01-01}|lien_ou_expose{<:seminaire:evenements_depuis_debut:>,#ENV{date_debut}|=={1900-01-01},noajax}) |]
+[(#GET{self}|parametre_url{date_debut,#ENV{date}|affdate{Y-m-d}}|lien_ou_expose{<:agenda:evenements_a_venir:>,#GET{date_debut}|=={#ENV{date}|affdate{Y-m-d}},noajax}) ]
+</div>
+
+<BOUCLE_mots(MOTS){type=Type}>
+<B_agenda>
+<h3>[(#DESCRIPTIF|attribut_html)]</h3>
+<ul class='liste_items evenements'>
+       <BOUCLE_agenda(EVENEMENTS){par date_debut}{!evenement_passe #GET{date_debut}}{id_evenement?}{id_article?}{id_rubrique?}{titre_mot=#TITRE}{id_evenement_source?}{pagination #ENV{pas,9}}>
+               <li class='item[ (#EXPOSE)][ (#ID_EVENEMENT_SOURCE|oui)repetition]'>
+                       [(#MODELE{evenement_vevent}|trim|sinon{'?'})]
+               </li>
+               <hr>
+       </BOUCLE_agenda>
+       [<p class='pagination'>(#PAGINATION{page})</p>]
+</ul>
+</B_agenda>
+
+</BOUCLE_mots>
+
+<div id="seminaire-article-boutons">
+
+  <span class="bouton">[(#REM)bouton de création d'événement]
+    <a href="#URL_ECRIRE{evenement_edit,id_evenement=new&id_article=#ID_ARTICLE}" class='noajax creer'><:seminaire:creer_evenement:></a>
+  </span>
+
+  <a class="google" href="http://www.google.com/calendar/render?cid=#URL_SITE_SPIP/%3Fpage%3Dseminaire_ical%26id_article%3D#ID_ARTICLE" target="_blank"><img src="http://www.google.com/calendar/images/ext/gc_button1_fr.gif" border=0></a>
+  <a class="ical" href="[(#URL_PAGE{seminaire_ical}|parametre_url{id_article, #ID_ARTICLE}|url_absolue|replace{'http://','webcal://'})]" title="<:seminaire:abonnement:>"><img src="#CHEMIN{images/calendrier.png}"/><span class="texte">iCal</span></a>[(#REM)bouton d'abonnement au calendrier]
+
+</div>
+<script type="text/javascript" src="#CHEMIN{javascript/toggle.js}"></script>
+
+</BOUCLE_seminaire>
+
+<style type="text/css">
+.plier_deplier {
+float: right;
+font-size: 0.9em;
+}
+.resume {
+cursor: help;
+}
+.date {
+color: #5d8ba2;
+}
+#seminaire ul {
+margin: 5px 0px 5px 0px;
+list-style-position: inside;
+list-style-type: none !important;
+list-style-image: none !important;
+}
+.evenements li.item {
+padding-left: 20px;
+}
+.evenements li.item:hover {
+background: 
+#edf3fe;
+-webkit-border-radius: 5px;
+-moz-border-radius: 5px;
+border-radius: 5px;
+}
+.bouton {
+background: 
+silver;
+color: 
+black;
+-webkit-box-shadow: 3px 3px 3px #888;
+-moz-box-shadow: 3px 3px 3px #888;
+box-shadow: 3px 3px 3px #888;
+padding: .3em .5em;
+margin-top: 10px;
+-moz-border-radius: 5px;
+-webkit-border-radius: 5px;
+border-radius: 5px;
+}
+.bouton:hover {
+background: 
+#B1C3D9;
+-webkit-box-shadow: 5px 5px 5px #888;
+-moz-box-shadow: 5px 5px 5px #888;
+box-shadow: 5px 5px 5px #888;
+}
+.bouton:active {
+background: 
+#808080;
+-webkit-box-shadow: 1px 1px 1px #888;
+-moz-box-shadow: 1px 1px 1px #888;
+box-shadow: 1px 1px 1px #888;
+}
+.bouton a {
+text-decoration: none;
+color: 
+#2E5B6B;
+}
+a.google {
+float: right;
+margin-right: 5px;
+}
+a.ical {
+float: right;
+width: 45px;
+text-decoration: none;
+text-align: center;
+}
+a.ical img {
+border: 0;
+width: 100%;
+margin-bottom: -28px;
+}
+</style>
+
+
+
+</div><!--fin du bloc seminaire-->
+</B_compte>
\ No newline at end of file
diff --git a/www/plugins/seminaire/inclure/evenement-ical.html b/www/plugins/seminaire/inclure/evenement-ical.html
new file mode 100755 (executable)
index 0000000..13dc1e3
--- /dev/null
@@ -0,0 +1,21 @@
+BEGIN:VEVENT\r
+SUMMARY:[(#EVTITRE|textebrut|filtrer_ical)]\r
+UID:[(#DATE_CREATION|date_ical)]-a#ID_ARTICLE-e#ID_EVENEMENT@[(#URL_SITE_SPIP||replace{http://})][\r
+DTSTAMP:(#DATE_CREATION|date_ical)][(#HORAIRE|=={oui}|?{[\r
+DTSTART:(#DATE_DEBUT|date_ical)][\r
+DTEND:(#DATE_FIN|date_ical)],[\r
+DTSTART;VALUE=DATE:(#DATE_DEBUT|affdate{Ymd})][\r
+DTEND;VALUE=DATE:(#DATE_FIN|agenda_jourdecal{1,Ymd})]})][\r
+CREATED:(#DATE_CREATION|date_ical)][\r
+LAST-MODIFIED:(#MAJ|date_ical)][\r
+LOCATION:(#LIEU|PtoBR|textebrut|filtrer_ical)]<BOUCLE_auteur(AUTEURS){id_article=#ID_ARTICLE}{0,1}>\r
+[ORGANIZER;CN=(#NOM|supprimer_tags|textebrut|filtrer_ical)][:mailto:#EMAIL]</BOUCLE_auteur>[\r
+ATTENDEE:(#ATTENDEE|supprimer_tags|textebrut|filtrer_ical)][ - (#ORIGIN|supprimer_tags|textebrut|filtrer_ical)][\r
+DESCRIPTION:(#DESCRIPTIF|supprimer_tags|textebrut|filtrer_ical)]<BOUCLE_article(ARTICLES) {id_article=#ID_ARTICLE}>[\r
+CATEGORIES:(#TITRE|textebrut|filtrer_ical)]</BOUCLE_article>[\r
+URL:(#URL_ARTICLE|parametre_url{id_evenement,#ID_EVENEMENT}|url_absolue|filtrer_ical)]<BOUCLE_sequence(VERSIONS ?){objet=evenement}{id_objet=#ID_EVENEMENT}{!par id_version}{0,1}>[\r
+SEQUENCE:(#ID_VERSION|moins{1})\r
+]</BOUCLE_sequence><BOUCLE_coordonnees(EVENEMENTS ?){si #PLUGIN{gis}|oui}{gis}{id_evenement}>\r
+[GEO:(#LAT);#LON]</BOUCLE_coordonnees>\r
+STATUS:CONFIRMED\r
+END:VEVENT\r
diff --git a/www/plugins/seminaire/inclure/evenement-xml.html b/www/plugins/seminaire/inclure/evenement-xml.html
new file mode 100644 (file)
index 0000000..6302dfe
--- /dev/null
@@ -0,0 +1,26 @@
+<components>
+  <vevent>
+    <properties>
+      <dtstart>
+        <date-time>[(#DATE_DEBUT|date_ical)]</date-time>
+      </dtstart>
+      <summary>
+         <text>[(#EVTITRE|textebrut)]</text>
+      </summary>
+      <location>
+         <text>[(#LIEU|PtoBR|textebrut)]</text>
+      </location>
+      <status>CONFIRMED</status>
+      <description>
+        <text>
+          [(#ABSTRACT|textebrut)]
+               </text>
+      </description>
+      <categories>
+                       <BOUCLE_article(ARTICLES) {id_article=#ID_ARTICLE}>
+                               [(#TITRE|textebrut)]
+                       </BOUCLE_article>
+                 </categories>
+   </properties>
+  </vevent>
+</components>
diff --git a/www/plugins/seminaire/javascript/rechargement.html b/www/plugins/seminaire/javascript/rechargement.html
new file mode 100644 (file)
index 0000000..5a1b467
--- /dev/null
@@ -0,0 +1,5 @@
+<script type="text/javascript">
+$(document).ready(function(){
+       $('#calendrier').fullCalendar('gotoDate',[(#DATE_DEBUT|affdate{"Y"})][,(#DATE_DEBUT|affdate{"n"})-1]);
+});
+</script>
\ No newline at end of file
diff --git a/www/plugins/seminaire/javascript/toggle.js b/www/plugins/seminaire/javascript/toggle.js
new file mode 100644 (file)
index 0000000..ac0fa89
--- /dev/null
@@ -0,0 +1,6 @@
+$(document).ready(function(){
+       $('.renseignements').hide();
+       $('.resume-titre').click(function() {
+               $(this).next().slideToggle('fast');
+       });
+});
\ No newline at end of file
diff --git a/www/plugins/seminaire/jour.html b/www/plugins/seminaire/jour.html
new file mode 100644 (file)
index 0000000..cc95b9e
--- /dev/null
@@ -0,0 +1,3 @@
+[(#PLUGIN{kitcnrs}|oui)
+<INCLURE{fond=css/charte-#CONFIG{kitcnrs/charte,1}/structure, type_page=jour, env}>]
+[(#PLUGIN{Z}|oui) <INCLURE{fond=structure}{env}{type=page}{composition=jour} />]
\ No newline at end of file
diff --git a/www/plugins/seminaire/lang/paquet-seminaire_fr.php b/www/plugins/seminaire/lang/paquet-seminaire_fr.php
new file mode 100644 (file)
index 0000000..915b115
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // S
+       'seminaire_description' => 'Chaque événement est associé à des mots-clés qui permettent, d’une part, de fixer son type, séminaire, colloque, groupe de travail... et d’autre part, de définir le laboratoire ou l’organisme responsable. La gestion des événements est assurée par le plugin Agenda.',
+       'seminaire_nom' => 'Séminaire',
+       'seminaire_slogan' => 'Gérer les événements d\\\'un laboratoire de recherche',
+);
+
+?>
\ No newline at end of file
diff --git a/www/plugins/seminaire/lang/seminaire_fr.php b/www/plugins/seminaire/lang/seminaire_fr.php
new file mode 100755 (executable)
index 0000000..7e78573
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'attention_mot_cle'                      =>             'Il faut IMPERATIVEMENT mettre un mot cl&eacute; sur l&rsquo;&eacute;v&eacute;nement',
+       'abstract'                                       =>     'R&eacute;sum&eacute;',
+       'apreciser'                                      =>             'A pr&eacute;ciser',
+       'aujour'                                         =>             'Les &eacute;v&eacute;nements de la journ&eacute;e',
+       'a_venir'                                        =>             'Les prochains s&eacute;minaires',
+       'attendee'                                       =>     'Nom de l&rsquo;intervenant',
+       'attention_type'                         =>             'Le choix du type d&rsquo;&eacute;v&eacute;nement d&eacute;termine le tri qui lui sera appliqu&eacute;.',
+       'abonnement'                             =>             'S&rsquo;abonner au calendrier',
+
+       // C
+       'cfg_exemple'                            =>     'Exemple',
+       'cfg_exemple_explication'        =>     'Explication de cet exemple',
+       'cfg_titre_parametrages'         =>     'Paramétrages',
+       'choix_mot'                                      =>             'Type d&rsquo;&eacute;v&eacute;nement',
+       'creer_evenement'                        =>             'Ajouter un &eacute;v&eacute;nement', 
+
+       // E
+       'erreur_install_groupe_categories' => 'erreur &agrave; la cr&eacute;ation du groupe',
+       'evenement_titre'                        =>             'Titre de l&rsquo;expos&eacute;',
+       'evenements_depuis_debut'        =>             'Archives',     
+       'evenements_du_jour'             =>             'Les &eacute;v&eacute;nements du jour', 
+       'evenements_seul'                        =>             'D&eacute;tails d&rsquo;un &eacute;v&eacutenement',     
+       'exemple'                                        =>             'Par exemple : CMI - Salle R164',
+
+       // L
+       'lien_tout_replier'                      =>             'Sans r&eacute;sum&eacute;',
+       'lien_tout_deplier'                      =>             'Avec r&eacute;sum&eacute;',
+       'lieu'                                           =>             'Lieu',
+
+       // M
+       'mots_categorie_kitcnrs'         =>     'Les mots cl&eacute;s à affecter aux articles pour que les &eacute;v&eacute;nements soient encore mieux tri&eacute;s',
+       'mots_cles_categories'           =>             'Les diff&eacute;rentes cat&eacute;gories de s&eacute;minaires, groupes de travail et &eacute;v&eacute;nements exceptionnels',
+       'mots_cles_techniques_kitcnrs'=>        'Les mots cl&eacute;s pour rep&eacute;rer plus finement les diff&eacute;rents types d&rsquo;&eacute;v&eacute;nements',
+
+       // N
+       'nom_de_l_intervenant'           =>             'Intervenant &agrave; pr&eacute;ciser',
+       'notes'                                          =>             'Notes de dernières minutes',
+
+       // O
+       'origin'                                         =>     'Institut d&rsquo;origine de l&rsquo;intervenant',
+       'oubli_titre'                            =>             'Ne pas oublier de mettre le titre de l&rsquo;&eacute;v&eacute;nement',
+
+
+       // P
+       'plus'                                           =>             'En savoir plus',
+       'precisions_name'                        =>             'le nom sera affich&eacute; dans le calendrier et dans le mini calendrier',
+       'precisions_origin'                      =>             'N&rsquo;apparaît que dans les d&eacute;tails de l&rsquo;&eacute;v&eacute;nement',
+       'precisions_abstract'            =>             'pour mettre du code LATEX, ajoutez les balises <math> </math> en d&eacute;but et fin de texte (ex :  <math>La fraction $\frac{1}{2}$</math>)',
+       'precisions_notes'                       =>             'N&rsquo;apparaît que dans les d&eacute;tails de l&rsquo;&eacute;v&eacute;nement',
+
+       // S
+       'seminaire_titre' => 'Séminaire',
+
+       // T
+       'titre_agenda'                           =>             'Agenda Scientifique',
+       'titre_evenement'                        =>             'Titre à préciser',
+       'titre_page_configurer_seminaire' => 'seminaire',
+       'type'                                           =>             'Type d&rsquo;&eacute;v&eacute;nement',
+       'type_evenement'                         => ' Le choix d\'un type d\'événement est obligatoire.',
+);
+
+?>
\ No newline at end of file
diff --git a/www/plugins/seminaire/modeles/connexion.html b/www/plugins/seminaire/modeles/connexion.html
new file mode 100644 (file)
index 0000000..966f2dd
--- /dev/null
@@ -0,0 +1,8 @@
+<div align="center">
+       [(#SESSION{id_auteur}|?{' '})
+               <div class='espace'></div>
+               <span class="bouton"><a href="#URL_LOGOUT" rel="nofollow"><:icone_deconnecter:></a></span>]
+       [(#SESSION{id_auteur}|?{'',' '})<div class='espace'></div>
+               <span class="bouton"><a href="[(#URL_PAGE{login}|parametre_url{url,#SELF})]" rel="nofollow" class='login_modal'><:lien_connecter:></a></span>]
+       [(#AUTORISER{ecrire})<p><a href="#EVAL{_DIR_RESTREINT_ABS}"><:espace_prive:></a></p>]
+</div>
\ No newline at end of file
diff --git a/www/plugins/seminaire/modeles/evenement_vevent.html b/www/plugins/seminaire/modeles/evenement_vevent.html
new file mode 100644 (file)
index 0000000..142c186
--- /dev/null
@@ -0,0 +1,26 @@
+<BOUCLE_vevent(EVENEMENTS){id_evenement=#ENV{id,#ENV{id_evenement}}}{tout}>
+<div class="vevent id_#ID_EVENEMENT" id="hcalendar-event-title">
+       <header class="resume-titre">
+               <h3 class="resume">
+                       <ACM.[(#DATE_DEBUT|affdate{'YmdHi'})]>
+                       [<span class="date">(#DATE_DEBUT|agenda_affdate_debut_fin{#DATE_FIN,#HORAIRE,'hcal'})</span> - ][(#REM)date de l'evenement]
+                       <ACM.nom><span class="#EDIT{name} name">#ATTENDEE</span>[ - <span class="#EDIT{origin} origin">(#ORIGIN)</span>]</ACM.nom>
+               </h3>
+               <ACM.titre>
+                       <span class="#EDIT{titre} titre summary">#TITRE</span> [(#REM)titre de l'evenement]
+               </ACM.titre>
+       </header>
+       <div class="renseignements">
+               <ACM.msc>       
+                       [(#REM)abstract de l'evenement, le filtre est là pour que les maths soient interprétés dans l'abstract (marche aussi avec le filtre typo). description est là pour le hcal]
+                       [(#INSCRIPTION|?{' ',''})<p class="inscrits"><strong><:seminaire:inscriptions:> : </strong>#NB_INSCRITS[/(#PLACES) ]<:agenda:inscrits:></p>]
+                       [<p class="#EDIT{abstract} abstract description"><strong><:seminaire:abstract:> : </strong>(#DESCRIPTIF|propre|PtoBR)</p>]
+                       [<p class="lieu"><strong><:seminaire:lieu:> : </strong><span class="location #EDIT{lieu}">(#LIEU|textebrut)</span>[<span class="adresse #EDIT{adresse}"> - (#ADRESSE|PtoBR)</span>]</p>]
+                       [<p class="#EDIT{notes} notes"><strong><:seminaire:notes:> :</strong> (#CHAMP_SQL{notes}|typo)</p>]
+               </ACM.msc>
+               <div class="ajax">
+                       #FORMULAIRE_PARTICIPER_EVENEMENT{#ID_EVENEMENT}
+               </div>  
+       </div>
+</div>
+</BOUCLE_vevent>
\ No newline at end of file
diff --git a/www/plugins/seminaire/navigation/dist.html b/www/plugins/seminaire/navigation/dist.html
new file mode 100644 (file)
index 0000000..3131131
--- /dev/null
@@ -0,0 +1,8 @@
+[(#REM) Menu de navigation par rubriques ]
+<INCLURE{fond=inclure/rubriques}{id_rubrique}>
+
+#FORMULAIRE_RECHERCHE
+<h2 class="menu-titre"><a href="[(#URL_PAGE{calendrier}|parametre_url{date_debut,#DATE|affdate{Y-m-01}})]"><:seminaire:titre_agenda:></a></h2>
+#CALENDRIER_MINI{#ENV{date},'date',#URL_PAGE{jour},#URL_PAGE{calendrier_mini_event.json}}
+[(#REM)connexion-->]
+#MODELE{connexion}
\ No newline at end of file
diff --git a/www/plugins/seminaire/navigation/page-agenda.html b/www/plugins/seminaire/navigation/page-agenda.html
new file mode 100644 (file)
index 0000000..2188c4b
--- /dev/null
@@ -0,0 +1,5 @@
+[(#REM) Menu de navigation par rubriques ]\r
+<INCLURE{fond=inclure/rubriques}{id_rubrique}>\r
+\r
+#FORMULAIRE_RECHERCHE\r
+\r
diff --git a/www/plugins/seminaire/paquet.xml b/www/plugins/seminaire/paquet.xml
new file mode 100644 (file)
index 0000000..85719cb
--- /dev/null
@@ -0,0 +1,23 @@
+<paquet\r
+       prefix="seminaire"\r
+       categorie="date"\r
+       version="2.1.12"\r
+       etat="stable"\r
+       compatibilite="[3.0.0;3.0.*]"\r
+       logo="prive/themes/spip/images/seminaire-64.png"\r
+       documentation="http://contrib.spip.net/?article4288"\r
+       schema="1.0.3" \r
+>\r
+       <nom>Séminaire</nom>\r
+\r
+       <auteur lien='http://contrib.spip.net/Amaury-Adon'>Amaury Adon</auteur>\r
+\r
+       <licence>GNU/GPL</licence>\r
+               <necessite nom="agenda" compatibilite="[3.5.1;]" />\r
+               <necessite nom="cextras" compatibilite="[3.0.5;[" />\r
+               <utilise nom="Z" compatibilite="[1.7.17;]" />\r
+               <pipeline nom="declarer_champs_extras" inclure="base/seminaire.php" />\r
+               <pipeline nom="insert_head_css" action="insert_head_css" inclure="seminaire_pipelines.php" />\r
+               <pipeline nom="post_insertion" action="post_insertion" inclure="seminaire_pipelines.php" />\r
+\r
+</paquet>
\ No newline at end of file
diff --git a/www/plugins/seminaire/prive/squelettes/contenu/configurer_seminaire.html b/www/plugins/seminaire/prive/squelettes/contenu/configurer_seminaire.html
new file mode 100644 (file)
index 0000000..8045df1
--- /dev/null
@@ -0,0 +1,7 @@
+[(#AUTORISER{configurer,_seminaire}|sinon_interdire_acces)]
+
+<h1 class="grostitre"><:seminaire:titre_page_configurer_seminaire:></h1>
+
+<div class="ajax">
+       #FORMULAIRE_CONFIGURER_SEMINAIRE
+</div>
\ No newline at end of file
diff --git a/www/plugins/seminaire/prive/themes/spip/images/seminaire-128.png b/www/plugins/seminaire/prive/themes/spip/images/seminaire-128.png
new file mode 100644 (file)
index 0000000..c016f71
Binary files /dev/null and b/www/plugins/seminaire/prive/themes/spip/images/seminaire-128.png differ
diff --git a/www/plugins/seminaire/prive/themes/spip/images/seminaire-32.png b/www/plugins/seminaire/prive/themes/spip/images/seminaire-32.png
new file mode 100644 (file)
index 0000000..c016f71
Binary files /dev/null and b/www/plugins/seminaire/prive/themes/spip/images/seminaire-32.png differ
diff --git a/www/plugins/seminaire/prive/themes/spip/images/seminaire-64.png b/www/plugins/seminaire/prive/themes/spip/images/seminaire-64.png
new file mode 100644 (file)
index 0000000..c016f71
Binary files /dev/null and b/www/plugins/seminaire/prive/themes/spip/images/seminaire-64.png differ
diff --git a/www/plugins/seminaire/seminaire_administrations.php b/www/plugins/seminaire/seminaire_administrations.php
new file mode 100644 (file)
index 0000000..f06c986
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+/**
+ * Plugin Séminaire LATP
+ * (c) 2012 Amaury Adon
+ * Licence GNU/GPL
+ */
+
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+       include_spip('inc/cextras');
+       include_spip('base/seminaire');
+       include_spip('inc/meta');
+
+
+/**
+ * Fonction d'installation du plugin et de mise à jour.
+**/
+function seminaire_upgrade($nom_meta_base_version, $version_cible) {
+
+       cextras_api_upgrade(seminaire_declarer_champs_extras(), $maj['create']);
+/**activer les mots clés et leur configuration avancée s'ils ne le sont pas déjà**/
+       if ($GLOBALS['meta']['articles_mots']!=oui){
+               ecrire_meta("articles_mots", "oui");
+               ecrire_meta("config_precise_groupes", "oui");
+               ecrire_meta("documents_objets", "spip_evenements");     
+               }
+/**activer les révisions sur les événements**/
+       // ce qui existe déjà
+       $versions = unserialize($GLOBALS['meta']['objets_versions']);
+       // ce que j'ajoute
+       $versionnage_des_evenements = array('spip_evenements');
+       // merge des 2
+       $versions = array_merge($versions,$versionnage_des_evenements);
+       // balançons dans meta
+       ecrire_meta('objets_versions',serialize($versions)); 
+ /**Creer le groupe de mots clés Type pour les types d'événements**/
+       if (sql_countsel('spip_mots', "titre IN ('seminaire','groupe de travail','evenement important')") == 0) 
+       {
+                       $id_groupe = sql_insertq('spip_groupes_mots', 
+                       array('titre'=>'Type', 'descriptif'=>_T('seminaire:mots_cles_techniques_kitcnrs'),'tables_liees'=>'evenements', 'minirezo'=>'oui','comite'=>'oui')
+                 );
+       if (sql_error() != '') die((_T('seminaire:erreur_install_groupe_technique ')).sql_error());
+       
+        $Tstatuts = array('séminaire','groupe de travail','événement important');
+       foreach ($Tstatuts as $st) 
+               {
+               sql_insertq('spip_mots', 
+                       array('titre'=>$st, 'descriptif'=>$st, 'id_groupe'=>$id_groupe, 'type'=>'Type')
+                               );
+               if (sql_error() != '') $Terreur[] = (_T('erreur_creation_mot_cle')).$st.': '.sql_error();
+       };
+       };
+/** création du groupe de mots clés Catégorie et de ses mots cles pours les équipes **/
+    if (sql_countsel('spip_mots', "titre IN ('Algèbre, Dynamique et Topologie','Analyse Appliquée', 'Analyse et Géométrie', 'FRUMAM', 'Géométrie et Singularités', 'Guide d’ondes et milieux stratifiés', 'Probabilités et statistiques', 'Séminaire des doctorants', 'Théorie des nombres')") == 0) 
+    {
+        $id_groupe = sql_insertq('spip_groupes_mots',array('titre'=>'Catégorie', 'descriptif'=> _T('seminaire:mots_cles_categories'), 'tables_liees'=>'articles', 'minirezo'=>'oui','comite'=>'oui')
+                  );
+        if (sql_error() != '') die((_T('seminaire:erreur_install_groupe_coordonnees')).sql_error());
+        
+        $Tstatuts = array('Algèbre, Dynamique et Topologie','Analyse Appliquée', 'Analyse et Géométrie', 'FRUMAM', 'Géométrie et Singularités', 'Guide d’ondes et milieux stratifiés', 'Probabilités et statistiques', 'Séminaire des doctorants', 'Théorie des nombres');
+        foreach ($Tstatuts as $st) {
+          sql_insertq('spip_mots', 
+              array('titre'=>$st, 'id_groupe'=>$id_groupe, 'type'=>'Catégorie')
+                  );
+          if (sql_error() != '') $Terreurs[] = (_T('erreur_creation_mot_cle')).$st.': '.sql_error();
+        }
+    }
+
+       $maj = array();
+       $maj['create']= array(
+       array('maj_tables',array('spip_evenements')),
+       );
+       $maj['1.0.1'] = array(
+/*Copie de abstract vers descriptif*/
+       array('sql_update','spip_evenements', array('descriptif'=>'abstract')),
+       array('sql_alter',"TABLE spip_evenements DROP abstract"),       
+/*on change name en attendee*/
+       array('sql_alter',"TABLE spip_evenements ADD attendee text NOT NULL"),
+       array('sql_update',"spip_evenements", array('attendee'=>'name')),
+       array('sql_alter',"TABLE spip_evenements DROP name"),   
+       );
+       $maj['1.0.2'] = array(
+       array('sql_alter',"TABLE spip_evenements ADD id_mot integer NOT NULL"),
+       );
+       $maj['1.0.3'] = array(
+       array('sql_alter',"TABLE spip_evenements DROP id_mot"),
+       );      include_spip('base/upgrade');
+       maj_plugin($nom_meta_base_version, $version_cible, $maj);
+}
+
+
+/**
+ * Fonction de désinstallation du plugin.
+**/
+function seminaire_vider_tables($nom_meta_base_version) {
+
+
+       effacer_meta($nom_meta_base_version);
+}
+
+?>
\ No newline at end of file
diff --git a/www/plugins/seminaire/seminaire_ical.html b/www/plugins/seminaire/seminaire_ical.html
new file mode 100755 (executable)
index 0000000..a719927
--- /dev/null
@@ -0,0 +1,11 @@
+#CACHE{3600}BEGIN:VCALENDAR\r
+VERSION:2.0\r
+PRODID:-//SPIP/Plugin #PLUGIN{AGENDA,nom}//NONSGML v1.0//FR\r
+X-WR-TIMEZONE:Europe/Paris\r
+CALSCALE:GREGORIAN\r
+X-WR-CALNAME;VALUE=TEXT:[(#INFO_TITRE{article,#ENV{id_article}}|supprimer_numero|filtrer_ical)] -- [(#NOM_SITE_SPIP|filtrer_ical)]\r
+X-WR-RELCALID:[(#URL_ARTICLE|url_absolue|filtrer_ical)]\r
+<BOUCLE_evenement2(EVENEMENTS){branche ?}{id_article ?}{statut=publie}{par date_fin}{age_fin<=0}{0,50}{doublons}>[(#INCLURE{fond=inclure/evenement-ical}{evtitre=#TITRE}{id_evenement}{date_debut}{horaire}{date_fin}{id_article}{descriptif}{lieu}{date_creation}{maj}{attendee}{descriptif})]\r
+</BOUCLE_evenement2><BOUCLE_evenement3(EVENEMENTS){branche ?}{id_article ?}{statut=publie}{par date_fin}{age_fin>=0}{inverse}{0,50}{doublons}>[(#INCLURE{fond=inclure/evenement-ical}{evtitre=#TITRE}{id_evenement}{date_debut}{horaire}{date_fin}{id_article}{descriptif}{lieu}{date_creation}{maj}{attendee}{descriptif})]\r
+</BOUCLE_evenement3><BOUCLE_evenement(EVENEMENTS){branche ?}{id_article ?}{statut=publie}{par date_fin}{inverse}{0,50}{doublons}>[(#INCLURE{fond=inclure/evenement-ical}{evtitre=#TITRE}{id_evenement}{date_debut}{horaire}{date_fin}{id_article}{descriptif}{lieu}{date_creation}{maj}{attendee}{descriptif})]\r
+</BOUCLE_evenement>END:VCALENDAR
\ No newline at end of file
diff --git a/www/plugins/seminaire/seminaire_pipelines.php b/www/plugins/seminaire/seminaire_pipelines.php
new file mode 100644 (file)
index 0000000..386f54e
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+function seminaire_insert_head_css($flux) {
+    $css = find_in_path('styles/calendrier-seminaire.css');
+    $flux .= "<link rel='stylesheet' type='text/css' media='all' href='$css' />\n";
+    return $flux;
+}
+
+function seminaire_post_insertion($flux) {
+       if ($flux['args']['table'] == 'spip_evenements') {
+               sql_insertq("spip_mots_liens", array(
+                       'id_mot' => _request('id_mot'),
+                       'id_article' =>$flux['args']['id_objet'],
+                       'objet' =>'evenement'));
+       }
+}
+
+?>
\ No newline at end of file
diff --git a/www/plugins/seminaire/styles/calendrier-seminaire.css b/www/plugins/seminaire/styles/calendrier-seminaire.css
new file mode 100644 (file)
index 0000000..6e43cae
--- /dev/null
@@ -0,0 +1,102 @@
+.calendriermini .ui-datepicker-title {
+       margin: 0 1.8em !important;/*important parce qu'il faut surcharger un style inline*/
+       font-size:0.9em;
+}
+.menu-titre{
+       text-align:center;
+}
+#calendar .menu-titre{display:none;}
+.fc-button-content{
+       height:1.5em;
+       line-height:1.5em;
+       padding:0.2em;
+       font-size:0.9em;
+       }
+.fc-content{
+clear:none !important;
+}
+.plier_deplier {
+float: right;
+font-size: 0.9em;
+}
+.resume {
+cursor: help;
+}
+.date {
+color: #5d8ba2;
+}
+#seminaire ul {
+margin: 5px 0px 5px 0px;
+list-style-position: inside;
+list-style-type: none !important;
+list-style-image: none !important;
+}
+.evenements li.item {
+padding-left: 20px;
+}
+.evenements li.item:hover {
+background: 
+#edf3fe;
+-webkit-border-radius: 5px;
+-moz-border-radius: 5px;
+border-radius: 5px;
+}
+.bouton {
+background: 
+silver;
+color: 
+black;
+-webkit-box-shadow: 3px 3px 3px #888;
+-moz-box-shadow: 3px 3px 3px #888;
+box-shadow: 3px 3px 3px #888;
+padding: .3em .5em;
+margin-top: 10px;
+-moz-border-radius: 5px;
+-webkit-border-radius: 5px;
+border-radius: 5px;
+}
+.bouton:hover {
+background: 
+#B1C3D9;
+-webkit-box-shadow: 5px 5px 5px #888;
+-moz-box-shadow: 5px 5px 5px #888;
+box-shadow: 5px 5px 5px #888;
+}
+.bouton:active {
+background: 
+#808080;
+-webkit-box-shadow: 1px 1px 1px #888;
+-moz-box-shadow: 1px 1px 1px #888;
+box-shadow: 1px 1px 1px #888;
+}
+.bouton a {
+text-decoration: none;
+color: 
+#2E5B6B;
+}
+a.google {
+float: right;
+margin: 0 8px;
+}
+a.ical {
+float: right;
+width: 45px;
+text-decoration: none;
+text-align: center;
+margin-bottom: 20px;
+}
+a.ical img {
+border: 0;
+width: 100%;
+margin-bottom: -28px;
+}
+.espace {
+  height: 0.5em;
+  clear: both;
+}      
+#seminaire-article-boutons {
+  width: 100%;
+  overflow: hidden;
+  padding-top: 8px;
+  margin-top: 10px;
+}
diff --git a/www/plugins/seminaire/svn.revision b/www/plugins/seminaire/svn.revision
new file mode 100644 (file)
index 0000000..1d569ad
--- /dev/null
@@ -0,0 +1,10 @@
+<svn_revision>
+<text_version>
+Origine: file:///home/svn/repository/spip-zone/_plugins_/seminaire/branches/v2.1
+Revision: 85477
+Dernier commit: 2014-10-23 11:15:17 +0200 
+</text_version>
+<origine>file:///home/svn/repository/spip-zone/_plugins_/seminaire/branches/v2.1</origine>
+<revision>85477</revision>
+<commit>2014-10-23 11:15:17 +0200 </commit>
+</svn_revision>
\ No newline at end of file