From: Ludovic CHEVALIER Date: Mon, 9 Feb 2015 15:36:33 +0000 (+0100) Subject: [PLUGINS] +clavettes et dependances X-Git-Tag: production~25 X-Git-Url: http://git.cyclocoop.org/?p=lhc%2Fweb%2Fclavette_www.git;a=commitdiff_plain;h=ac14f39a91006baf83ec2c1ab8adfd0d71cbb78e [PLUGINS] +clavettes et dependances --- diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b71ef4e --- /dev/null +++ b/.gitmodules @@ -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 index 0000000..0fb08c7 --- /dev/null +++ b/www/plugins/agenda_3_5/action/activer_agenda_rubrique.php @@ -0,0 +1,33 @@ +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 index 0000000..d192815 --- /dev/null +++ b/www/plugins/agenda_3_5/action/editer_evenement.php @@ -0,0 +1,383 @@ +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 index 0000000..dc0b8c9 --- /dev/null +++ b/www/plugins/agenda_3_5/action/supprimer_evenement.php @@ -0,0 +1,27 @@ +$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 index 0000000..fad3ec0 --- /dev/null +++ b/www/plugins/agenda_3_5/action/supprimer_evenement_participant.php @@ -0,0 +1,30 @@ + \ 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 index 0000000..2e02f7c --- /dev/null +++ b/www/plugins/agenda_3_5/agenda_administrations.php @@ -0,0 +1,117 @@ +"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 index 0000000..5a944c9 --- /dev/null +++ b/www/plugins/agenda_3_5/agenda_autoriser.php @@ -0,0 +1,146 @@ + 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 index 0000000..2060d58 --- /dev/null +++ b/www/plugins/agenda_3_5/agenda_fonctions.php @@ -0,0 +1,197 @@ + 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 index 0000000..601a787 --- /dev/null +++ b/www/plugins/agenda_3_5/agenda_options.php @@ -0,0 +1,6 @@ +'; + } + } + 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 .= "" . _T('agenda:aucune_rubrique_mode_agenda') . "
"; + 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').'
'; + $statut="-non-32"; + } + if ($actif){ + $alt = _T('agenda:rubrique_mode_agenda').'
'; + $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 .= " | $voir"; + 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'],'')) + $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("&","&",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 index 0000000..0573b30 --- /dev/null +++ b/www/plugins/agenda_3_5/base/agenda_evenements.php @@ -0,0 +1,154 @@ + "BIGINT(21) NOT NULL AUTO_INCREMENT", + "id_evenement" => "BIGINT (21) DEFAULT '0' NOT NULL", + "id_auteur" => "BIGINT (21) DEFAULT '0' NOT NULL", + "nom" => "text NOT NULL DEFAULT ''", + "email" => "tinytext NOT NULL DEFAULT ''", + "date" => "datetime DEFAULT '0000-00-00 00:00:00' NOT NULL", + "reponse" => "char(3) default '?' NOT NULL", // oui, non, ? + ); + + $spip_evenements_participants_key = array( + "PRIMARY KEY" => "id_evenement_participant", + "KEY id_evenement" => "id_evenement", + "KEY id_auteur" => "id_auteur"); + + $tables_auxiliaires['spip_evenements_participants'] = array( + 'field' => &$spip_evenements_participants, + 'key' => &$spip_evenements_participants_key); + + return $tables_auxiliaires; +} + +/** + * Declarer la table objet evenement + * + * @param array $tables + * @return array + */ +function agenda_declarer_tables_objets_sql($tables){ + $tables['spip_evenements'] = array( + 'page'=>'evenement', + 'texte_retour' => 'icone_retour', + 'texte_objets' => 'agenda:info_evenements', + 'texte_objet' => 'agenda:info_evenement', + 'texte_modifier' => 'agenda:icone_modifier_evenement', + 'texte_creer' => 'agenda:titre_cadre_ajouter_evenement', + 'texte_logo_objet' => 'agenda:texte_logo_objet', + 'info_aucun_objet'=> 'agenda:info_aucun_evenement', + 'info_1_objet' => 'agenda:info_un_evenement', + 'info_nb_objets' => 'agenda:info_nombre_evenements', + 'titre' => 'titre, "" AS lang', + 'date' => 'date_debut', + 'principale' => 'oui', + 'champs_editables' => array('date_debut', 'date_fin', 'titre', 'descriptif','lieu', 'adresse', 'inscription', 'places', 'horaire'), + 'field'=> array( + "id_evenement" => "bigint(21) NOT NULL", + "id_article" => "bigint(21) DEFAULT '0' NOT NULL", + "date_debut" => "datetime DEFAULT '0000-00-00 00:00:00' NOT NULL", + "date_fin" => "datetime DEFAULT '0000-00-00 00:00:00' NOT NULL", + "titre" => "text NOT NULL DEFAULT ''", + "descriptif" => "text NOT NULL DEFAULT ''", + "lieu" => "text NOT NULL DEFAULT ''", + "adresse" => "text NOT NULL DEFAULT ''", + "inscription" => "tinyint(1) DEFAULT 0 NOT NULL", + "places" => "int(11) DEFAULT 0 NOT NULL", + "horaire" => "varchar(3) DEFAULT 'oui' NOT NULL", + "id_evenement_source" => "bigint(21) NOT NULL", + "statut" => "varchar(10) DEFAULT '0' NOT NULL", + "maj" => "TIMESTAMP", + "date_creation" => "datetime DEFAULT '0000-00-00 00:00:00' NOT NULL" + ), + 'key' => array( + "PRIMARY KEY" => "id_evenement", + "KEY date_debut" => "date_debut", + "KEY date_fin" => "date_fin", + "KEY id_article" => "id_article" + ), + 'join' => array( + "id_evenement"=>"id_evenement", + "id_article"=>"id_article" + ), + 'tables_jointures' => array( + 'articles', + 'evenements_participants', + ), + 'rechercher_champs' => array( + 'titre' => 8, 'descriptif' => 5, 'lieu' => 5, 'adresse' => 3 + ), + 'rechercher_jointures' => array( + 'document' => array('titre' => 2, 'descriptif' => 1) + ), + 'statut' => array( + array( + 'champ' => 'statut', + 'publie' => 'publie', + 'previsu' => '!', + 'exception' => array('statut','tout') + ), + ), + 'statut_titres' => array( + 'prop'=>'agenda:info_evenement_propose', + 'publie'=>'agenda:info_evenement_publie', + 'poubelle'=>'agenda:info_evenement_supprime' + ), + 'statut_textes_instituer' => array( + 'prop' => 'texte_statut_propose_evaluation', + 'publie' => 'texte_statut_publie', + 'poubelle' => 'texte_statut_poubelle', + ), + 'texte_changer_statut' => 'agenda:texte_evenement_statut', + 'champs_versionnes' => array('id_article', 'titre', 'descriptif', 'lieu', 'adresse', 'date_debut', 'date_fin', 'horaire'), + + ); + + //-- Jointures ---------------------------------------------------- + $tables['spip_articles']['tables_jointures'][] = 'evenements'; + $tables['spip_auteurs']['tables_jointures'][] = 'evenements_participants'; + $tables['spip_rubriques']['field']['agenda'] = 'tinyint(1) DEFAULT 0 NOT NULL'; + + return $tables; +} + + +?> \ 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 index 0000000..795e0c5 --- /dev/null +++ b/www/plugins/agenda_3_5/css/spip.agenda.css @@ -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 index 0000000..34e8c5b --- /dev/null +++ b/www/plugins/agenda_3_5/demo/agenda_calendrier_mini.html @@ -0,0 +1,173 @@ +#CACHE{86400} + + + + [(#TITRE|textebrut)][ - (#NOM_SITE_SPIP|textebrut)] + + [] + + [] + + + + + + + + + + + + + +
+ + [(#REM) Entete de la page + titre du site ] + + + [(#REM) Fil d'Ariane ] +
<:accueil_site:> > [(#TITRE|couper{80})][ > (#TITRE|couper{80})]
+ + +
+ + + [(#REM) Contenu principal : contenu de l'article ] + +
+ +[(#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} + +
+ #DEBUT_SURLIGNE + [(#LOGO_ARTICLE||image_reduire{200,200})] + [

(#SURTITRE)

] +

#TITRE

+ [

(#SOUSTITRE)

] + #FIN_SURLIGNE + +

[(#DATE|nom_jour) ][(#DATE|affdate)][, <:par_auteur:> (#LESAUTEURS)]

+ + [(#REM) Traductions de l'article ] + +
+

<:trad_article_traduction:>

+
    + [(#LANG|traduire_nom_langue)] + +
  • [(#LANG|traduire_nom_langue)]
  • + +
+
+
+
+ + + #DEBUT_SURLIGNE + [
(#CHAPO)
] +
+ [

<:voir_en_ligne:> : [(#NOM_SITE|sinon{[(#URL_SITE|couper{80})]})]

] + [
(#TEXTE|image_reduire{520,0})
] + #FIN_SURLIGNE + + [(#REM) Portfolio : album d'images ] + + + + +
+ #EMBED_DOCUMENT + [
(#TITRE)
] + [
(#DESCRIPTIF)
] +
+ + + [

<:info_ps:>

#DEBUT_SURLIGNE(#PS)#FIN_SURLIGNE
] + + [(#REM) Autres documents joints a l'article ] + +
+

<:titre_documents_joints:>

+ +
+
+ + [(#REM) Petition : + La petition ayant une PAGINATION il faut absolument lui passer SELF] + [ #REM Conserver cet invalideur invisible : (#PETITION|?{'',''}) ] + + + [

<:info_notes:>

#DEBUT_SURLIGNE(#NOTES)#FIN_SURLIGNE
] + + [(#REM) Forum de l'article ] + [

<:repondre_article:>

] + + + +
+ + + [(#REM) Menu de navigation laterale ] + + +
+ + [(#REM) Pied de page ] + + +
+ + + 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 index 0000000..b3b318b --- /dev/null +++ b/www/plugins/agenda_3_5/demo/exemple_navigation_jours.html @@ -0,0 +1,8 @@ + +[ +[(#VAL{Y-m-d}|date{#VAL{-1 day}|strtotime{#ENV{date}|affdate{U}}}|nom_jour)] ‹] + +[(#EVAL{' '}|concat{[(#ENV{date}|nom_jour) ],#ENV{date}|jour})] + +[›  [(#VAL{Y-m-d}|date{#VAL{+1 day}|strtotime{#ENV{date}|affdate{U}}}|nom_jour)]] \ 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 index 0000000..91769a1 --- /dev/null +++ b/www/plugins/agenda_3_5/demo/test/test_boucle_evenements.html @@ -0,0 +1,9 @@ + +#ID_EVENEMENT: + + + + +plop + + \ 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 index 0000000..8e994db --- /dev/null +++ b/www/plugins/agenda_3_5/demo/test/testagenda.html @@ -0,0 +1,36 @@ +

{id_mot}

+
    + +
  • #TITRE + +
      + +
    • #TITRE
    • + +
    +
    +
  • + +
+ +

{titre_mot}

+
    + +
  • #TITRE
  • + +
+ +

{branche}

+
    + +
  • #TITRE + +
      + +
    • #TITRE
    • + +
    +
    +
  • + +
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 index 0000000..7d61bbd --- /dev/null +++ b/www/plugins/agenda_3_5/formulaires/configurer_agenda.html @@ -0,0 +1,105 @@ +
+

<:configureragenda:titre_configuration:>

+ [

(#ENV**{message_ok})

] + [

(#ENV**{message_erreur})

] + +
+ [(#REM) déclarer les hidden qui déclencheront le service du formulaire + paramêtre : url d'action ] + #ACTION_FORMULAIRE{#ENV{action}} + #SET{fl,configureragenda} +
    + + #SET{name,titre}#SET{obli,''}[(#SET{defaut,<:agenda:titre_sur_l_agenda:>})]#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}} +
  • + [ + (#GET{erreurs}) + ] +
  • + #SET{name,descriptif}#SET{obli,''}#SET{defaut,''}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}} +
  • + [ + (#GET{erreurs}) + ] +
  • + #SET{name,url_evenement}#SET{obli,''}#SET{defaut,'evenement'}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}} +
  • + [ + (#GET{erreurs}) + ] + #SET{val,evenement} +
    + + +
    + #SET{val,article} +
    + + +
    +
  • + [(#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}}} +
  • [ + (#GET{erreurs}) + ] + #SET{val,1} +
    + + +
    +
  • + ] +
  • +
    + <:configureragenda:legend_presentation_agenda:> +
      + #SET{name,affichage_debut}#SET{obli,''}#SET{defaut,''}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}} +
    • + [ + (#GET{erreurs}) + ] +
    • + #SET{name,affichage_duree}#SET{obli,''}#SET{defaut,1}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}} +
    • + [ + (#GET{erreurs}) + ] +
    • +
    +
    +
  • +
+ + [(#REM) ajouter les saisies supplémentaires : extra et autre, à cet endroit ] + +

  + +

+
+
\ 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 index 0000000..0ef9273 --- /dev/null +++ b/www/plugins/agenda_3_5/formulaires/editer_evenement.html @@ -0,0 +1,113 @@ +
+ [

(#ENV**{message_ok})

] + [

(#ENV**{message_erreur})

] + [(#ENV{editable}) +
+ [(#REM) declarer les hidden qui declencheront le service du formulaire + parametre : url d'action ] + #ACTION_FORMULAIRE{#ENV{action}} + + +
    +
  • + [ + (#ENV**{erreurs}|table_valeur{titre}) + ] +
  • +
  • + [ + (#ENV**{erreurs}|table_valeur{id_parent}) + ] +
  • +
  • <:agenda:evenement_date:> +
      +
    • + [ + (#ENV**{erreurs}|table_valeur{horaire}) + ] +
    • +
    • + [ + (#ENV**{erreurs}|table_valeur{date_debut})][ + (#ENV**{erreurs}|table_valeur{date_fin}) + ] + + + + + + +
    • +
    +
  • +
  • + [ + (#ENV{repetitions}|non)<:agenda:ajouter_repetition:> +
    ][ + (#ENV**{erreurs}|table_valeur{repetitions}) + ]
    + [ + (#ENV{repetitions}|non)
    ] +
  • +
  • + [ + (#ENV**{erreurs}|table_valeur{descriptif}) + ] +
  • [ + (#ENV{affiche_inscription,oui}|=={oui}|oui) +
  • +
    + [(#ENV**{erreurs}|table_valeur{inscription})] + + +
    + [ + (#ENV**{erreurs}|table_valeur{places}) + ] +
    +
  • ] +
  • + [ + (#ENV**{erreurs}|table_valeur{lieu}) + ] +
  • +
  • + [ + (#ENV**{erreurs}|table_valeur{adresse}) + ] +
  • +
+ [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ] + +

+
+ #INCLURE{fond=formulaires/dateur/inc-dateur} + ] +
+ \ 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 index 0000000..a4776af --- /dev/null +++ b/www/plugins/agenda_3_5/formulaires/editer_evenement.php @@ -0,0 +1,141 @@ +'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 index 0000000..29f09e7 --- /dev/null +++ b/www/plugins/agenda_3_5/formulaires/migrer_agenda.html @@ -0,0 +1,102 @@ +
+

<:migreragenda:titre_migrer_agenda:>

+ [

(#ENV**{message_ok})

] + [

(#ENV**{message_erreur})

] + [(#ENV{editable}) +
+ [(#REM) déclarer les hidden qui déclencheront le service du formulaire + paramêtre : url d'action ] + #ACTION_FORMULAIRE{#ENV{action}} + #SET{fl,migreragenda} + +

<:migreragenda:explication_migration_agenda_article_1:>

+

<:migreragenda:explication_migration_agenda_article_2:>

+
    + [
  • + [ + (#ENV**{erreurs}|table_valeur{id_parent}) + ] + (#VAL|chercher_rubrique{0,#ENV{id_parent},'article',0,0,0,form_simple}) +
  • ] + #SET{name,toute_la_branche}#SET{obli,''}#SET{defaut,''}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}} +
  • [ + (#GET{erreurs}) + ] + #SET{val,oui} +
    + + +
    +
  • + + #SET{name,champ_date_debut}#SET{obli,''}#SET{defaut,'date'}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}} +
  • + [ + (#GET{erreurs}) + ] +
  • + #SET{name,champ_date_fin}#SET{obli,''}#SET{defaut,'date_redac'}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}} +
  • + [ + (#GET{erreurs}) + ] +
  • + + #SET{name,horaire}#SET{obli,''}#SET{defaut,'oui'}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}} +
  • + [ + (#GET{erreurs}) + ] + #SET{val,oui} +
    + + +
    + #SET{val,non} +
    + + +
    +
  • + + #SET{name,groupes_mots}#SET{obli,''}#SET{defaut,''}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}} +
  • + [ + (#GET{erreurs}) + ] + ] + + #SET{val,#ID_GROUPE} +
    + + +
    + + [(#ENV{editable}) +
  • +
+

<:migreragenda:explication_migration_agenda_article_fin:>

+ [(#REM) ajouter les saisies supplémentaires : extra et autre, à cet endroit ] + +

  +

+ + [
+ (#ENV**{erreurs}|table_valeur{confirmer}) +
+

  +

+ ] +
+ ] +
\ 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 index 0000000..1f79a57 --- /dev/null +++ b/www/plugins/agenda_3_5/formulaires/migrer_agenda.php @@ -0,0 +1,182 @@ +'', + '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 index 0000000..dc649cb --- /dev/null +++ b/www/plugins/agenda_3_5/formulaires/participer_evenement.html @@ -0,0 +1,51 @@ +
+ [(#ENV{editable}|non) + [

(#ENV**{message_ok})

] + [

(#ENV**{message_erreur})

]] + [(#ENV{editable}) +
+ [(#REM) declarer les hidden qui declencheront le service du formulaire + parametre : url d'action ] + #ACTION_FORMULAIRE{#ENV{action}} +
<:agenda:label_vous_inscrire:> + [

(#ENV*{message_ok})

] + [

(#ENV*{message_erreur})

] +
    + [(#SESSION{statut}|oui) + [
  • <:nom:> (#SESSION{nom}|typo) [<:icone_deconnecter:>]
  • ] + ] + [(#SESSION{statut}|non) +
  • + + [(#ENV**{erreurs}|table_valeur{nom})] + +
  • +
  • + + <:agenda:evenement_participant_email_mention:> + [(#ENV**{erreurs}|table_valeur{email})] + +
  • + ] +
  • + [(#ENV**{erreurs}|table_valeur{reponse})] +
    + + +
    +
    + + +
    +
    + + +
    +
  • +
+
+ [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ] + +

+
] +
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 index 0000000..8c53fab --- /dev/null +++ b/www/plugins/agenda_3_5/formulaires/participer_evenement.php @@ -0,0 +1,123 @@ + 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 index 0000000..84a6186 --- /dev/null +++ b/www/plugins/agenda_3_5/inc/agenda_filtres.php @@ -0,0 +1,197 @@ +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 = ""; + $dtend = ""; + $dtabbr = ""; + } + + $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 index 0000000..6531615 --- /dev/null +++ b/www/plugins/agenda_3_5/inc/date_gestion.php @@ -0,0 +1,46 @@ + \ 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 index 0000000..ac77161 --- /dev/null +++ b/www/plugins/agenda_3_5/inc/un-evenement-ical.html @@ -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:[(#TITRE|textebrut|filtrer_ical)] +URL:[(#URL_ARTICLE|parametre_url{id_evenement,#ID_EVENEMENT}|url_absolue|filtrer_ical)][ +SEQUENCE:(#ID_VERSION|moins{1}) +]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 index 0000000..a65661d --- /dev/null +++ b/www/plugins/agenda_3_5/inclure/agenda-vue-calendrier.html @@ -0,0 +1,82 @@ + + +#SET{data,#ARRAY} +[ +(#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}}}) +] + +[(#SET{json_data,#GET{data}|json_encode{2}})] + +
+ +#SET{français,0} +[(#LANG|is_null|oui)#SET{français,1}] +[(#LANG|=={fr}|oui)#SET{français,1}] + + 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 index 0000000..c9ddc28 --- /dev/null +++ b/www/plugins/agenda_3_5/inclure/liste_participants_evenement.html @@ -0,0 +1,31 @@ + +#SET{inscrits,#TOTAL_BOUCLE} + + + #ANCRE_PAGINATION + + [] + + + + + + + + + + + + + + + + +
(#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})]
<:nom:><:agenda:evenement_date_inscription:><:agenda:info_reponse_inscriptions:>
[(#NOM|sinon{#INFO_NOM{auteur,#ID_AUTEUR}})][(#DATE|affdate_jourcourt)] + [(#REPONSE|=={'oui'}|oui)<:agenda:label_reponse_jyparticipe:>] + [(#REPONSE|=={'non'}|oui)<:agenda:label_reponse_jyparticipe_pas:>] + [(#REPONSE|=={'?'}|oui)<:agenda:label_reponse_jyparticipe_peutetre:>] +
+ [

(#PAGINATION)

] +
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 index 0000000..36511a8 --- /dev/null +++ b/www/plugins/agenda_3_5/javascript/jquery-ui.multidatespicker.js @@ -0,0 +1,461 @@ +/* + * MultiDatesPicker v1.6.1 + * http://multidatespickr.sourceforge.net/ + * + * Copyright 2011, Luca Lauretta + * Dual licensed under the MIT or GPL version 2 licenses. + */ +(function( $ ){ + $.extend($.ui, { multiDatesPicker: { version: "1.6.1" } }); + + $.fn.multiDatesPicker = function(method) { + var mdp_arguments = arguments; + var ret = this; + var today_date = new Date(); + var day_zero = new Date(0); + var mdp_events = {}; + + function removeDate(date, type) { + if(!type) type = 'picked'; + date = dateConvert.call(this, date); + for(var i in this.multiDatesPicker.dates[type]) + if(!methods.compareDates(this.multiDatesPicker.dates[type][i], date)) + return this.multiDatesPicker.dates[type].splice(i, 1).pop(); + } + function removeIndex(index, type) { + if(!type) type = 'picked'; + return this.multiDatesPicker.dates[type].splice(index, 1).pop(); + } + function addDate(date, type, no_sort) { + if(!type) type = 'picked'; + date = dateConvert.call(this, date); + + // @todo: use jQuery UI datepicker method instead + date.setHours(0); + date.setMinutes(0); + date.setSeconds(0); + date.setMilliseconds(0); + + if (methods.gotDate.call(this, date, type) === false) { + this.multiDatesPicker.dates[type].push(date); + if(!no_sort) this.multiDatesPicker.dates[type].sort(methods.compareDates); + } + } + function sortDates(type) { + if(!type) type = 'picked'; + this.multiDatesPicker.dates[type].sort(methods.compareDates); + } + function dateConvert(date, desired_type, date_format) { + if(!desired_type) desired_type = 'object';/* + if(!date_format && (typeof date == 'string')) { + date_format = $(this).datepicker('option', 'dateFormat'); + if(!date_format) date_format = $.datepicker._defaults.dateFormat; + } + */ + return methods.dateConvert.call(this, date, desired_type, date_format); + } + + var methods = { + init : function( options ) { + var $this = $(this); + this.multiDatesPicker.changed = false; + + var mdp_events = { + beforeShow: function(input, inst) { + this.multiDatesPicker.changed = false; + if(this.multiDatesPicker.originalBeforeShow) + this.multiDatesPicker.originalBeforeShow.call(this, input, inst); + }, + onSelect : function(dateText, inst) { + var $this = $(this); + this.multiDatesPicker.changed = true; + + if (dateText) { + $this.multiDatesPicker('toggleDate', dateText); + } + + if (this.multiDatesPicker.mode == 'normal' && this.multiDatesPicker.dates.picked.length > 0 && this.multiDatesPicker.pickableRange) { + var min_date = this.multiDatesPicker.dates.picked[0], + max_date = new Date(min_date.getTime()); + + methods.sumDays(max_date, this.multiDatesPicker.pickableRange-1); + + // counts the number of disabled dates in the range + if(this.multiDatesPicker.adjustRangeToDisabled) { + var c_disabled, + disabled = this.multiDatesPicker.dates.disabled.slice(0); + do { + c_disabled = 0; + for(var i = 0; i < disabled.length; i++) { + if(disabled[i].getTime() <= max_date.getTime()) { + if((min_date.getTime() <= disabled[i].getTime()) && (disabled[i].getTime() <= max_date.getTime()) ) { + c_disabled++; + } + disabled.splice(i, 1); + i--; + } + } + max_date.setDate(max_date.getDate() + c_disabled); + } while(c_disabled != 0); + } + + if(this.multiDatesPicker.maxDate && (max_date > this.multiDatesPicker.maxDate)) + max_date = this.multiDatesPicker.maxDate; + + $this + .datepicker("option", "minDate", min_date) + .datepicker("option", "maxDate", max_date); + } else { + $this + .datepicker("option", "minDate", this.multiDatesPicker.minDate) + .datepicker("option", "maxDate", this.multiDatesPicker.maxDate); + } + + if(this.tagName == 'INPUT') { // for inputs + $this.val( + $this.multiDatesPicker('getDates', 'string') + ); + } + + if(this.multiDatesPicker.originalOnSelect && dateText) + this.multiDatesPicker.originalOnSelect.call(this, dateText, inst); + + // thanks to bibendus83 -> http://sourceforge.net/tracker/?func=detail&atid=1495384&aid=3403159&group_id=358205 + if ($this.datepicker('option', 'altField') != undefined && $this.datepicker('option', 'altField') != "") { + $($this.datepicker('option', 'altField')).val( + $this.multiDatesPicker('getDates', 'string') + ); + } + }, + beforeShowDay : function(date) { + var $this = $(this), + gotThisDate = $this.multiDatesPicker('gotDate', date) !== false, + isDisabledCalendar = $this.datepicker('option', 'disabled'), + isDisabledDate = $this.multiDatesPicker('gotDate', date, 'disabled') !== false, + areAllSelected = this.multiDatesPicker.maxPicks == this.multiDatesPicker.dates.picked.length; + + var custom = [true, '']; + if(this.multiDatesPicker.originalBeforeShowDay) + custom = this.multiDatesPicker.originalBeforeShowDay.call(this, date); + + var highlight_class = gotThisDate ? 'ui-state-highlight' : custom[1]; + var highlight_class = (gotThisDate ? 'ui-state-highlight' : '') + ((custom[1] && gotThisDate) ? ' ' : '') + custom[1]; + var selectable_date = !(isDisabledCalendar || isDisabledDate || (areAllSelected && !highlight_class)); + custom[0] = selectable_date && custom[0]; + custom[1] = highlight_class; + return custom; + }, + onClose: function(dateText, inst) { + if(this.tagName == 'INPUT' && this.multiDatesPicker.changed) { + $(inst.dpDiv[0]).stop(false,true); + setTimeout('$("#'+inst.id+'").datepicker("show")',50); + } + if(this.multiDatesPicker.originalOnClose) this.multiDatesPicker.originalOnClose.call(this, dateText, inst); + } + }; + + if(options) { + this.multiDatesPicker.originalBeforeShow = options.beforeShow; + this.multiDatesPicker.originalOnSelect = options.onSelect; + this.multiDatesPicker.originalBeforeShowDay = options.beforeShowDay; + this.multiDatesPicker.originalOnClose = options.onClose; + + $this.datepicker(options); + + this.multiDatesPicker.minDate = $.datepicker._determineDate(this, options.minDate, null); + this.multiDatesPicker.maxDate = $.datepicker._determineDate(this, options.maxDate, null); + + if(options.addDates) methods.addDates.call(this, options.addDates); + if(options.addDisabledDates) + methods.addDates.call(this, options.addDisabledDates, 'disabled'); + + methods.setMode.call(this, options); + } else { + $this.datepicker(); + } + + $this.datepicker('option', mdp_events); + + if(this.tagName == 'INPUT') $this.val($this.multiDatesPicker('getDates', 'string')); + + // Fixes the altField filled with defaultDate by default + var altFieldOption = $this.datepicker('option', 'altField'); + if (altFieldOption) $(altFieldOption).val($this.multiDatesPicker('getDates', 'string')); + }, + compareDates : function(date1, date2) { + date1 = dateConvert.call(this, date1); + date2 = dateConvert.call(this, date2); + // return > 0 means date1 is later than date2 + // return == 0 means date1 is the same day as date2 + // return < 0 means date1 is earlier than date2 + var diff = date1.getFullYear() - date2.getFullYear(); + if(!diff) { + diff = date1.getMonth() - date2.getMonth(); + if(!diff) + diff = date1.getDate() - date2.getDate(); + } + return diff; + }, + sumDays : function( date, n_days ) { + var origDateType = typeof date; + obj_date = dateConvert.call(this, date); + obj_date.setDate(obj_date.getDate() + n_days); + return dateConvert.call(this, obj_date, origDateType); + }, + dateConvert : function( date, desired_format, dateFormat ) { + var from_format = typeof date; + + if(from_format == desired_format) { + if(from_format == 'object') { + try { + date.getTime(); + } catch (e) { + $.error('Received date is in a non supported format!'); + return false; + } + } + return date; + } + + var $this = $(this); + if(typeof date == 'undefined') date = new Date(0); + + if(desired_format != 'string' && desired_format != 'object' && desired_format != 'number') + $.error('Date format "'+ desired_format +'" not supported!'); + + if(!dateFormat) { + dateFormat = $.datepicker._defaults.dateFormat; + + // thanks to bibendus83 -> http://sourceforge.net/tracker/index.php?func=detail&aid=3213174&group_id=358205&atid=1495382 + var dp_dateFormat = $this.datepicker('option', 'dateFormat'); + if (dp_dateFormat) { + dateFormat = dp_dateFormat; + } + } + + // converts to object as a neutral format + switch(from_format) { + case 'object': break; + case 'string': date = $.datepicker.parseDate(dateFormat, date); break; + case 'number': date = new Date(date); break; + default: $.error('Conversion from "'+ desired_format +'" format not allowed on jQuery.multiDatesPicker'); + } + // then converts to the desired format + switch(desired_format) { + case 'object': return date; + case 'string': return $.datepicker.formatDate(dateFormat, date); + case 'number': return date.getTime(); + default: $.error('Conversion to "'+ desired_format +'" format not allowed on jQuery.multiDatesPicker'); + } + return false; + }, + gotDate : function( date, type ) { + if(!type) type = 'picked'; + for(var i = 0; i < this.multiDatesPicker.dates[type].length; i++) { + if(methods.compareDates.call(this, this.multiDatesPicker.dates[type][i], date) === 0) { + return i; + } + } + return false; + }, + getDates : function( format, type ) { + if(!format) format = 'string'; + if(!type) type = 'picked'; + switch (format) { + case 'object': + return this.multiDatesPicker.dates[type]; + case 'string': + case 'number': + var o_dates = new Array(); + for(var i in this.multiDatesPicker.dates[type]) + o_dates.push( + dateConvert.call( + this, + this.multiDatesPicker.dates[type][i], + format + ) + ); + return o_dates; + + default: $.error('Format "'+format+'" not supported!'); + } + }, + addDates : function( dates, type ) { + if(dates.length > 0) { + if(!type) type = 'picked'; + switch(typeof dates) { + case 'object': + case 'array': + if(dates.length) { + for(var i in dates) + if (typeof dates[i] != "function") + addDate.call(this, dates[i], type, true); + sortDates.call(this, type); + break; + } // else does the same as 'string' + case 'string': + case 'number': + addDate.call(this, dates, type); + break; + default: + $.error('Date format "'+ typeof dates +'" not allowed on jQuery.multiDatesPicker'); + } + $(this).datepicker('refresh'); + } else { + $.error('Empty array of dates received.'); + } + }, + removeDates : function( dates, type ) { + if(!type) type = 'picked'; + var removed = []; + if (Object.prototype.toString.call(dates) === '[object Array]') { + for(var i in dates.sort(function(a,b){return b-a})) { + removed.push(removeDate.call(this, dates[i], type)); + } + } else { + removed.push(removeDate.call(this, dates, type)); + } + $(this).datepicker('refresh'); + return removed; + }, + removeIndexes : function( indexes, type ) { + if(!type) type = 'picked'; + var removed = []; + if (Object.prototype.toString.call(indexes) === '[object Array]') { + for(var i in indexes.sort(function(a,b){return b-a})) { + removed.push(removeIndex.call(this, indexes[i], type)); + } + } else { + removed.push(removeIndex.call(this, indexes, type)); + } + $(this).datepicker('refresh'); + return removed; + }, + resetDates : function ( type ) { + if(!type) type = 'picked'; + this.multiDatesPicker.dates[type] = []; + $(this).datepicker('refresh'); + }, + toggleDate : function( date, type ) { + if(!type) type = 'picked'; + + switch(this.multiDatesPicker.mode) { + case 'daysRange': + this.multiDatesPicker.dates[type] = []; // deletes all picked/disabled dates + var end = this.multiDatesPicker.autoselectRange[1]; + var begin = this.multiDatesPicker.autoselectRange[0]; + if(end < begin) { // switch + end = this.multiDatesPicker.autoselectRange[0]; + begin = this.multiDatesPicker.autoselectRange[1]; + } + for(var i = begin; i < end; i++) + methods.addDates.call(this, methods.sumDays(date, i), type); + break; + default: + if(methods.gotDate.call(this, date) === false) // adds dates + methods.addDates.call(this, date, type); + else // removes dates + methods.removeDates.call(this, date, type); + break; + } + }, + setMode : function( options ) { + var $this = $(this); + if(options.mode) this.multiDatesPicker.mode = options.mode; + + switch(this.multiDatesPicker.mode) { + case 'normal': + for(option in options) + switch(option) { + case 'maxPicks': + case 'minPicks': + case 'pickableRange': + case 'adjustRangeToDisabled': + this.multiDatesPicker[option] = options[option]; + break; + //default: $.error('Option ' + option + ' ignored for mode "'.options.mode.'".'); + } + break; + case 'daysRange': + case 'weeksRange': + var mandatory = 1; + for(option in options) + switch(option) { + case 'autoselectRange': + mandatory--; + case 'pickableRange': + case 'adjustRangeToDisabled': + this.multiDatesPicker[option] = options[option]; + break; + //default: $.error('Option ' + option + ' does not exist for setMode on jQuery.multiDatesPicker'); + } + if(mandatory > 0) $.error('Some mandatory options not specified!'); + break; + } + + /* + if(options.pickableRange) { + $this.datepicker("option", "maxDate", options.pickableRange); + $this.datepicker("option", "minDate", this.multiDatesPicker.minDate); + } + */ + + if(mdp_events.onSelect) + mdp_events.onSelect(); + $this.datepicker('refresh'); + } + }; + + this.each(function() { + if (!this.multiDatesPicker) { + this.multiDatesPicker = { + dates: { + picked: [], + disabled: [] + }, + mode: 'normal', + adjustRangeToDisabled: true + }; + } + + if(methods[method]) { + var exec_result = methods[method].apply(this, Array.prototype.slice.call(mdp_arguments, 1)); + switch(method) { + case 'getDates': + case 'removeDates': + case 'gotDate': + case 'sumDays': + case 'compareDates': + case 'dateConvert': + ret = exec_result; + } + return exec_result; + } else if( typeof method === 'object' || ! method ) { + return methods.init.apply(this, mdp_arguments); + } else { + $.error('Method ' + method + ' does not exist on jQuery.multiDatesPicker'); + } + return false; + }); + + if(method != 'gotDate' && method != 'getDates') { + aaaa = 1; + } + + return ret; + }; + + var PROP_NAME = 'multiDatesPicker'; + var dpuuid = new Date().getTime(); + var instActive; + + $.multiDatesPicker = {version: false}; + //$.multiDatesPicker = new MultiDatesPicker(); // singleton instance + $.multiDatesPicker.initialized = false; + $.multiDatesPicker.uuid = new Date().getTime(); + $.multiDatesPicker.version = $.ui.multiDatesPicker.version; + + // Workaround for #4055 + // Add another global to avoid noConflict issues with inline event handlers + window['DP_jQuery_' + dpuuid] = $; +})( 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 index 0000000..72dfd45 --- /dev/null +++ b/www/plugins/agenda_3_5/lang/agenda.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + 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 index 0000000..d5ba3d4 --- /dev/null +++ b/www/plugins/agenda_3_5/lang/agenda_de.php @@ -0,0 +1,136 @@ + '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 index 0000000..747b657 --- /dev/null +++ b/www/plugins/agenda_3_5/lang/agenda_en.php @@ -0,0 +1,166 @@ + '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' => '@mois@ @jour@ @annee@', + + // 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 index 0000000..36d6360 --- /dev/null +++ b/www/plugins/agenda_3_5/lang/agenda_es.php @@ -0,0 +1,166 @@ + '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' => '@jour@ @mois@ @annee@', + + // 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 index 0000000..8292a7d --- /dev/null +++ b/www/plugins/agenda_3_5/lang/agenda_fr.php @@ -0,0 +1,164 @@ + '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' => '@jour@ @mois@ @annee@', + + // 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 index 0000000..4c8b266 --- /dev/null +++ b/www/plugins/agenda_3_5/lang/agenda_nl.php @@ -0,0 +1,166 @@ + '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 hier inloggen om je te kunnen inschrijven.', + 'creer_evenement' => 'Een gebeurtenis maken', + + // D + 'date_fmt_agenda_label' => '@jour@ @mois@ @annee@', + + // 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' => '
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 index 0000000..3c815b3 --- /dev/null +++ b/www/plugins/agenda_3_5/lang/agenda_sk.php @@ -0,0 +1,166 @@ + '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' => '@jour@ @mois@ @annee@', + + // 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 index 0000000..33b685f --- /dev/null +++ b/www/plugins/agenda_3_5/lang/configureragenda.xml @@ -0,0 +1,4 @@ + + + + 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 index 0000000..b8d18be --- /dev/null +++ b/www/plugins/agenda_3_5/lang/configureragenda_fr.php @@ -0,0 +1,31 @@ + '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 index 0000000..efd7d32 --- /dev/null +++ b/www/plugins/agenda_3_5/lang/migreragenda.xml @@ -0,0 +1,4 @@ + + + + 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 index 0000000..0c2283f --- /dev/null +++ b/www/plugins/agenda_3_5/lang/migreragenda_fr.php @@ -0,0 +1,40 @@ + '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 index 0000000..984c57c --- /dev/null +++ b/www/plugins/agenda_3_5/lang/paquet-agenda.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + 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 index 0000000..d3e9409 --- /dev/null +++ b/www/plugins/agenda_3_5/lang/paquet-agenda_en.php @@ -0,0 +1,16 @@ + '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 index 0000000..340def3 --- /dev/null +++ b/www/plugins/agenda_3_5/lang/paquet-agenda_es.php @@ -0,0 +1,16 @@ + '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 index 0000000..f6fba01 --- /dev/null +++ b/www/plugins/agenda_3_5/lang/paquet-agenda_fr.php @@ -0,0 +1,14 @@ + '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 index 0000000..f21f2f3 --- /dev/null +++ b/www/plugins/agenda_3_5/lang/paquet-agenda_nl.php @@ -0,0 +1,16 @@ + '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 index 0000000..20fa3df --- /dev/null +++ b/www/plugins/agenda_3_5/lang/paquet-agenda_sk.php @@ -0,0 +1,16 @@ + '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 index 0000000..f2b1b69 --- /dev/null +++ b/www/plugins/agenda_3_5/lang/paquet-albums.xml @@ -0,0 +1,2 @@ + + 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 index 0000000..28aa078 --- /dev/null +++ b/www/plugins/agenda_3_5/modeles/evenement_vevent.html @@ -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 index 0000000..26c68f0 --- /dev/null +++ b/www/plugins/agenda_3_5/paquet.xml @@ -0,0 +1,51 @@ + + + Agenda + + + Cedric MORIN + b_b + Tetue + Julien Tessier + + 2006-2014 + + GPL 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 index 0000000..28fa90a --- /dev/null +++ b/www/plugins/agenda_3_5/prive/objets/contenu/article-evenements.html @@ -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] +
+ + [(#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})] + ] +
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 index 0000000..780d67b --- /dev/null +++ b/www/plugins/agenda_3_5/prive/objets/contenu/evenement.html @@ -0,0 +1,46 @@ + +
+
<:info_titre:>
+
#TITRE
+
+
+
<:agenda:info_dates:>
+
[(#DATE_DEBUT|affdate_debut_fin{#DATE_FIN,#HORAIRE})]
+
+ +
+
[(#GRAND_TOTAL|singulier_ou_pluriel{agenda:une_repetition,agenda:nb_repetitions})]
+
([(#DATE_DEBUT|affdate_court)])
+
+
+
+
<:info_descriptif:>
+
[(#DESCRIPTIF|image_reduire{500,0})]
+
+
+
<:agenda:info_lieu:>
+
[(#LIEU|image_reduire{500,0})]
+
+
+
<:agenda:evenement_adresse:>
+
[(#ADRESSE|image_reduire{500,0})]
+
+
+
<:agenda:info_inscription:>
+#SET{rep_oui,#TOTAL_BOUCLE} +#SET{rep_non,#TOTAL_BOUCLE} +#SET{rep_nsp,#TOTAL_BOUCLE} +[
(#PLACES|singulier_ou_pluriel{agenda:info_1_place,agenda:info_nb_places}) + (<: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:>) + +
] + +
+[
+
<:info_notes:>
+
(#NOTES)
+
+] \ 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 index 0000000..2304c66 --- /dev/null +++ b/www/plugins/agenda_3_5/prive/objets/infos/evenement.html @@ -0,0 +1,33 @@ + +
+#SET{texte_objet,#VAL{evenement}|objet_info{texte_objet}|_T} +
<:titre_cadre_numero_objet{objet=#GET{texte_objet}}:>

#ID_EVENEMENT

+ +[(#FORMULAIRE_INSTITUER_OBJET{evenement,#ID_EVENEMENT})] + +[(#REM) + + Bouton voir en ligne + +] + + + + + + [(#VAL{redirect} + |generer_url_action{type=evenement&id=#ID_EVENEMENT} + |parametre_url{var_mode,calcul} + |icone_horizontale{<:icone_voir_en_ligne:>,racine})] + + [(#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})] + ] + +
+ 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 index 0000000..1b858cd --- /dev/null +++ b/www/plugins/agenda_3_5/prive/objets/liste/evenement_participants.html @@ -0,0 +1,46 @@ + +#ANCRE_PAGINATION +
+ +[] + + + + + + + [ + (#AUTORISER{modifier,evenement,#ID_EVENEMENT})] + + + + + + + + + + [ + (#AUTORISER{modifier,evenement,#ID_EVENEMENT})] + + + +
(#ENV*{titre,#GRAND_TOTAL|singulier_ou_pluriel{agenda:info_un_inscrit,agenda:info_nb_inscrits,nb}})
[(#TRI{statut,#,ajax})][(#TRI{nom,<:nom:>,ajax})][(#TRI{email,<:email:>,ajax})][(#TRI{date,<:agenda:evenement_date_inscription:>,ajax})][(#TRI{reponse,<:agenda:info_reponse_inscriptions:>,ajax})]Action
[(#STATUT|puce_statut{auteur,#ID_AUTEUR})] + [(#NOM|sinon{ + [(#RANG). ][(#INFO_NOM{auteur,#ID_AUTEUR}|sinon{<:texte_vide:>})] + })] + [(#EMAIL|sinon{#INFO_EMAIL{auteur,#ID_AUTEUR}})][(#DATE|affdate_jourcourt)]#REPONSE + [(#BOUTON_ACTION{<:agenda:lien_desinscrire:>, [(#URL_ACTION_AUTEUR{supprimer_evenement_participant,#ID_EVENEMENT-#ID_EVENEMENT_PARTICIPANT,#SELF})], ajax, <:agenda:confirm_suppression_inscription:>})] +
+[

(#PAGINATION{prive})

] +
+[(#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:>})] +] + +
+

<:agenda:aucun_inscrit:>

+ + + + 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 index 0000000..0197a75 --- /dev/null +++ b/www/plugins/agenda_3_5/prive/objets/liste/evenements-post.html @@ -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}} + +[(#REM) Si un evenement passe, on commence par le jour de cet evenement] + #SET{id_evenement,#ID_EVENEMENT} + + + #SET{id_evenement,#ID_EVENEMENT} + + +[(#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 index 0000000..42dc489 --- /dev/null +++ b/www/plugins/agenda_3_5/prive/objets/liste/evenements.html @@ -0,0 +1,49 @@ +[(#SET{defaut_tri,#ARRAY{ + date_debut,#ENV{date_sens,1}, + titre,1, + id_evenement,1, + points,-1 +}}) +] +#ANCRE_PAGINATION +
+ +[] + + + + + + + + + + + + + + + + + + + #SET{id_evenement,#ID_EVENEMENT_SOURCE|?{#ID_EVENEMENT_SOURCE,#ID_EVENEMENT}} + + + + +
(#ENV*{titre,#GRAND_TOTAL|singulier_ou_pluriel{agenda:info_un_evenement,agenda:info_nombre_evenements}})
[(#TRI{statut,#,ajax})][(#TRI{date_debut,<:date:>,ajax})][(#TRI{titre,<:info_titre:>,ajax})]<:agenda:evenement_lieu:>[(#TRI{id_evenement,<:info_numero_abbreviation:>,ajax})]
[(#STATUT|puce_statut{evenement,#ID_EVENEMENT})][(#DATE_DEBUT|affdate_jourcourt|unique{liste_evt})][[(#CHEMIN_IMAGE{article-16.png}|balise_img{#INFO_TITRE{article,#ID_ARTICLE}})]][(#RANG). ]#TITRE +

[(#DATE_DEBUT|affdate_debut_fin{#DATE_FIN,#HORAIRE})]

+
#LIEU[(#ID_EVENEMENT_SOURCE|oui) + [(#CHEMIN_IMAGE{repetition-16.png}|balise_img{<:agenda:repetition:>,''}|inserer_attribut{title,<:agenda:repetition:>})] + ][(#AUTORISER{modifier,evenement,#GET{id_evenement}}|?{[ + (#GET{id_evenement})], + [(#GET{id_evenement})] + })] +
+[

(#PAGINATION{prive})

] +
+
[ +
(#ENV*{sinon,''})
+] 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 index 0000000..c9b7c0d --- /dev/null +++ b/www/plugins/agenda_3_5/prive/squelettes/contenu/agenda_inscriptions.html @@ -0,0 +1,14 @@ +#BOITE_OUVRIR + +

<:agenda:liste_inscrits:>

+

#TITRE

+
[(#DATE_DEBUT|affdate_debut_fin{#DATE_FIN,#HORAIRE})]
+[
<:agenda:evenement_descriptif:> : (#DESCRIPTIF|PtoBR)
] +[
<:agenda:evenement_lieu:> : (#LIEU)
] + + + +

<:agenda:aucun_evenement:>

+ +#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 index 0000000..d47dd30 --- /dev/null +++ b/www/plugins/agenda_3_5/prive/squelettes/contenu/configurer_agenda.html @@ -0,0 +1,9 @@ +[(#AUTORISER{configurer_agenda}|sinon_interdire_acces)] +

<:agenda:agenda:>

+[(#BOITE_OUVRIR{[(#VAL{agenda:explication_synchro_flux_ical_titre}|_T)],'info'})] +<:agenda:explication_synchro_flux_ical:> +#BOITE_FERMER +
#FORMULAIRE_CONFIGURER_AGENDA
+
#FORMULAIRE_CONFIGURER_CALENDRIERMINI
+

<:agenda:rubriques:>

+
#FORMULAIRE_MIGRER_AGENDA
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 index 0000000..48e97a3 --- /dev/null +++ b/www/plugins/agenda_3_5/prive/squelettes/contenu/evenement_edit.html @@ -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}}}}} +
+
+ [(#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:>})] +

(#ENV{titre,#INFO_TITRE{evenement,#ID_EVENEMENT}|sinon{<:agenda:info_nouvel_evenement:>}})

] +
+ +#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();'} +
+ +] + #FORMULAIRE_EDITER_EVENEMENT{#ENV{id_evenement,oui},#ENV{id_article},#GET{redirect},#ENV{associer_objet}} +[(#ENV{retourajax,''}|oui) +
+ +] +[(#ENV{id_evenement,''}|intval|oui) +
+ #FORMULAIRE_EDITER_LIENS{mots,evenement,#ENV{id_evenement}} +
+] + +
+ 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 index 0000000..9584d51 --- /dev/null +++ b/www/plugins/agenda_3_5/prive/squelettes/contenu/evenements.html @@ -0,0 +1,16 @@ +[(#AUTORISER{menu,_evenements}|sinon_interdire_acces)] +

<:agenda:agenda:>

+ +#BOITE_OUVRIR{'','note'} + [(#CHEMIN_IMAGE{fermer-16.png}|balise_img|inserer_attribut{alt,<:info_tout_afficher:>})] + <:agenda:rubrique_liste_evenements_de:> +

#TITRE

+#BOITE_FERMER + + +
+ [(#INCLURE{fond=prive/objets/liste/evenements-post}{env})] +
+[(#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 index 0000000..d3fff83 --- /dev/null +++ b/www/plugins/agenda_3_5/prive/squelettes/extra/agenda_inscriptions.html @@ -0,0 +1,23 @@ +[(#BOITE_OUVRIR{[(#CHEMIN_IMAGE{calendrier-24.png}|balise_img{'',cadre-icone})],'info'})] +

<:agenda:info_inscription:>

+#SET{rep_oui,#TOTAL_BOUCLE} +#SET{rep_non,#TOTAL_BOUCLE} +#SET{rep_nsp,#TOTAL_BOUCLE} + +[
(#PLACES|singulier_ou_pluriel{agenda:info_1_place,agenda:info_nb_places})
] + +
<: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:>
  • +
+#SET{args,#ARRAY{id_evenement,#ID_EVENEMENT}} +

<:agenda:telecharger:>

+ +#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 index 0000000..1a986b0 --- /dev/null +++ b/www/plugins/agenda_3_5/prive/squelettes/inclure/agenda-navigation-mois.html @@ -0,0 +1,12 @@ +#SET{self,#ENV{self}|parametre_url{debut_mois,#EVAL{_request('debut_mois')}}} + +[(#REM) navigation par mois] + +[

(#PAGINATION{prive})

] +
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 index 0000000..0371454 --- /dev/null +++ b/www/plugins/agenda_3_5/prive/squelettes/inclure/agenda-rubriques.html @@ -0,0 +1,13 @@ +#SET{self,#SELF|parametre_url{debut_rubriques,#EVAL{_request('debut_rubriques')}}} + +[(#BOITE_OUVRIR{[<:agenda:rubriques:>(#CHEMIN_IMAGE{rubrique-24.png}|balise_img{'',cadre-icone})],simple})] +[(#REM) navigation par rubriques agenda] +[

(#PAGINATION{page})

] +
    +
  • [(#GET{self}|parametre_url{id_rubrique,''}|lien_ou_expose{<:agenda:toutes_rubriques:>,#ENV{id_rubrique,0}|=={0}})]
  • + +
  • [(#GET{self}|parametre_url{id_rubrique,#ID_RUBRIQUE}|lien_ou_expose{#TITRE|supprimer_numero{},#ENV{id_rubrique}|=={#ID_RUBRIQUE}})] (<:info_numero_abbreviation:>#ID_RUBRIQUE)
  • + +
+#BOITE_FERMER +
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 index 0000000..ac724bc --- /dev/null +++ b/www/plugins/agenda_3_5/prive/squelettes/navigation/agenda_inscriptions.html @@ -0,0 +1,5 @@ + +[(#BOITE_OUVRIR{[(#CHEMIN_IMAGE{calendrier-24.png}|balise_img{'',cadre-icone})]})] +

<:agenda:retour_evenement:>

+#BOITE_FERMER + 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 index 0000000..0c3d615 --- /dev/null +++ b/www/plugins/agenda_3_5/prive/squelettes/navigation/evenement.html @@ -0,0 +1,14 @@ + +#BOITE_OUVRIR{'','info'} +#PIPELINE{boite_infos,#ARRAY{data,'',args,#ARRAY{'type','evenement','id',#ENV{id_evenement}}}} +#BOITE_FERMER + +
+#FORMULAIRE_EDITER_LOGO{evenement,#ID_EVENEMENT,'',#ENV**} +
+ +#PIPELINE{afficher_config_objet,#ARRAY{args,#ARRAY{type,evenement,id,#ID_EVENEMENT},data,''}} + + +[(#ENV{exec}|=={evenement_edit}|oui)] + 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 index 0000000..a2c1eb3 --- /dev/null +++ b/www/plugins/agenda_3_5/prive/squelettes/navigation/evenements.html @@ -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')}}} + +[(#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}})] + \ 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 index 0000000..7d0c867 --- /dev/null +++ b/www/plugins/agenda_3_5/prive/style_prive_plugin_agenda.html @@ -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 ! + 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 index 0000000..fa4cc28 --- /dev/null +++ b/www/plugins/calendrier_mini-2.0/formulaires/configurer_calendriermini.html @@ -0,0 +1,79 @@ +
+

<:minical:config_titre_calendriermini:>

+ +[

(#ENV*{message_ok})

] +[

(#ENV*{message_erreur})

] + +
+ #ACTION_FORMULAIRE{#ENV{action}} +
    + #SET{fl,minical} + #SET{name,jour1}#SET{obli,''}#SET{defaut,1}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}} +
  • + [ + (#GET{erreurs}) + ] +
  • + #SET{name,format_jour}#SET{defaut,initiale}#SET{obli,''}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}} +
  • + [ + (#GET{erreurs}) + ] + #SET{val,initiale} +
    + + +
    + #SET{val,abbr} +
    + + +
    +
  • + #SET{name,affichage_hors_mois}#SET{defaut,1}#SET{obli,''}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}} +
  • + [ + (#GET{erreurs}) + ] + #SET{val,1} +
    + + +
    + #SET{val,0} +
    + + +
    +
  • + #SET{name,changement_rapide}#SET{defaut,1}#SET{obli,''}#SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}} +
  • + [ + (#GET{erreurs}) + ] + #SET{val,1} +
    + + +
    + #SET{val,0} +
    + + +
    +
  • + + +
+ +

  +

+
+
\ 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 index 0000000..6393793 --- /dev/null +++ b/www/plugins/calendrier_mini-2.0/javascript/calendrier_mini.js.html @@ -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)'; + 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 index 0000000..3de901d --- /dev/null +++ b/www/plugins/couleur_rubrique/prive/squelettes/contenu/configurer_pb_couleur_rubrique.html @@ -0,0 +1,6 @@ +[(#AUTORISER{configurer,pb_couleur_rubrique}|oui) +

<:pb_couleur_rubrique:configurer_couleur_de_rubrique:>

+
+ #FORMULAIRE_CONFIGURER_PB_COULEUR_RUBRIQUE +
+] \ 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 index 0000000..de83c29 --- /dev/null +++ b/www/plugins/couleur_rubrique/svn.revision @@ -0,0 +1,10 @@ + + +Origine: file:///home/svn/repository/spip-zone/_plugins_/couleur_rubrique/trunk +Revision: 86141 +Dernier commit: 2014-11-18 00:15:32 +0100 + +file:///home/svn/repository/spip-zone/_plugins_/couleur_rubrique/trunk +86141 +2014-11-18 00:15:32 +0100 + \ 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 index 0000000..b1495c4 --- /dev/null +++ b/www/plugins/gis/action/editer_gis.php @@ -0,0 +1,229 @@ + 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 index 0000000..f5633d3 --- /dev/null +++ b/www/plugins/gis/action/editer_lien_gis.php @@ -0,0 +1,21 @@ + $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 index 0000000..ec300dc --- /dev/null +++ b/www/plugins/gis/action/gis_geocoder_rechercher.php @@ -0,0 +1,28 @@ + $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 index 0000000..6c838f2 --- /dev/null +++ b/www/plugins/gis/action/supprimer_gis.php @@ -0,0 +1,19 @@ + \ 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 index 0000000..345c14f --- /dev/null +++ b/www/plugins/gis/base/gis.php @@ -0,0 +1,135 @@ + '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 index 0000000..ab9e955 --- /dev/null +++ b/www/plugins/gis/crud/gis.php @@ -0,0 +1,62 @@ +'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 index 0000000..3fe9d5a --- /dev/null +++ b/www/plugins/gis/embed/kml.html @@ -0,0 +1,38 @@ + + + + + [(#TITRE|sinon{#FICHIER|basename})] - #NOM_SITE_SPIP + + + + [] + + [(#VAL{''}|gis_insert_head_css)] + [] + [] + [] + [(#VAL{''}|gis_insert_head)] + + +
+ [] +

[(#TITRE|sinon{#FICHIER|basename})]

+ +
+ #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} +
+ + +
+ #MODELE{carte_gis,largeur=100%,hauteur=#ENV{hauteur,400px},kml=#ID_DOCUMENT,point=non,id_carte_gis=kml#ID_DOCUMENT} +
+ +
+ + + + +#INCLURE{fond=embed/document,env} + +#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 index 0000000..7ad44a3 --- /dev/null +++ b/www/plugins/gis/embed/kml_fonctions.php @@ -0,0 +1,12 @@ + diff --git a/www/plugins/gis/formulaires/configurer_gis.html b/www/plugins/gis/formulaires/configurer_gis.html new file mode 100755 index 0000000..5ebe968 --- /dev/null +++ b/www/plugins/gis/formulaires/configurer_gis.html @@ -0,0 +1,196 @@ +
+ +[

(#ENV*{message_ok})

] +[

(#ENV*{message_erreur})

] + +
+ +
+ #ACTION_FORMULAIRE{#ENV{action}} +
    +
  • + + + <:info_rechercher:> +
  • + [(#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} + + #SET{layers,#GET{layers}|array_merge{#ARRAY{#CLE,#VALEUR|table_valeur{nom}}}} + + + [(#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})] + +
+ + +

+ +

+
+ + +
diff --git a/www/plugins/gis/formulaires/configurer_gis.php b/www/plugins/gis/formulaires/configurer_gis.php new file mode 100644 index 0000000..40bdc3b --- /dev/null +++ b/www/plugins/gis/formulaires/configurer_gis.php @@ -0,0 +1,33 @@ + 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 index 0000000..2cf65c9 --- /dev/null +++ b/www/plugins/gis/formulaires/editer_gis.html @@ -0,0 +1,66 @@ +#CACHE{0} +
+ + [

(#ENV**{message_ok})

] + [

(#ENV*{message_erreur})

] + +
+ [(#REM) declarer les hidden qui declencheront le service du formulaire + parametre : url d'action ] + #ACTION_FORMULAIRE{#ENV{action}} +
    + [(#SAISIE{hidden,objet})] + [(#SAISIE{hidden,id_objet})] + [(#SAISIE{carte,editer_gis_#ENV{id_gis},env})] +
  • +
      + [(#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})] +
    +
  • + +
+ [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ] + +

+ +

+
+ +
diff --git a/www/plugins/gis/formulaires/editer_gis.php b/www/plugins/gis/formulaires/editer_gis.php new file mode 100644 index 0000000..22619dd --- /dev/null +++ b/www/plugins/gis/formulaires/editer_gis.php @@ -0,0 +1,93 @@ + 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 index 0000000..0126211 --- /dev/null +++ b/www/plugins/gis/formulaires/gis_inserer_modeles_traiter.php @@ -0,0 +1,34 @@ +'; + + return $code; +} + +?> diff --git a/www/plugins/gis/formulaires/rechercher_gis.html b/www/plugins/gis/formulaires/rechercher_gis.html new file mode 100755 index 0000000..9d6e8b4 --- /dev/null +++ b/www/plugins/gis/formulaires/rechercher_gis.html @@ -0,0 +1,36 @@ +#CACHE{0} + + + +
    + #SET{id_gis,#ID_GIS} + #SET{ou, #LISTE{#VILLE, #PAYS}|array_filter|join{", "}} + [
  • (#TITRE)[ ((#GET{ou}))] +
    + [(#BOUTON_ACTION{<:gis:bouton_lier:>,[(#URL_ACTION_AUTEUR{editer_lien_gis,lier/#GET{id_gis}/#ENV{objet}/#ENV{id_objet},#SELF})],ajax})] +
    +
  • ] + +
+
+

<:gis:erreur_recherche_pas_resultats:>

+ + +
+ [

(#ENV*{message_ok})

] + [

(#ENV*{message_erreur})

] + +
+ #ACTION_FORMULAIRE{#ENV{action}} +
    + [(#SAISIE{input,recherche_gis, + label=<:gis:label_rechercher_point:>})] +
+ [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ] + +

+ +

+
+ +
diff --git a/www/plugins/gis/formulaires/rechercher_gis.php b/www/plugins/gis/formulaires/rechercher_gis.php new file mode 100755 index 0000000..5e1597a --- /dev/null +++ b/www/plugins/gis/formulaires/rechercher_gis.php @@ -0,0 +1,51 @@ + \ 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 index 0000000..c158c2f --- /dev/null +++ b/www/plugins/gis/gis_administrations.php @@ -0,0 +1,116 @@ + $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 index 0000000..13d4c48 --- /dev/null +++ b/www/plugins/gis/gis_autoriser.php @@ -0,0 +1,113 @@ + \ 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 index 0000000..914a7bd --- /dev/null +++ b/www/plugins/gis/gis_download.html @@ -0,0 +1 @@ +[(#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})] \ 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 index 0000000..1c22da8 --- /dev/null +++ b/www/plugins/gis/gis_fonctions.php @@ -0,0 +1,427 @@ + 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 distanceid_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$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 `` 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 index 0000000..ab7370c --- /dev/null +++ b/www/plugins/gis/gis_json.html @@ -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 index 0000000..28e2934 --- /dev/null +++ b/www/plugins/gis/gis_kml.html @@ -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}},''})] + + +[(#NOM_SITE_SPIP|texte_backend)] +[(#DESCRIPTIF_SITE_SPIP|supprimer_tags|texte_backend)] + + [(#INCLURE{fond=inclure/kml-item,id_gis,objet,id_objet})] + + + \ 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 index 0000000..a02cc19 --- /dev/null +++ b/www/plugins/gis/gis_options.php @@ -0,0 +1,163 @@ + 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 index 0000000..14fdd0b --- /dev/null +++ b/www/plugins/gis/gis_pipelines.php @@ -0,0 +1,369 @@ +'; + $flux .="\n".''; + $flux .="\n".''; + 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".''; + } + + 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 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 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 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 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 index 0000000..e2867fc --- /dev/null +++ b/www/plugins/gis/inc/gis_xmlrpc.php @@ -0,0 +1,132 @@ + 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 index 0000000..9eff943 --- /dev/null +++ b/www/plugins/gis/inc/iptc.php @@ -0,0 +1,328 @@ + 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 index 0000000..484c03e --- /dev/null +++ b/www/plugins/gis/inc/kml_infos.php @@ -0,0 +1,187 @@ +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('//is', '$1',$infos['titre']); + if(isset($infos['descriptif'])) + $infos['descriptif'] = preg_replace('//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 index 0000000..8191d49 --- /dev/null +++ b/www/plugins/gis/inclure/download_gpx.html @@ -0,0 +1,7 @@ + + + [(#INCLURE{fond=inclure/gpx-item,id_gis})] + \ 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 index 0000000..87d6aff --- /dev/null +++ b/www/plugins/gis/inclure/download_kml.html @@ -0,0 +1,9 @@ + + + +[(#NOM_SITE_SPIP|texte_backend)] +[(#DESCRIPTIF_SITE_SPIP|supprimer_tags|texte_backend)] + [(#INCLURE{fond=inclure/kml-item,id_gis})] + + \ 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 index 0000000..5264c65 --- /dev/null +++ b/www/plugins/gis/inclure/gpx-item.html @@ -0,0 +1,9 @@ + + + [(#TITRE|supprimer_numero|texte_backend)][ + (#DESCRIPTIF|texte_backend) + ] + [] + + + \ 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 index 0000000..ade8ba0 --- /dev/null +++ b/www/plugins/gis/inclure/kml-item.html @@ -0,0 +1,42 @@ + + + [(#TITRE|supprimer_numero|texte_backend)][ + + ] + + + [ + (#LON),[(#LAT)] + ] + [(#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,''} + + + [ + (#GET{icon_w}),[(#GET{icon_h})] + ] + + 0.5,0.5 + + ] + + \ 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 index 0000000..8537b6a --- /dev/null +++ b/www/plugins/gis/javascript/gis.js.html @@ -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 index 0000000..822cb7f --- /dev/null +++ b/www/plugins/gis/javascript/gis_geocoder.js @@ -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 index 0000000..0faa8fd --- /dev/null +++ b/www/plugins/gis/javascript/gis_utils.js @@ -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 index 0000000..f55cd4a --- /dev/null +++ b/www/plugins/gis/javascript/leaflet.gis.js @@ -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 = '' + feature.properties.title + ''; + 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 index 0000000..cab4848 --- /dev/null +++ b/www/plugins/gis/json/gis.html @@ -0,0 +1,9 @@ + + {"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)] + }} diff --git a/www/plugins/gis/json/gis_articles.html b/www/plugins/gis/json/gis_articles.html new file mode 100644 index 0000000..d46943d --- /dev/null +++ b/www/plugins/gis/json/gis_articles.html @@ -0,0 +1,9 @@ + + {"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)] + }} \ 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 index 0000000..1f3de60 --- /dev/null +++ b/www/plugins/gis/json/gis_articles_branche.html @@ -0,0 +1,9 @@ + + {"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)] + }} \ 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 index 0000000..8527953 --- /dev/null +++ b/www/plugins/gis/json/gis_articles_plus_sites.html @@ -0,0 +1,2 @@ +[(#INCLURE{fond=json/gis_articles,env})#SET{articlesvus,1}][[(#GET{articlesvus}|?{','})] + (#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 index 0000000..e9d81cc --- /dev/null +++ b/www/plugins/gis/json/gis_auteurs.html @@ -0,0 +1,9 @@ + + {"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)] + }} \ 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 index 0000000..c7b66b0 --- /dev/null +++ b/www/plugins/gis/json/gis_documents.html @@ -0,0 +1,9 @@ + + {"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)] + }} \ 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 index 0000000..9c975ac --- /dev/null +++ b/www/plugins/gis/json/gis_evenements.html @@ -0,0 +1,9 @@ + + {"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)] + }} diff --git a/www/plugins/gis/json/gis_mots.html b/www/plugins/gis/json/gis_mots.html new file mode 100644 index 0000000..ea3455f --- /dev/null +++ b/www/plugins/gis/json/gis_mots.html @@ -0,0 +1,9 @@ + + {"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)] + }} \ 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 index 0000000..8e5c3fa --- /dev/null +++ b/www/plugins/gis/json/gis_point_libre.html @@ -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{

},''}|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 index 0000000..7d64147 --- /dev/null +++ b/www/plugins/gis/json/gis_rubriques.html @@ -0,0 +1,9 @@ + + {"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)] + }} \ 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 index 0000000..965e291 --- /dev/null +++ b/www/plugins/gis/json/gis_sites.html @@ -0,0 +1,9 @@ + + {"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)] + }} \ 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 index 0000000..d714179 --- /dev/null +++ b/www/plugins/gis/json/gis_tous_avec_liens_espace_prive.html @@ -0,0 +1,9 @@ + + {"type": "Feature", + "geometry": {"type": "Point", "coordinates": [#LON, #LAT]}, + "id":"#ID_GIS", + "properties": { + "title":[(#VAL{[(#TITRE*|extraire_multi|supprimer_numero|sinon{----})]}|json_encode)], + "description":[(#DESCRIPTIF|json_encode)][(#SET{logo_doc,''})][ + (#LOGO_GIS|gis_icon_properties)] + }} diff --git a/www/plugins/gis/lang/gis.xml b/www/plugins/gis/lang/gis.xml new file mode 100644 index 0000000..7a671c4 --- /dev/null +++ b/www/plugins/gis/lang/gis.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/www/plugins/gis/lang/gis_en.php b/www/plugins/gis/lang/gis_en.php new file mode 100644 index 0000000..b8281e4 --- /dev/null +++ b/www/plugins/gis/lang/gis_en.php @@ -0,0 +1,171 @@ + '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.
Link to the documentation.', + '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 the Bing website.', + 'cfg_inf_cloudmade' => 'This API needs a key you can create on the CloudMade website.', + '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 the GoogleMaps website.', + 'cfg_inf_yandex' => 'This API needs a key you can create on the yandex website.', + '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 index 0000000..71747e8 --- /dev/null +++ b/www/plugins/gis/lang/gis_es.php @@ -0,0 +1,171 @@ + '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.
Ir a la documentación.', + '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 el sitio de Bing.', + 'cfg_inf_cloudmade' => 'Esta API necesita crear una clave en el sitio de CloudMade.', + '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 el sitio de GoogleMaps.', + 'cfg_inf_yandex' => 'Esta API necesita crear una clave en el sitio de Yandex.', + '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 index 0000000..82eebf7 --- /dev/null +++ b/www/plugins/gis/lang/gis_fr.php @@ -0,0 +1,169 @@ + '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.
Accéder la documentation.', + '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 le site de Bing.', + 'cfg_inf_cloudmade' => 'Cette API nécessite une clé à créer sur le site de CloudMade.', + '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 le site de GoogleMaps.', + 'cfg_inf_yandex' => 'Cette API nécessite une clé à créer sur le site de Yandex.', + '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 index 0000000..8c62b01 --- /dev/null +++ b/www/plugins/gis/lang/gis_nl.php @@ -0,0 +1,171 @@ + '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.
Naar de documentatie.', + '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 de Bing website.', + 'cfg_inf_cloudmade' => 'Deze API heeft een sleutel nodig die je kunt aanmaken op de CloudMade website.', + '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 de GoogleMaps website.', + 'cfg_inf_yandex' => 'Deze API heeft een sleutel nodig die je kunt aanmaken op de Yandex website.', + '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 index 0000000..0e8d01f --- /dev/null +++ b/www/plugins/gis/lang/gis_ru.php @@ -0,0 +1,155 @@ + 'Нет ни одной точки на карте', + 'aucun_objet' => 'Нет связанных объектов', + + // B + 'bouton_lier' => 'Связать точку', + 'bouton_supprimer_gis' => 'Удалить точку', + 'bouton_supprimer_lien' => 'Удалить связь', + + // C + 'cfg_descr_gis' => 'Географическая Информационная Система (GIS).
Документация.', # MODIF + 'cfg_inf_adresse' => 'Показываются дополнительные поля для ввода адреса (страна, город, область, адрес...)', + 'cfg_inf_bing' => 'Для использования карты Bing Aerial необходимо создать ключ на сайте Bing.', + 'cfg_inf_cloudmade' => 'Для использования карты необходимо создать ключ на сайте CloudMade.', + 'cfg_inf_geocoder' => 'Включить функцию геопоиска (поиск точки на карте по адресу).', + 'cfg_inf_geolocaliser_user_html5' => 'Новая карта центрируется по расположению пользователя ( если позволяет его браузер).', + 'cfg_inf_google' => 'Для работы с картой необходим API ключ, который можно создать на сайте GoogleMaps.', + 'cfg_inf_yandex' => 'Для работы с картой необходим API ключ. Получить на сайте Yandex.', + '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 index 0000000..0b7c38b --- /dev/null +++ b/www/plugins/gis/lang/gis_sk.php @@ -0,0 +1,171 @@ + 'Ž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.
PrejsÅ¥ na dokumentáciu.', + '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 na stránke vyhľadávača Bing vytvorili kľúč.', + 'cfg_inf_cloudmade' => 'Táto aplikácia potrebuje kľúč na vytvorenie stránky v Cloude.', + '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 stránke GoogleMaps.', + 'cfg_inf_yandex' => 'Táto aplikácia potrebuje kľúč na vytvorenie stránky v Yandexe.', + '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 index 0000000..12799ff --- /dev/null +++ b/www/plugins/gis/lang/paquet-gis.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/www/plugins/gis/lang/paquet-gis_en.php b/www/plugins/gis/lang/paquet-gis_en.php new file mode 100644 index 0000000..9a87053 --- /dev/null +++ b/www/plugins/gis/lang/paquet-gis_en.php @@ -0,0 +1,15 @@ + '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 index 0000000..c433e2f --- /dev/null +++ b/www/plugins/gis/lang/paquet-gis_es.php @@ -0,0 +1,15 @@ + '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 index 0000000..efdd91a --- /dev/null +++ b/www/plugins/gis/lang/paquet-gis_fr.php @@ -0,0 +1,13 @@ + '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 index 0000000..442fb70 --- /dev/null +++ b/www/plugins/gis/lang/paquet-gis_nl.php @@ -0,0 +1,15 @@ + '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 index 0000000..5a85d3b --- /dev/null +++ b/www/plugins/gis/lang/paquet-gis_ru.php @@ -0,0 +1,15 @@ + 'Плагин 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 index 0000000..50cfd09 --- /dev/null +++ b/www/plugins/gis/lang/paquet-gis_sk.php @@ -0,0 +1,15 @@ + '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 index 0000000..46cd121 --- /dev/null +++ b/www/plugins/gis/lib/leaflet/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2010-2013, Vladimir Agafonkin +Copyright (c) 2010-2011, CloudMade +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/www/plugins/gis/lib/leaflet/README.md b/www/plugins/gis/lib/leaflet/README.md new file mode 100644 index 0000000..add1f0e --- /dev/null +++ b/www/plugins/gis/lib/leaflet/README.md @@ -0,0 +1,33 @@ +Sources utilisées dans la lib : + +* dist/ + * http://leafletjs.com/download.html +* plugins/ + * Bing.js + * https://github.com/shramov/leaflet-plugins/blob/master/layer/tile/Bing.js + * Control.FullScreen.js + * https://github.com/brunob/leaflet.fullscreen/blob/master/Control.FullScreen.js + * Control.MiniMap.js + * https://github.com/Norkart/Leaflet-MiniMap/blob/master/src/Control.MiniMap.js + * Google.js + * https://github.com/shramov/leaflet-plugins/blob/master/layer/tile/Google.js + * GPX.js + * https://github.com/shramov/leaflet-plugins/blob/master/layer/vector/GPX.js + * GPX.Speed.js + * https://github.com/shramov/leaflet-plugins/blob/master/layer/vector/GPX.Speed.js + * images + * mixed from all sources + * KML.js + * https://github.com/shramov/leaflet-plugins/blob/master/layer/vector/KML.js + * leaflet.markercluster.css + * https://github.com/Leaflet/Leaflet.markercluster/blob/master/dist/MarkerCluster.css + * https://github.com/Leaflet/Leaflet.markercluster/blob/master/dist/MarkerCluster.Default.css + * leaflet.markercluster-src.js + * https://github.com/Leaflet/Leaflet.markercluster/blob/master/dist/leaflet.markercluster-src.js + * leaflet-plugins.css + * https://github.com/brunob/leaflet.fullscreen/blob/master/Control.FullScreen.css + * https://github.com/Norkart/Leaflet-MiniMap/blob/master/src/Control.MiniMap.css + * leaflet-providers.js + * https://github.com/leaflet-extras/leaflet-providers/blob/master/leaflet-providers.js + * Marker.Rotate.js + * https://github.com/shramov/leaflet-plugins/blob/master/layer/Marker.Rotate.js 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 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 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 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 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 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 index 0000000..640ef73 --- /dev/null +++ b/www/plugins/gis/lib/leaflet/dist/leaflet-src.js @@ -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) { +var oldL = window.L, + L = {}; + +L.version = '0.7.3'; + +// define Leaflet for Node module pattern loaders, including Browserify +if (typeof module === 'object' && typeof module.exports === 'object') { + module.exports = L; + +// define Leaflet as an AMD module +} else if (typeof define === 'function' && define.amd) { + define(L); +} + +// define Leaflet as a global L variable, saving the original L to restore later if needed + +L.noConflict = function () { + window.L = oldL; + return this; +}; + +window.L = L; + + +/* + * L.Util contains various utility functions used throughout Leaflet code. + */ + +L.Util = { + extend: function (dest) { // (Object[, Object, ...]) -> + var sources = Array.prototype.slice.call(arguments, 1), + i, j, len, src; + + for (j = 0, len = sources.length; j < len; j++) { + src = sources[j] || {}; + for (i in src) { + if (src.hasOwnProperty(i)) { + dest[i] = src[i]; + } + } + } + return dest; + }, + + bind: function (fn, obj) { // (Function, Object) -> Function + var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null; + return function () { + return fn.apply(obj, args || arguments); + }; + }, + + stamp: (function () { + var lastId = 0, + key = '_leaflet_id'; + return function (obj) { + obj[key] = obj[key] || ++lastId; + return obj[key]; + }; + }()), + + invokeEach: function (obj, method, context) { + var i, args; + + if (typeof obj === 'object') { + args = Array.prototype.slice.call(arguments, 3); + + for (i in obj) { + method.apply(context, [i, obj[i]].concat(args)); + } + return true; + } + + return false; + }, + + limitExecByInterval: function (fn, time, context) { + var lock, execOnUnlock; + + return function wrapperFn() { + var args = arguments; + + if (lock) { + execOnUnlock = true; + return; + } + + lock = true; + + setTimeout(function () { + lock = false; + + if (execOnUnlock) { + wrapperFn.apply(context, args); + execOnUnlock = false; + } + }, time); + + fn.apply(context, args); + }; + }, + + falseFn: function () { + return false; + }, + + formatNum: function (num, digits) { + var pow = Math.pow(10, digits || 5); + return Math.round(num * pow) / pow; + }, + + trim: function (str) { + return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); + }, + + splitWords: function (str) { + return L.Util.trim(str).split(/\s+/); + }, + + setOptions: function (obj, options) { + obj.options = L.extend({}, obj.options, options); + return obj.options; + }, + + getParamString: function (obj, existingUrl, uppercase) { + var params = []; + for (var i in obj) { + params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i])); + } + return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&'); + }, + template: function (str, data) { + return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) { + var value = data[key]; + if (value === undefined) { + throw new Error('No value provided for variable ' + str); + } else if (typeof value === 'function') { + value = value(data); + } + return value; + }); + }, + + isArray: Array.isArray || function (obj) { + return (Object.prototype.toString.call(obj) === '[object Array]'); + }, + + emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=' +}; + +(function () { + + // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/ + + function getPrefixed(name) { + var i, fn, + prefixes = ['webkit', 'moz', 'o', 'ms']; + + for (i = 0; i < prefixes.length && !fn; i++) { + fn = window[prefixes[i] + name]; + } + + return fn; + } + + var lastTime = 0; + + function timeoutDefer(fn) { + var time = +new Date(), + timeToCall = Math.max(0, 16 - (time - lastTime)); + + lastTime = time + timeToCall; + return window.setTimeout(fn, timeToCall); + } + + var requestFn = window.requestAnimationFrame || + getPrefixed('RequestAnimationFrame') || timeoutDefer; + + var cancelFn = window.cancelAnimationFrame || + getPrefixed('CancelAnimationFrame') || + getPrefixed('CancelRequestAnimationFrame') || + function (id) { window.clearTimeout(id); }; + + + L.Util.requestAnimFrame = function (fn, context, immediate, element) { + fn = L.bind(fn, context); + + if (immediate && requestFn === timeoutDefer) { + fn(); + } else { + return requestFn.call(window, fn, element); + } + }; + + L.Util.cancelAnimFrame = function (id) { + if (id) { + cancelFn.call(window, id); + } + }; + +}()); + +// shortcuts for most used utility functions +L.extend = L.Util.extend; +L.bind = L.Util.bind; +L.stamp = L.Util.stamp; +L.setOptions = L.Util.setOptions; + + +/* + * L.Class powers the OOP facilities of the library. + * Thanks to John Resig and Dean Edwards for inspiration! + */ + +L.Class = function () {}; + +L.Class.extend = function (props) { + + // extended class with the new prototype + var NewClass = function () { + + // call the constructor + if (this.initialize) { + this.initialize.apply(this, arguments); + } + + // call all constructor hooks + if (this._initHooks) { + this.callInitHooks(); + } + }; + + // instantiate class without calling constructor + var F = function () {}; + F.prototype = this.prototype; + + var proto = new F(); + proto.constructor = NewClass; + + NewClass.prototype = proto; + + //inherit parent's statics + for (var i in this) { + if (this.hasOwnProperty(i) && i !== 'prototype') { + NewClass[i] = this[i]; + } + } + + // mix static properties into the class + if (props.statics) { + L.extend(NewClass, props.statics); + delete props.statics; + } + + // mix includes into the prototype + if (props.includes) { + L.Util.extend.apply(null, [proto].concat(props.includes)); + delete props.includes; + } + + // merge options + if (props.options && proto.options) { + props.options = L.extend({}, proto.options, props.options); + } + + // mix given properties into the prototype + L.extend(proto, props); + + proto._initHooks = []; + + var parent = this; + // jshint camelcase: false + NewClass.__super__ = parent.prototype; + + // add method for calling all hooks + proto.callInitHooks = function () { + + if (this._initHooksCalled) { return; } + + if (parent.prototype.callInitHooks) { + parent.prototype.callInitHooks.call(this); + } + + this._initHooksCalled = true; + + for (var i = 0, len = proto._initHooks.length; i < len; i++) { + proto._initHooks[i].call(this); + } + }; + + return NewClass; +}; + + +// method for adding properties to prototype +L.Class.include = function (props) { + L.extend(this.prototype, props); +}; + +// merge new default options to the Class +L.Class.mergeOptions = function (options) { + L.extend(this.prototype.options, options); +}; + +// add a constructor hook +L.Class.addInitHook = function (fn) { // (Function) || (String, args...) + var args = Array.prototype.slice.call(arguments, 1); + + var init = typeof fn === 'function' ? fn : function () { + this[fn].apply(this, args); + }; + + this.prototype._initHooks = this.prototype._initHooks || []; + this.prototype._initHooks.push(init); +}; + + +/* + * L.Mixin.Events is used to add custom events functionality to Leaflet classes. + */ + +var eventsKey = '_leaflet_events'; + +L.Mixin = {}; + +L.Mixin.Events = { + + addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object]) + + // types can be a map of types/handlers + if (L.Util.invokeEach(types, this.addEventListener, this, fn, context)) { return this; } + + var events = this[eventsKey] = this[eventsKey] || {}, + contextId = context && context !== this && L.stamp(context), + i, len, event, type, indexKey, indexLenKey, typeIndex; + + // types can be a string of space-separated words + types = L.Util.splitWords(types); + + for (i = 0, len = types.length; i < len; i++) { + event = { + action: fn, + context: context || this + }; + type = types[i]; + + if (contextId) { + // store listeners of a particular context in a separate hash (if it has an id) + // gives a major performance boost when removing thousands of map layers + + indexKey = type + '_idx'; + indexLenKey = indexKey + '_len'; + + typeIndex = events[indexKey] = events[indexKey] || {}; + + if (!typeIndex[contextId]) { + typeIndex[contextId] = []; + + // keep track of the number of keys in the index to quickly check if it's empty + events[indexLenKey] = (events[indexLenKey] || 0) + 1; + } + + typeIndex[contextId].push(event); + + + } else { + events[type] = events[type] || []; + events[type].push(event); + } + } + + return this; + }, + + hasEventListeners: function (type) { // (String) -> Boolean + var events = this[eventsKey]; + return !!events && ((type in events && events[type].length > 0) || + (type + '_idx' in events && events[type + '_idx_len'] > 0)); + }, + + removeEventListener: function (types, fn, context) { // ([String, Function, Object]) or (Object[, Object]) + + if (!this[eventsKey]) { + return this; + } + + if (!types) { + return this.clearAllEventListeners(); + } + + if (L.Util.invokeEach(types, this.removeEventListener, this, fn, context)) { return this; } + + var events = this[eventsKey], + contextId = context && context !== this && L.stamp(context), + i, len, type, listeners, j, indexKey, indexLenKey, typeIndex, removed; + + types = L.Util.splitWords(types); + + for (i = 0, len = types.length; i < len; i++) { + type = types[i]; + indexKey = type + '_idx'; + indexLenKey = indexKey + '_len'; + + typeIndex = events[indexKey]; + + if (!fn) { + // clear all listeners for a type if function isn't specified + delete events[type]; + delete events[indexKey]; + delete events[indexLenKey]; + + } else { + listeners = contextId && typeIndex ? typeIndex[contextId] : events[type]; + + if (listeners) { + for (j = listeners.length - 1; j >= 0; j--) { + if ((listeners[j].action === fn) && (!context || (listeners[j].context === context))) { + removed = listeners.splice(j, 1); + // set the old action to a no-op, because it is possible + // that the listener is being iterated over as part of a dispatch + removed[0].action = L.Util.falseFn; + } + } + + if (context && typeIndex && (listeners.length === 0)) { + delete typeIndex[contextId]; + events[indexLenKey]--; + } + } + } + } + + return this; + }, + + clearAllEventListeners: function () { + delete this[eventsKey]; + return this; + }, + + fireEvent: function (type, data) { // (String[, Object]) + if (!this.hasEventListeners(type)) { + return this; + } + + var event = L.Util.extend({}, data, { type: type, target: this }); + + var events = this[eventsKey], + listeners, i, len, typeIndex, contextId; + + if (events[type]) { + // make sure adding/removing listeners inside other listeners won't cause infinite loop + listeners = events[type].slice(); + + for (i = 0, len = listeners.length; i < len; i++) { + listeners[i].action.call(listeners[i].context, event); + } + } + + // fire event for the context-indexed listeners as well + typeIndex = events[type + '_idx']; + + for (contextId in typeIndex) { + listeners = typeIndex[contextId].slice(); + + if (listeners) { + for (i = 0, len = listeners.length; i < len; i++) { + listeners[i].action.call(listeners[i].context, event); + } + } + } + + return this; + }, + + addOneTimeEventListener: function (types, fn, context) { + + if (L.Util.invokeEach(types, this.addOneTimeEventListener, this, fn, context)) { return this; } + + var handler = L.bind(function () { + this + .removeEventListener(types, fn, context) + .removeEventListener(types, handler, context); + }, this); + + return this + .addEventListener(types, fn, context) + .addEventListener(types, handler, context); + } +}; + +L.Mixin.Events.on = L.Mixin.Events.addEventListener; +L.Mixin.Events.off = L.Mixin.Events.removeEventListener; +L.Mixin.Events.once = L.Mixin.Events.addOneTimeEventListener; +L.Mixin.Events.fire = L.Mixin.Events.fireEvent; + + +/* + * L.Browser handles different browser and feature detections for internal Leaflet use. + */ + +(function () { + + var ie = 'ActiveXObject' in window, + ielt9 = ie && !document.addEventListener, + + // terrible browser detection to work around Safari / iOS / Android browser bugs + ua = navigator.userAgent.toLowerCase(), + webkit = ua.indexOf('webkit') !== -1, + chrome = ua.indexOf('chrome') !== -1, + phantomjs = ua.indexOf('phantom') !== -1, + android = ua.indexOf('android') !== -1, + android23 = ua.search('android [23]') !== -1, + gecko = ua.indexOf('gecko') !== -1, + + mobile = typeof orientation !== undefined + '', + msPointer = window.navigator && window.navigator.msPointerEnabled && + window.navigator.msMaxTouchPoints && !window.PointerEvent, + pointer = (window.PointerEvent && window.navigator.pointerEnabled && window.navigator.maxTouchPoints) || + msPointer, + retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) || + ('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') && + window.matchMedia('(min-resolution:144dpi)').matches), + + doc = document.documentElement, + ie3d = ie && ('transition' in doc.style), + webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23, + gecko3d = 'MozPerspective' in doc.style, + opera3d = 'OTransition' in doc.style, + any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d) && !phantomjs; + + + // PhantomJS has 'ontouchstart' in document.documentElement, but doesn't actually support touch. + // https://github.com/Leaflet/Leaflet/pull/1434#issuecomment-13843151 + + var touch = !window.L_NO_TOUCH && !phantomjs && (function () { + + var startName = 'ontouchstart'; + + // IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.Pointer) or WebKit, etc. + if (pointer || (startName in doc)) { + return true; + } + + // Firefox/Gecko + var div = document.createElement('div'), + supported = false; + + if (!div.setAttribute) { + return false; + } + div.setAttribute(startName, 'return;'); + + if (typeof div[startName] === 'function') { + supported = true; + } + + div.removeAttribute(startName); + div = null; + + return supported; + }()); + + + L.Browser = { + ie: ie, + ielt9: ielt9, + webkit: webkit, + gecko: gecko && !webkit && !window.opera && !ie, + + android: android, + android23: android23, + + chrome: chrome, + + ie3d: ie3d, + webkit3d: webkit3d, + gecko3d: gecko3d, + opera3d: opera3d, + any3d: any3d, + + mobile: mobile, + mobileWebkit: mobile && webkit, + mobileWebkit3d: mobile && webkit3d, + mobileOpera: mobile && window.opera, + + touch: touch, + msPointer: msPointer, + pointer: pointer, + + retina: retina + }; + +}()); + + +/* + * L.Point represents a point with x and y coordinates. + */ + +L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) { + this.x = (round ? Math.round(x) : x); + this.y = (round ? Math.round(y) : y); +}; + +L.Point.prototype = { + + clone: function () { + return new L.Point(this.x, this.y); + }, + + // non-destructive, returns a new point + add: function (point) { + return this.clone()._add(L.point(point)); + }, + + // destructive, used directly for performance in situations where it's safe to modify existing point + _add: function (point) { + this.x += point.x; + this.y += point.y; + return this; + }, + + subtract: function (point) { + return this.clone()._subtract(L.point(point)); + }, + + _subtract: function (point) { + this.x -= point.x; + this.y -= point.y; + return this; + }, + + divideBy: function (num) { + return this.clone()._divideBy(num); + }, + + _divideBy: function (num) { + this.x /= num; + this.y /= num; + return this; + }, + + multiplyBy: function (num) { + return this.clone()._multiplyBy(num); + }, + + _multiplyBy: function (num) { + this.x *= num; + this.y *= num; + return this; + }, + + round: function () { + return this.clone()._round(); + }, + + _round: function () { + this.x = Math.round(this.x); + this.y = Math.round(this.y); + return this; + }, + + floor: function () { + return this.clone()._floor(); + }, + + _floor: function () { + this.x = Math.floor(this.x); + this.y = Math.floor(this.y); + return this; + }, + + distanceTo: function (point) { + point = L.point(point); + + var x = point.x - this.x, + y = point.y - this.y; + + return Math.sqrt(x * x + y * y); + }, + + equals: function (point) { + point = L.point(point); + + return point.x === this.x && + point.y === this.y; + }, + + contains: function (point) { + point = L.point(point); + + return Math.abs(point.x) <= Math.abs(this.x) && + Math.abs(point.y) <= Math.abs(this.y); + }, + + toString: function () { + return 'Point(' + + L.Util.formatNum(this.x) + ', ' + + L.Util.formatNum(this.y) + ')'; + } +}; + +L.point = function (x, y, round) { + if (x instanceof L.Point) { + return x; + } + if (L.Util.isArray(x)) { + return new L.Point(x[0], x[1]); + } + if (x === undefined || x === null) { + return x; + } + return new L.Point(x, y, round); +}; + + +/* + * L.Bounds represents a rectangular area on the screen in pixel coordinates. + */ + +L.Bounds = function (a, b) { //(Point, Point) or Point[] + if (!a) { return; } + + var points = b ? [a, b] : a; + + for (var i = 0, len = points.length; i < len; i++) { + this.extend(points[i]); + } +}; + +L.Bounds.prototype = { + // extend the bounds to contain the given point + extend: function (point) { // (Point) + point = L.point(point); + + if (!this.min && !this.max) { + this.min = point.clone(); + this.max = point.clone(); + } else { + this.min.x = Math.min(point.x, this.min.x); + this.max.x = Math.max(point.x, this.max.x); + this.min.y = Math.min(point.y, this.min.y); + this.max.y = Math.max(point.y, this.max.y); + } + return this; + }, + + getCenter: function (round) { // (Boolean) -> Point + return new L.Point( + (this.min.x + this.max.x) / 2, + (this.min.y + this.max.y) / 2, round); + }, + + getBottomLeft: function () { // -> Point + return new L.Point(this.min.x, this.max.y); + }, + + getTopRight: function () { // -> Point + return new L.Point(this.max.x, this.min.y); + }, + + getSize: function () { + return this.max.subtract(this.min); + }, + + contains: function (obj) { // (Bounds) or (Point) -> Boolean + var min, max; + + if (typeof obj[0] === 'number' || obj instanceof L.Point) { + obj = L.point(obj); + } else { + obj = L.bounds(obj); + } + + if (obj instanceof L.Bounds) { + min = obj.min; + max = obj.max; + } else { + min = max = obj; + } + + return (min.x >= this.min.x) && + (max.x <= this.max.x) && + (min.y >= this.min.y) && + (max.y <= this.max.y); + }, + + intersects: function (bounds) { // (Bounds) -> Boolean + bounds = L.bounds(bounds); + + var min = this.min, + max = this.max, + min2 = bounds.min, + max2 = bounds.max, + xIntersects = (max2.x >= min.x) && (min2.x <= max.x), + yIntersects = (max2.y >= min.y) && (min2.y <= max.y); + + return xIntersects && yIntersects; + }, + + isValid: function () { + return !!(this.min && this.max); + } +}; + +L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[]) + if (!a || a instanceof L.Bounds) { + return a; + } + return new L.Bounds(a, b); +}; + + +/* + * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix. + */ + +L.Transformation = function (a, b, c, d) { + this._a = a; + this._b = b; + this._c = c; + this._d = d; +}; + +L.Transformation.prototype = { + transform: function (point, scale) { // (Point, Number) -> Point + return this._transform(point.clone(), scale); + }, + + // destructive transform (faster) + _transform: function (point, scale) { + scale = scale || 1; + point.x = scale * (this._a * point.x + this._b); + point.y = scale * (this._c * point.y + this._d); + return point; + }, + + untransform: function (point, scale) { + scale = scale || 1; + return new L.Point( + (point.x / scale - this._b) / this._a, + (point.y / scale - this._d) / this._c); + } +}; + + +/* + * L.DomUtil contains various utility functions for working with DOM. + */ + +L.DomUtil = { + get: function (id) { + return (typeof id === 'string' ? document.getElementById(id) : id); + }, + + getStyle: function (el, style) { + + var value = el.style[style]; + + if (!value && el.currentStyle) { + value = el.currentStyle[style]; + } + + if ((!value || value === 'auto') && document.defaultView) { + var css = document.defaultView.getComputedStyle(el, null); + value = css ? css[style] : null; + } + + return value === 'auto' ? null : value; + }, + + getViewportOffset: function (element) { + + var top = 0, + left = 0, + el = element, + docBody = document.body, + docEl = document.documentElement, + pos; + + do { + top += el.offsetTop || 0; + left += el.offsetLeft || 0; + + //add borders + top += parseInt(L.DomUtil.getStyle(el, 'borderTopWidth'), 10) || 0; + left += parseInt(L.DomUtil.getStyle(el, 'borderLeftWidth'), 10) || 0; + + pos = L.DomUtil.getStyle(el, 'position'); + + if (el.offsetParent === docBody && pos === 'absolute') { break; } + + if (pos === 'fixed') { + top += docBody.scrollTop || docEl.scrollTop || 0; + left += docBody.scrollLeft || docEl.scrollLeft || 0; + break; + } + + if (pos === 'relative' && !el.offsetLeft) { + var width = L.DomUtil.getStyle(el, 'width'), + maxWidth = L.DomUtil.getStyle(el, 'max-width'), + r = el.getBoundingClientRect(); + + if (width !== 'none' || maxWidth !== 'none') { + left += r.left + el.clientLeft; + } + + //calculate full y offset since we're breaking out of the loop + top += r.top + (docBody.scrollTop || docEl.scrollTop || 0); + + break; + } + + el = el.offsetParent; + + } while (el); + + el = element; + + do { + if (el === docBody) { break; } + + top -= el.scrollTop || 0; + left -= el.scrollLeft || 0; + + el = el.parentNode; + } while (el); + + return new L.Point(left, top); + }, + + documentIsLtr: function () { + if (!L.DomUtil._docIsLtrCached) { + L.DomUtil._docIsLtrCached = true; + L.DomUtil._docIsLtr = L.DomUtil.getStyle(document.body, 'direction') === 'ltr'; + } + return L.DomUtil._docIsLtr; + }, + + create: function (tagName, className, container) { + + var el = document.createElement(tagName); + el.className = className; + + if (container) { + container.appendChild(el); + } + + return el; + }, + + hasClass: function (el, name) { + if (el.classList !== undefined) { + return el.classList.contains(name); + } + var className = L.DomUtil._getClass(el); + return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); + }, + + addClass: function (el, name) { + if (el.classList !== undefined) { + var classes = L.Util.splitWords(name); + for (var i = 0, len = classes.length; i < len; i++) { + el.classList.add(classes[i]); + } + } else if (!L.DomUtil.hasClass(el, name)) { + var className = L.DomUtil._getClass(el); + L.DomUtil._setClass(el, (className ? className + ' ' : '') + name); + } + }, + + removeClass: function (el, name) { + if (el.classList !== undefined) { + el.classList.remove(name); + } else { + L.DomUtil._setClass(el, L.Util.trim((' ' + L.DomUtil._getClass(el) + ' ').replace(' ' + name + ' ', ' '))); + } + }, + + _setClass: function (el, name) { + if (el.className.baseVal === undefined) { + el.className = name; + } else { + // in case of SVG element + el.className.baseVal = name; + } + }, + + _getClass: function (el) { + return el.className.baseVal === undefined ? el.className : el.className.baseVal; + }, + + setOpacity: function (el, value) { + + if ('opacity' in el.style) { + el.style.opacity = value; + + } else if ('filter' in el.style) { + + var filter = false, + filterName = 'DXImageTransform.Microsoft.Alpha'; + + // filters collection throws an error if we try to retrieve a filter that doesn't exist + try { + filter = el.filters.item(filterName); + } catch (e) { + // don't set opacity to 1 if we haven't already set an opacity, + // it isn't needed and breaks transparent pngs. + if (value === 1) { return; } + } + + value = Math.round(value * 100); + + if (filter) { + filter.Enabled = (value !== 100); + filter.Opacity = value; + } else { + el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; + } + } + }, + + testProp: function (props) { + + var style = document.documentElement.style; + + for (var i = 0; i < props.length; i++) { + if (props[i] in style) { + return props[i]; + } + } + return false; + }, + + getTranslateString: function (point) { + // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate + // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care + // (same speed either way), Opera 12 doesn't support translate3d + + var is3d = L.Browser.webkit3d, + open = 'translate' + (is3d ? '3d' : '') + '(', + close = (is3d ? ',0' : '') + ')'; + + return open + point.x + 'px,' + point.y + 'px' + close; + }, + + getScaleString: function (scale, origin) { + + var preTranslateStr = L.DomUtil.getTranslateString(origin.add(origin.multiplyBy(-1 * scale))), + scaleStr = ' scale(' + scale + ') '; + + return preTranslateStr + scaleStr; + }, + + setPosition: function (el, point, disable3D) { // (HTMLElement, Point[, Boolean]) + + // jshint camelcase: false + el._leaflet_pos = point; + + if (!disable3D && L.Browser.any3d) { + el.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(point); + } else { + el.style.left = point.x + 'px'; + el.style.top = point.y + 'px'; + } + }, + + getPosition: function (el) { + // this method is only used for elements previously positioned using setPosition, + // so it's safe to cache the position for performance + + // jshint camelcase: false + return el._leaflet_pos; + } +}; + + +// prefix style property names + +L.DomUtil.TRANSFORM = L.DomUtil.testProp( + ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']); + +// webkitTransition comes first because some browser versions that drop vendor prefix don't do +// the same for the transitionend event, in particular the Android 4.1 stock browser + +L.DomUtil.TRANSITION = L.DomUtil.testProp( + ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); + +L.DomUtil.TRANSITION_END = + L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ? + L.DomUtil.TRANSITION + 'End' : 'transitionend'; + +(function () { + if ('onselectstart' in document) { + L.extend(L.DomUtil, { + disableTextSelection: function () { + L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault); + }, + + enableTextSelection: function () { + L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault); + } + }); + } else { + var userSelectProperty = L.DomUtil.testProp( + ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); + + L.extend(L.DomUtil, { + disableTextSelection: function () { + if (userSelectProperty) { + var style = document.documentElement.style; + this._userSelect = style[userSelectProperty]; + style[userSelectProperty] = 'none'; + } + }, + + enableTextSelection: function () { + if (userSelectProperty) { + document.documentElement.style[userSelectProperty] = this._userSelect; + delete this._userSelect; + } + } + }); + } + + L.extend(L.DomUtil, { + disableImageDrag: function () { + L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault); + }, + + enableImageDrag: function () { + L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault); + } + }); +})(); + + +/* + * L.LatLng represents a geographical point with latitude and longitude coordinates. + */ + +L.LatLng = function (lat, lng, alt) { // (Number, Number, Number) + lat = parseFloat(lat); + lng = parseFloat(lng); + + if (isNaN(lat) || isNaN(lng)) { + throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); + } + + this.lat = lat; + this.lng = lng; + + if (alt !== undefined) { + this.alt = parseFloat(alt); + } +}; + +L.extend(L.LatLng, { + DEG_TO_RAD: Math.PI / 180, + RAD_TO_DEG: 180 / Math.PI, + MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check +}); + +L.LatLng.prototype = { + equals: function (obj) { // (LatLng) -> Boolean + if (!obj) { return false; } + + obj = L.latLng(obj); + + var margin = Math.max( + Math.abs(this.lat - obj.lat), + Math.abs(this.lng - obj.lng)); + + return margin <= L.LatLng.MAX_MARGIN; + }, + + toString: function (precision) { // (Number) -> String + return 'LatLng(' + + L.Util.formatNum(this.lat, precision) + ', ' + + L.Util.formatNum(this.lng, precision) + ')'; + }, + + // Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula + // TODO move to projection code, LatLng shouldn't know about Earth + distanceTo: function (other) { // (LatLng) -> Number + other = L.latLng(other); + + var R = 6378137, // earth radius in meters + d2r = L.LatLng.DEG_TO_RAD, + dLat = (other.lat - this.lat) * d2r, + dLon = (other.lng - this.lng) * d2r, + lat1 = this.lat * d2r, + lat2 = other.lat * d2r, + sin1 = Math.sin(dLat / 2), + sin2 = Math.sin(dLon / 2); + + var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2); + + return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + }, + + wrap: function (a, b) { // (Number, Number) -> LatLng + var lng = this.lng; + + a = a || -180; + b = b || 180; + + lng = (lng + b) % (b - a) + (lng < a || lng === b ? b : a); + + return new L.LatLng(this.lat, lng); + } +}; + +L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Number) + if (a instanceof L.LatLng) { + return a; + } + if (L.Util.isArray(a)) { + if (typeof a[0] === 'number' || typeof a[0] === 'string') { + return new L.LatLng(a[0], a[1], a[2]); + } else { + return null; + } + } + if (a === undefined || a === null) { + return a; + } + if (typeof a === 'object' && 'lat' in a) { + return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon); + } + if (b === undefined) { + return null; + } + return new L.LatLng(a, b); +}; + + + +/* + * L.LatLngBounds represents a rectangular area on the map in geographical coordinates. + */ + +L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[]) + if (!southWest) { return; } + + var latlngs = northEast ? [southWest, northEast] : southWest; + + for (var i = 0, len = latlngs.length; i < len; i++) { + this.extend(latlngs[i]); + } +}; + +L.LatLngBounds.prototype = { + // extend the bounds to contain the given point or bounds + extend: function (obj) { // (LatLng) or (LatLngBounds) + if (!obj) { return this; } + + var latLng = L.latLng(obj); + if (latLng !== null) { + obj = latLng; + } else { + obj = L.latLngBounds(obj); + } + + if (obj instanceof L.LatLng) { + if (!this._southWest && !this._northEast) { + this._southWest = new L.LatLng(obj.lat, obj.lng); + this._northEast = new L.LatLng(obj.lat, obj.lng); + } else { + this._southWest.lat = Math.min(obj.lat, this._southWest.lat); + this._southWest.lng = Math.min(obj.lng, this._southWest.lng); + + this._northEast.lat = Math.max(obj.lat, this._northEast.lat); + this._northEast.lng = Math.max(obj.lng, this._northEast.lng); + } + } else if (obj instanceof L.LatLngBounds) { + this.extend(obj._southWest); + this.extend(obj._northEast); + } + return this; + }, + + // extend the bounds by a percentage + pad: function (bufferRatio) { // (Number) -> LatLngBounds + var sw = this._southWest, + ne = this._northEast, + heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, + widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; + + return new L.LatLngBounds( + new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), + new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); + }, + + getCenter: function () { // -> LatLng + return new L.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 L.LatLng(this.getNorth(), this.getWest()); + }, + + getSouthEast: function () { + return new L.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 (obj) { // (LatLngBounds) or (LatLng) -> Boolean + if (typeof obj[0] === 'number' || obj instanceof L.LatLng) { + obj = L.latLng(obj); + } else { + obj = L.latLngBounds(obj); + } + + var sw = this._southWest, + ne = this._northEast, + sw2, ne2; + + if (obj instanceof L.LatLngBounds) { + sw2 = obj.getSouthWest(); + ne2 = obj.getNorthEast(); + } else { + sw2 = ne2 = obj; + } + + return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && + (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); + }, + + intersects: function (bounds) { // (LatLngBounds) + bounds = L.latLngBounds(bounds); + + var sw = this._southWest, + ne = this._northEast, + sw2 = bounds.getSouthWest(), + ne2 = bounds.getNorthEast(), + + latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), + lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); + + return latIntersects && lngIntersects; + }, + + toBBoxString: function () { + return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(','); + }, + + equals: function (bounds) { // (LatLngBounds) + if (!bounds) { return false; } + + bounds = L.latLngBounds(bounds); + + return this._southWest.equals(bounds.getSouthWest()) && + this._northEast.equals(bounds.getNorthEast()); + }, + + isValid: function () { + return !!(this._southWest && this._northEast); + } +}; + +//TODO International date line? + +L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng) + if (!a || a instanceof L.LatLngBounds) { + return a; + } + return new L.LatLngBounds(a, b); +}; + + +/* + * L.Projection contains various geographical projections used by CRS classes. + */ + +L.Projection = {}; + + +/* + * Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default. + */ + +L.Projection.SphericalMercator = { + MAX_LATITUDE: 85.0511287798, + + project: function (latlng) { // (LatLng) -> Point + var d = L.LatLng.DEG_TO_RAD, + max = this.MAX_LATITUDE, + lat = Math.max(Math.min(max, latlng.lat), -max), + x = latlng.lng * d, + y = lat * d; + + y = Math.log(Math.tan((Math.PI / 4) + (y / 2))); + + return new L.Point(x, y); + }, + + unproject: function (point) { // (Point, Boolean) -> LatLng + var d = L.LatLng.RAD_TO_DEG, + lng = point.x * d, + lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d; + + return new L.LatLng(lat, lng); + } +}; + + +/* + * Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple. + */ + +L.Projection.LonLat = { + project: function (latlng) { + return new L.Point(latlng.lng, latlng.lat); + }, + + unproject: function (point) { + return new L.LatLng(point.y, point.x); + } +}; + + +/* + * L.CRS is a base object for all defined CRS (Coordinate Reference Systems) in Leaflet. + */ + +L.CRS = { + latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point + var projectedPoint = this.projection.project(latlng), + scale = this.scale(zoom); + + return this.transformation._transform(projectedPoint, scale); + }, + + pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng + var scale = this.scale(zoom), + untransformedPoint = this.transformation.untransform(point, scale); + + return this.projection.unproject(untransformedPoint); + }, + + project: function (latlng) { + return this.projection.project(latlng); + }, + + scale: function (zoom) { + return 256 * Math.pow(2, zoom); + }, + + getSize: function (zoom) { + var s = this.scale(zoom); + return L.point(s, s); + } +}; + + +/* + * 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); + } +}); + + +/* + * L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping + * and is used by Leaflet by default. + */ + +L.CRS.EPSG3857 = L.extend({}, L.CRS, { + code: 'EPSG:3857', + + projection: L.Projection.SphericalMercator, + transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5), + + project: function (latlng) { // (LatLng) -> Point + var projectedPoint = this.projection.project(latlng), + earthRadius = 6378137; + return projectedPoint.multiplyBy(earthRadius); + } +}); + +L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, { + code: 'EPSG:900913' +}); + + +/* + * L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists. + */ + +L.CRS.EPSG4326 = L.extend({}, L.CRS, { + code: 'EPSG:4326', + + projection: L.Projection.LonLat, + transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5) +}); + + +/* + * L.Map is the central class of the API - it is used to create a map. + */ + +L.Map = L.Class.extend({ + + includes: L.Mixin.Events, + + options: { + crs: L.CRS.EPSG3857, + + /* + center: LatLng, + zoom: Number, + layers: Array, + */ + + fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23, + trackResize: true, + markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d + }, + + initialize: function (id, options) { // (HTMLElement or String, Object) + options = L.setOptions(this, options); + + + this._initContainer(id); + this._initLayout(); + + // hack for https://github.com/Leaflet/Leaflet/issues/1980 + this._onResize = L.bind(this._onResize, this); + + this._initEvents(); + + if (options.maxBounds) { + this.setMaxBounds(options.maxBounds); + } + + if (options.center && options.zoom !== undefined) { + this.setView(L.latLng(options.center), options.zoom, {reset: true}); + } + + this._handlers = []; + + this._layers = {}; + this._zoomBoundLayers = {}; + this._tileLayersNum = 0; + + this.callInitHooks(); + + this._addLayers(options.layers); + }, + + + // public methods that modify map state + + // replaced by animation-powered implementation in Map.PanAnimation.js + setView: function (center, zoom) { + zoom = zoom === undefined ? this.getZoom() : zoom; + this._resetView(L.latLng(center), this._limitZoom(zoom)); + return this; + }, + + setZoom: function (zoom, options) { + if (!this._loaded) { + this._zoom = this._limitZoom(zoom); + return this; + } + return this.setView(this.getCenter(), zoom, {zoom: options}); + }, + + zoomIn: function (delta, options) { + return this.setZoom(this._zoom + (delta || 1), options); + }, + + zoomOut: function (delta, options) { + return this.setZoom(this._zoom - (delta || 1), options); + }, + + setZoomAround: function (latlng, zoom, options) { + var scale = this.getZoomScale(zoom), + viewHalf = this.getSize().divideBy(2), + containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng), + + centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale), + newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset)); + + return this.setView(newCenter, zoom, {zoom: options}); + }, + + fitBounds: function (bounds, options) { + + options = options || {}; + bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds); + + var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]), + paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]), + + zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)), + paddingOffset = paddingBR.subtract(paddingTL).divideBy(2), + + swPoint = this.project(bounds.getSouthWest(), zoom), + nePoint = this.project(bounds.getNorthEast(), zoom), + center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom); + + zoom = options && options.maxZoom ? Math.min(options.maxZoom, zoom) : zoom; + + return this.setView(center, zoom, options); + }, + + fitWorld: function (options) { + return this.fitBounds([[-90, -180], [90, 180]], options); + }, + + panTo: function (center, options) { // (LatLng) + return this.setView(center, this._zoom, {pan: options}); + }, + + panBy: function (offset) { // (Point) + // replaced with animated panBy in Map.PanAnimation.js + this.fire('movestart'); + + this._rawPanBy(L.point(offset)); + + this.fire('move'); + return this.fire('moveend'); + }, + + setMaxBounds: function (bounds) { + bounds = L.latLngBounds(bounds); + + this.options.maxBounds = bounds; + + if (!bounds) { + return this.off('moveend', this._panInsideMaxBounds, this); + } + + if (this._loaded) { + this._panInsideMaxBounds(); + } + + return this.on('moveend', this._panInsideMaxBounds, this); + }, + + panInsideBounds: function (bounds, options) { + var center = this.getCenter(), + newCenter = this._limitCenter(center, this._zoom, bounds); + + if (center.equals(newCenter)) { return this; } + + return this.panTo(newCenter, options); + }, + + addLayer: function (layer) { + // TODO method is too big, refactor + + var id = L.stamp(layer); + + if (this._layers[id]) { return this; } + + this._layers[id] = layer; + + // TODO getMaxZoom, getMinZoom in ILayer (instead of options) + if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) { + this._zoomBoundLayers[id] = layer; + this._updateZoomLevels(); + } + + // TODO looks ugly, refactor!!! + if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) { + this._tileLayersNum++; + this._tileLayersToLoad++; + layer.on('load', this._onTileLayerLoad, this); + } + + if (this._loaded) { + this._layerAdd(layer); + } + + return this; + }, + + removeLayer: function (layer) { + var id = L.stamp(layer); + + if (!this._layers[id]) { return this; } + + if (this._loaded) { + layer.onRemove(this); + } + + delete this._layers[id]; + + if (this._loaded) { + this.fire('layerremove', {layer: layer}); + } + + if (this._zoomBoundLayers[id]) { + delete this._zoomBoundLayers[id]; + this._updateZoomLevels(); + } + + // TODO looks ugly, refactor + if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) { + this._tileLayersNum--; + this._tileLayersToLoad--; + layer.off('load', this._onTileLayerLoad, this); + } + + return this; + }, + + hasLayer: function (layer) { + if (!layer) { return false; } + + return (L.stamp(layer) in this._layers); + }, + + eachLayer: function (method, context) { + for (var i in this._layers) { + method.call(context, this._layers[i]); + } + return this; + }, + + invalidateSize: function (options) { + if (!this._loaded) { return this; } + + options = L.extend({ + animate: false, + pan: true + }, options === true ? {animate: true} : options); + + var oldSize = this.getSize(); + this._sizeChanged = true; + this._initialCenter = null; + + var newSize = this.getSize(), + oldCenter = oldSize.divideBy(2).round(), + newCenter = newSize.divideBy(2).round(), + offset = oldCenter.subtract(newCenter); + + if (!offset.x && !offset.y) { return this; } + + if (options.animate && options.pan) { + this.panBy(offset); + + } else { + if (options.pan) { + this._rawPanBy(offset); + } + + this.fire('move'); + + if (options.debounceMoveend) { + clearTimeout(this._sizeTimer); + this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200); + } else { + this.fire('moveend'); + } + } + + return this.fire('resize', { + oldSize: oldSize, + newSize: newSize + }); + }, + + // TODO handler.addTo + addHandler: function (name, HandlerClass) { + if (!HandlerClass) { return this; } + + var handler = this[name] = new HandlerClass(this); + + this._handlers.push(handler); + + if (this.options[name]) { + handler.enable(); + } + + return this; + }, + + remove: function () { + if (this._loaded) { + this.fire('unload'); + } + + this._initEvents('off'); + + try { + // throws error in IE6-8 + delete this._container._leaflet; + } catch (e) { + this._container._leaflet = undefined; + } + + this._clearPanes(); + if (this._clearControlPos) { + this._clearControlPos(); + } + + this._clearHandlers(); + + return this; + }, + + + // public methods for getting map state + + getCenter: function () { // (Boolean) -> LatLng + this._checkIfLoaded(); + + if (this._initialCenter && !this._moved()) { + return this._initialCenter; + } + return this.layerPointToLatLng(this._getCenterLayerPoint()); + }, + + getZoom: function () { + return this._zoom; + }, + + getBounds: function () { + var bounds = this.getPixelBounds(), + sw = this.unproject(bounds.getBottomLeft()), + ne = this.unproject(bounds.getTopRight()); + + return new L.LatLngBounds(sw, ne); + }, + + getMinZoom: function () { + return this.options.minZoom === undefined ? + (this._layersMinZoom === undefined ? 0 : this._layersMinZoom) : + this.options.minZoom; + }, + + getMaxZoom: function () { + return this.options.maxZoom === undefined ? + (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) : + this.options.maxZoom; + }, + + getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number + bounds = L.latLngBounds(bounds); + + var zoom = this.getMinZoom() - (inside ? 1 : 0), + maxZoom = this.getMaxZoom(), + size = this.getSize(), + + nw = bounds.getNorthWest(), + se = bounds.getSouthEast(), + + zoomNotFound = true, + boundsSize; + + padding = L.point(padding || [0, 0]); + + do { + zoom++; + boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)).add(padding); + zoomNotFound = !inside ? size.contains(boundsSize) : boundsSize.x < size.x || boundsSize.y < size.y; + + } while (zoomNotFound && zoom <= maxZoom); + + if (zoomNotFound && inside) { + return null; + } + + return inside ? zoom : zoom - 1; + }, + + getSize: function () { + if (!this._size || this._sizeChanged) { + this._size = new L.Point( + this._container.clientWidth, + this._container.clientHeight); + + this._sizeChanged = false; + } + return this._size.clone(); + }, + + getPixelBounds: function () { + var topLeftPoint = this._getTopLeftPoint(); + return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); + }, + + getPixelOrigin: function () { + this._checkIfLoaded(); + return this._initialTopLeftPoint; + }, + + getPanes: function () { + return this._panes; + }, + + getContainer: function () { + return this._container; + }, + + + // TODO replace with universal implementation after refactoring projections + + getZoomScale: function (toZoom) { + var crs = this.options.crs; + return crs.scale(toZoom) / crs.scale(this._zoom); + }, + + getScaleZoom: function (scale) { + return this._zoom + (Math.log(scale) / Math.LN2); + }, + + + // conversion methods + + project: function (latlng, zoom) { // (LatLng[, Number]) -> Point + zoom = zoom === undefined ? this._zoom : zoom; + return this.options.crs.latLngToPoint(L.latLng(latlng), zoom); + }, + + unproject: function (point, zoom) { // (Point[, Number]) -> LatLng + zoom = zoom === undefined ? this._zoom : zoom; + return this.options.crs.pointToLatLng(L.point(point), zoom); + }, + + layerPointToLatLng: function (point) { // (Point) + var projectedPoint = L.point(point).add(this.getPixelOrigin()); + return this.unproject(projectedPoint); + }, + + latLngToLayerPoint: function (latlng) { // (LatLng) + var projectedPoint = this.project(L.latLng(latlng))._round(); + return projectedPoint._subtract(this.getPixelOrigin()); + }, + + containerPointToLayerPoint: function (point) { // (Point) + return L.point(point).subtract(this._getMapPanePos()); + }, + + layerPointToContainerPoint: function (point) { // (Point) + return L.point(point).add(this._getMapPanePos()); + }, + + containerPointToLatLng: function (point) { + var layerPoint = this.containerPointToLayerPoint(L.point(point)); + return this.layerPointToLatLng(layerPoint); + }, + + latLngToContainerPoint: function (latlng) { + return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng))); + }, + + mouseEventToContainerPoint: function (e) { // (MouseEvent) + return L.DomEvent.getMousePosition(e, this._container); + }, + + mouseEventToLayerPoint: function (e) { // (MouseEvent) + return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e)); + }, + + mouseEventToLatLng: function (e) { // (MouseEvent) + return this.layerPointToLatLng(this.mouseEventToLayerPoint(e)); + }, + + + // map initialization methods + + _initContainer: function (id) { + var container = this._container = L.DomUtil.get(id); + + if (!container) { + throw new Error('Map container not found.'); + } else if (container._leaflet) { + throw new Error('Map container is already initialized.'); + } + + container._leaflet = true; + }, + + _initLayout: function () { + var container = this._container; + + L.DomUtil.addClass(container, 'leaflet-container' + + (L.Browser.touch ? ' leaflet-touch' : '') + + (L.Browser.retina ? ' leaflet-retina' : '') + + (L.Browser.ielt9 ? ' leaflet-oldie' : '') + + (this.options.fadeAnimation ? ' leaflet-fade-anim' : '')); + + var position = L.DomUtil.getStyle(container, 'position'); + + if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') { + container.style.position = 'relative'; + } + + this._initPanes(); + + if (this._initControlPos) { + this._initControlPos(); + } + }, + + _initPanes: function () { + var panes = this._panes = {}; + + this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container); + + this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane); + panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane); + panes.shadowPane = this._createPane('leaflet-shadow-pane'); + panes.overlayPane = this._createPane('leaflet-overlay-pane'); + panes.markerPane = this._createPane('leaflet-marker-pane'); + panes.popupPane = this._createPane('leaflet-popup-pane'); + + var zoomHide = ' leaflet-zoom-hide'; + + if (!this.options.markerZoomAnimation) { + L.DomUtil.addClass(panes.markerPane, zoomHide); + L.DomUtil.addClass(panes.shadowPane, zoomHide); + L.DomUtil.addClass(panes.popupPane, zoomHide); + } + }, + + _createPane: function (className, container) { + return L.DomUtil.create('div', className, container || this._panes.objectsPane); + }, + + _clearPanes: function () { + this._container.removeChild(this._mapPane); + }, + + _addLayers: function (layers) { + layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : []; + + for (var i = 0, len = layers.length; i < len; i++) { + this.addLayer(layers[i]); + } + }, + + + // private methods that modify map state + + _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) { + + var zoomChanged = (this._zoom !== zoom); + + if (!afterZoomAnim) { + this.fire('movestart'); + + if (zoomChanged) { + this.fire('zoomstart'); + } + } + + this._zoom = zoom; + this._initialCenter = center; + + this._initialTopLeftPoint = this._getNewTopLeftPoint(center); + + if (!preserveMapOffset) { + L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0)); + } else { + this._initialTopLeftPoint._add(this._getMapPanePos()); + } + + this._tileLayersToLoad = this._tileLayersNum; + + var loading = !this._loaded; + this._loaded = true; + + this.fire('viewreset', {hard: !preserveMapOffset}); + + if (loading) { + this.fire('load'); + this.eachLayer(this._layerAdd, this); + } + + this.fire('move'); + + if (zoomChanged || afterZoomAnim) { + this.fire('zoomend'); + } + + this.fire('moveend', {hard: !preserveMapOffset}); + }, + + _rawPanBy: function (offset) { + L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); + }, + + _getZoomSpan: function () { + return this.getMaxZoom() - this.getMinZoom(); + }, + + _updateZoomLevels: function () { + var i, + minZoom = Infinity, + maxZoom = -Infinity, + oldZoomSpan = this._getZoomSpan(); + + for (i in this._zoomBoundLayers) { + var layer = this._zoomBoundLayers[i]; + if (!isNaN(layer.options.minZoom)) { + minZoom = Math.min(minZoom, layer.options.minZoom); + } + if (!isNaN(layer.options.maxZoom)) { + maxZoom = Math.max(maxZoom, layer.options.maxZoom); + } + } + + if (i === undefined) { // we have no tilelayers + this._layersMaxZoom = this._layersMinZoom = undefined; + } else { + this._layersMaxZoom = maxZoom; + this._layersMinZoom = minZoom; + } + + if (oldZoomSpan !== 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.'); + } + }, + + // map events + + _initEvents: function (onOff) { + if (!L.DomEvent) { return; } + + onOff = onOff || 'on'; + + L.DomEvent[onOff](this._container, 'click', this._onMouseClick, this); + + var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter', + 'mouseleave', 'mousemove', 'contextmenu'], + i, len; + + for (i = 0, len = events.length; i < len; i++) { + L.DomEvent[onOff](this._container, events[i], this._fireMouseEvent, this); + } + + if (this.options.trackResize) { + L.DomEvent[onOff](window, 'resize', this._onResize, this); + } + }, + + _onResize: function () { + L.Util.cancelAnimFrame(this._resizeRequest); + this._resizeRequest = L.Util.requestAnimFrame( + function () { this.invalidateSize({debounceMoveend: true}); }, this, false, this._container); + }, + + _onMouseClick: function (e) { + if (!this._loaded || (!e._simulated && + ((this.dragging && this.dragging.moved()) || + (this.boxZoom && this.boxZoom.moved()))) || + L.DomEvent._skipped(e)) { return; } + + this.fire('preclick'); + this._fireMouseEvent(e); + }, + + _fireMouseEvent: function (e) { + if (!this._loaded || L.DomEvent._skipped(e)) { return; } + + var type = e.type; + + type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type)); + + if (!this.hasEventListeners(type)) { return; } + + if (type === 'contextmenu') { + L.DomEvent.preventDefault(e); + } + + var containerPoint = this.mouseEventToContainerPoint(e), + layerPoint = this.containerPointToLayerPoint(containerPoint), + latlng = this.layerPointToLatLng(layerPoint); + + this.fire(type, { + latlng: latlng, + layerPoint: layerPoint, + containerPoint: containerPoint, + originalEvent: e + }); + }, + + _onTileLayerLoad: function () { + this._tileLayersToLoad--; + if (this._tileLayersNum && !this._tileLayersToLoad) { + this.fire('tilelayersload'); + } + }, + + _clearHandlers: function () { + for (var i = 0, len = this._handlers.length; i < len; i++) { + this._handlers[i].disable(); + } + }, + + whenReady: function (callback, context) { + if (this._loaded) { + callback.call(context || this, this); + } else { + this.on('load', callback, context); + } + return this; + }, + + _layerAdd: function (layer) { + layer.onAdd(this); + this.fire('layeradd', {layer: layer}); + }, + + + // private methods for getting map state + + _getMapPanePos: function () { + return L.DomUtil.getPosition(this._mapPane); + }, + + _moved: function () { + var pos = this._getMapPanePos(); + return pos && !pos.equals([0, 0]); + }, + + _getTopLeftPoint: function () { + return this.getPixelOrigin().subtract(this._getMapPanePos()); + }, + + _getNewTopLeftPoint: function (center, zoom) { + var viewHalf = this.getSize()._divideBy(2); + // TODO round on display, not calculation to increase precision? + return this.project(center, zoom)._subtract(viewHalf)._round(); + }, + + _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) { + var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos()); + return this.project(latlng, newZoom)._subtract(topLeft); + }, + + // layer point of the current center + _getCenterLayerPoint: function () { + return this.containerPointToLayerPoint(this.getSize()._divideBy(2)); + }, + + // offset of the specified place to the current center in pixels + _getCenterOffset: function (latlng) { + return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint()); + }, + + // adjust center for view to get inside bounds + _limitCenter: function (center, zoom, bounds) { + + if (!bounds) { return center; } + + var centerPoint = this.project(center, zoom), + viewHalf = this.getSize().divideBy(2), + viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), + offset = this._getBoundsOffset(viewBounds, bounds, zoom); + + return this.unproject(centerPoint.add(offset), zoom); + }, + + // adjust offset for view to get inside bounds + _limitOffset: function (offset, bounds) { + if (!bounds) { return offset; } + + var viewBounds = this.getPixelBounds(), + newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset)); + + return offset.add(this._getBoundsOffset(newBounds, bounds)); + }, + + // returns offset needed for pxBounds to get inside maxBounds at a specified zoom + _getBoundsOffset: function (pxBounds, maxBounds, zoom) { + var nwOffset = this.project(maxBounds.getNorthWest(), zoom).subtract(pxBounds.min), + seOffset = this.project(maxBounds.getSouthEast(), zoom).subtract(pxBounds.max), + + dx = this._rebound(nwOffset.x, -seOffset.x), + dy = this._rebound(nwOffset.y, -seOffset.y); + + return new L.Point(dx, dy); + }, + + _rebound: function (left, right) { + return left + right > 0 ? + Math.round(left - right) / 2 : + Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right)); + }, + + _limitZoom: function (zoom) { + var min = this.getMinZoom(), + max = this.getMaxZoom(); + + return Math.max(min, Math.min(max, zoom)); + } +}); + +L.map = function (id, options) { + return new L.Map(id, options); +}; + + +/* + * Mercator projection that takes into account that the Earth is not a perfect sphere. + * Less popular than spherical mercator; used by projections like EPSG:3395. + */ + +L.Projection.Mercator = { + MAX_LATITUDE: 85.0840591556, + + R_MINOR: 6356752.314245179, + R_MAJOR: 6378137, + + project: function (latlng) { // (LatLng) -> Point + var d = L.LatLng.DEG_TO_RAD, + max = this.MAX_LATITUDE, + lat = Math.max(Math.min(max, latlng.lat), -max), + r = this.R_MAJOR, + r2 = this.R_MINOR, + x = latlng.lng * d * r, + y = lat * d, + tmp = r2 / r, + eccent = Math.sqrt(1.0 - tmp * tmp), + con = eccent * Math.sin(y); + + con = Math.pow((1 - con) / (1 + con), eccent * 0.5); + + var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con; + y = -r * Math.log(ts); + + return new L.Point(x, y); + }, + + unproject: function (point) { // (Point, Boolean) -> LatLng + var d = L.LatLng.RAD_TO_DEG, + r = this.R_MAJOR, + r2 = this.R_MINOR, + lng = point.x * d / r, + tmp = r2 / r, + eccent = Math.sqrt(1 - (tmp * tmp)), + ts = Math.exp(- point.y / r), + phi = (Math.PI / 2) - 2 * Math.atan(ts), + numIter = 15, + tol = 1e-7, + i = numIter, + dphi = 0.1, + con; + + while ((Math.abs(dphi) > tol) && (--i > 0)) { + con = eccent * Math.sin(phi); + dphi = (Math.PI / 2) - 2 * Math.atan(ts * + Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi; + phi += dphi; + } + + return new L.LatLng(phi * d, lng); + } +}; + + + +L.CRS.EPSG3395 = L.extend({}, L.CRS, { + code: 'EPSG:3395', + + projection: L.Projection.Mercator, + + transformation: (function () { + var m = L.Projection.Mercator, + r = m.R_MAJOR, + scale = 0.5 / (Math.PI * r); + + return new L.Transformation(scale, 0.5, -scale, 0.5); + }()) +}); + + +/* + * L.TileLayer is used for standard xyz-numbered tile layers. + */ + +L.TileLayer = L.Class.extend({ + includes: L.Mixin.Events, + + options: { + minZoom: 0, + maxZoom: 18, + tileSize: 256, + subdomains: 'abc', + errorTileUrl: '', + attribution: '', + zoomOffset: 0, + opacity: 1, + /* + maxNativeZoom: null, + zIndex: null, + tms: false, + continuousWorld: false, + noWrap: false, + zoomReverse: false, + detectRetina: false, + reuseTiles: false, + bounds: false, + */ + unloadInvisibleTiles: L.Browser.mobile, + updateWhenIdle: L.Browser.mobile + }, + + initialize: function (url, options) { + options = L.setOptions(this, options); + + // detecting retina displays, adjusting tileSize and zoom levels + if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) { + + options.tileSize = Math.floor(options.tileSize / 2); + options.zoomOffset++; + + if (options.minZoom > 0) { + options.minZoom--; + } + this.options.maxZoom--; + } + + if (options.bounds) { + options.bounds = L.latLngBounds(options.bounds); + } + + this._url = url; + + var subdomains = this.options.subdomains; + + if (typeof subdomains === 'string') { + this.options.subdomains = subdomains.split(''); + } + }, + + onAdd: function (map) { + this._map = map; + this._animated = map._zoomAnimated; + + // create a container div for tiles + this._initContainer(); + + // set up events + map.on({ + 'viewreset': this._reset, + 'moveend': this._update + }, this); + + if (this._animated) { + map.on({ + 'zoomanim': this._animateZoom, + 'zoomend': this._endZoomAnim + }, this); + } + + if (!this.options.updateWhenIdle) { + this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this); + map.on('move', this._limitedUpdate, this); + } + + this._reset(); + this._update(); + }, + + addTo: function (map) { + map.addLayer(this); + return this; + }, + + onRemove: function (map) { + this._container.parentNode.removeChild(this._container); + + map.off({ + 'viewreset': this._reset, + 'moveend': this._update + }, this); + + if (this._animated) { + map.off({ + 'zoomanim': this._animateZoom, + 'zoomend': this._endZoomAnim + }, this); + } + + if (!this.options.updateWhenIdle) { + map.off('move', this._limitedUpdate, this); + } + + this._container = null; + this._map = null; + }, + + bringToFront: function () { + var pane = this._map._panes.tilePane; + + if (this._container) { + pane.appendChild(this._container); + this._setAutoZIndex(pane, Math.max); + } + + return this; + }, + + bringToBack: function () { + var pane = this._map._panes.tilePane; + + if (this._container) { + pane.insertBefore(this._container, pane.firstChild); + this._setAutoZIndex(pane, Math.min); + } + + return this; + }, + + getAttribution: function () { + return this.options.attribution; + }, + + getContainer: function () { + return this._container; + }, + + setOpacity: function (opacity) { + this.options.opacity = opacity; + + if (this._map) { + this._updateOpacity(); + } + + return this; + }, + + setZIndex: function (zIndex) { + this.options.zIndex = zIndex; + this._updateZIndex(); + + return this; + }, + + setUrl: function (url, noRedraw) { + this._url = url; + + if (!noRedraw) { + this.redraw(); + } + + return this; + }, + + redraw: function () { + if (this._map) { + this._reset({hard: true}); + this._update(); + } + return this; + }, + + _updateZIndex: function () { + if (this._container && this.options.zIndex !== undefined) { + this._container.style.zIndex = this.options.zIndex; + } + }, + + _setAutoZIndex: function (pane, compare) { + + var layers = pane.children, + edgeZIndex = -compare(Infinity, -Infinity), // -Infinity for max, Infinity for min + zIndex, i, len; + + for (i = 0, len = layers.length; i < len; i++) { + + if (layers[i] !== this._container) { + zIndex = parseInt(layers[i].style.zIndex, 10); + + if (!isNaN(zIndex)) { + edgeZIndex = compare(edgeZIndex, zIndex); + } + } + } + + this.options.zIndex = this._container.style.zIndex = + (isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1); + }, + + _updateOpacity: function () { + var i, + tiles = this._tiles; + + if (L.Browser.ielt9) { + for (i in tiles) { + L.DomUtil.setOpacity(tiles[i], this.options.opacity); + } + } else { + L.DomUtil.setOpacity(this._container, this.options.opacity); + } + }, + + _initContainer: function () { + var tilePane = this._map._panes.tilePane; + + if (!this._container) { + this._container = L.DomUtil.create('div', 'leaflet-layer'); + + this._updateZIndex(); + + if (this._animated) { + var className = 'leaflet-tile-container'; + + this._bgBuffer = L.DomUtil.create('div', className, this._container); + this._tileContainer = L.DomUtil.create('div', className, this._container); + + } else { + this._tileContainer = this._container; + } + + tilePane.appendChild(this._container); + + if (this.options.opacity < 1) { + this._updateOpacity(); + } + } + }, + + _reset: function (e) { + for (var key in this._tiles) { + this.fire('tileunload', {tile: this._tiles[key]}); + } + + this._tiles = {}; + this._tilesToLoad = 0; + + if (this.options.reuseTiles) { + this._unusedTiles = []; + } + + this._tileContainer.innerHTML = ''; + + if (this._animated && e && e.hard) { + this._clearBgBuffer(); + } + + this._initContainer(); + }, + + _getTileSize: function () { + var map = this._map, + zoom = map.getZoom() + this.options.zoomOffset, + zoomN = this.options.maxNativeZoom, + tileSize = this.options.tileSize; + + if (zoomN && zoom > zoomN) { + tileSize = Math.round(map.getZoomScale(zoom) / map.getZoomScale(zoomN) * tileSize); + } + + return tileSize; + }, + + _update: function () { + + if (!this._map) { return; } + + var map = this._map, + bounds = map.getPixelBounds(), + zoom = map.getZoom(), + tileSize = this._getTileSize(); + + if (zoom > this.options.maxZoom || zoom < this.options.minZoom) { + return; + } + + var tileBounds = L.bounds( + bounds.min.divideBy(tileSize)._floor(), + bounds.max.divideBy(tileSize)._floor()); + + this._addTilesFromCenterOut(tileBounds); + + if (this.options.unloadInvisibleTiles || this.options.reuseTiles) { + this._removeOtherTiles(tileBounds); + } + }, + + _addTilesFromCenterOut: function (bounds) { + var queue = [], + center = bounds.getCenter(); + + var j, i, point; + + for (j = bounds.min.y; j <= bounds.max.y; j++) { + for (i = bounds.min.x; i <= bounds.max.x; i++) { + point = new L.Point(i, j); + + if (this._tileShouldBeLoaded(point)) { + queue.push(point); + } + } + } + + var tilesToLoad = queue.length; + + if (tilesToLoad === 0) { return; } + + // load tiles in order of their distance to center + queue.sort(function (a, b) { + return a.distanceTo(center) - b.distanceTo(center); + }); + + var fragment = document.createDocumentFragment(); + + // if its the first batch of tiles to load + if (!this._tilesToLoad) { + this.fire('loading'); + } + + this._tilesToLoad += tilesToLoad; + + for (i = 0; i < tilesToLoad; i++) { + this._addTile(queue[i], fragment); + } + + this._tileContainer.appendChild(fragment); + }, + + _tileShouldBeLoaded: function (tilePoint) { + if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) { + return false; // already loaded + } + + var options = this.options; + + if (!options.continuousWorld) { + var limit = this._getWrapTileNum(); + + // don't load if exceeds world bounds + if ((options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit.x)) || + tilePoint.y < 0 || tilePoint.y >= limit.y) { return false; } + } + + if (options.bounds) { + var tileSize = options.tileSize, + nwPoint = tilePoint.multiplyBy(tileSize), + sePoint = nwPoint.add([tileSize, tileSize]), + nw = this._map.unproject(nwPoint), + se = this._map.unproject(sePoint); + + // TODO temporary hack, will be removed after refactoring projections + // https://github.com/Leaflet/Leaflet/issues/1618 + if (!options.continuousWorld && !options.noWrap) { + nw = nw.wrap(); + se = se.wrap(); + } + + if (!options.bounds.intersects([nw, se])) { return false; } + } + + return true; + }, + + _removeOtherTiles: function (bounds) { + var kArr, x, y, key; + + for (key in this._tiles) { + kArr = key.split(':'); + x = parseInt(kArr[0], 10); + y = parseInt(kArr[1], 10); + + // remove tile if it's out of bounds + if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) { + this._removeTile(key); + } + } + }, + + _removeTile: function (key) { + var tile = this._tiles[key]; + + this.fire('tileunload', {tile: tile, url: tile.src}); + + if (this.options.reuseTiles) { + L.DomUtil.removeClass(tile, 'leaflet-tile-loaded'); + this._unusedTiles.push(tile); + + } else if (tile.parentNode === this._tileContainer) { + this._tileContainer.removeChild(tile); + } + + // for https://github.com/CloudMade/Leaflet/issues/137 + if (!L.Browser.android) { + tile.onload = null; + tile.src = L.Util.emptyImageUrl; + } + + delete this._tiles[key]; + }, + + _addTile: function (tilePoint, container) { + var tilePos = this._getTilePos(tilePoint); + + // get unused tile - or create a new tile + var tile = this._getTile(); + + /* + Chrome 20 layouts much faster with top/left (verify with timeline, frames) + Android 4 browser has display issues with top/left and requires transform instead + (other browsers don't currently care) - see debug/hacks/jitter.html for an example + */ + L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome); + + this._tiles[tilePoint.x + ':' + tilePoint.y] = tile; + + this._loadTile(tile, tilePoint); + + if (tile.parentNode !== this._tileContainer) { + container.appendChild(tile); + } + }, + + _getZoomForUrl: function () { + + var options = this.options, + zoom = this._map.getZoom(); + + if (options.zoomReverse) { + zoom = options.maxZoom - zoom; + } + + zoom += options.zoomOffset; + + return options.maxNativeZoom ? Math.min(zoom, options.maxNativeZoom) : zoom; + }, + + _getTilePos: function (tilePoint) { + var origin = this._map.getPixelOrigin(), + tileSize = this._getTileSize(); + + return tilePoint.multiplyBy(tileSize).subtract(origin); + }, + + // image-specific code (override to implement e.g. Canvas or SVG tile layer) + + getTileUrl: function (tilePoint) { + return L.Util.template(this._url, L.extend({ + s: this._getSubdomain(tilePoint), + z: tilePoint.z, + x: tilePoint.x, + y: tilePoint.y + }, this.options)); + }, + + _getWrapTileNum: function () { + var crs = this._map.options.crs, + size = crs.getSize(this._map.getZoom()); + return size.divideBy(this._getTileSize())._floor(); + }, + + _adjustTilePoint: function (tilePoint) { + + var limit = this._getWrapTileNum(); + + // wrap tile coordinates + if (!this.options.continuousWorld && !this.options.noWrap) { + tilePoint.x = ((tilePoint.x % limit.x) + limit.x) % limit.x; + } + + if (this.options.tms) { + tilePoint.y = limit.y - tilePoint.y - 1; + } + + tilePoint.z = this._getZoomForUrl(); + }, + + _getSubdomain: function (tilePoint) { + var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length; + return this.options.subdomains[index]; + }, + + _getTile: function () { + if (this.options.reuseTiles && this._unusedTiles.length > 0) { + var tile = this._unusedTiles.pop(); + this._resetTile(tile); + return tile; + } + return this._createTile(); + }, + + // Override if data stored on a tile needs to be cleaned up before reuse + _resetTile: function (/*tile*/) {}, + + _createTile: function () { + var tile = L.DomUtil.create('img', 'leaflet-tile'); + tile.style.width = tile.style.height = this._getTileSize() + 'px'; + tile.galleryimg = 'no'; + + tile.onselectstart = tile.onmousemove = L.Util.falseFn; + + if (L.Browser.ielt9 && this.options.opacity !== undefined) { + L.DomUtil.setOpacity(tile, this.options.opacity); + } + // without this hack, tiles disappear after zoom on Chrome for Android + // https://github.com/Leaflet/Leaflet/issues/2078 + if (L.Browser.mobileWebkit3d) { + tile.style.WebkitBackfaceVisibility = 'hidden'; + } + return tile; + }, + + _loadTile: function (tile, tilePoint) { + tile._layer = this; + tile.onload = this._tileOnLoad; + tile.onerror = this._tileOnError; + + this._adjustTilePoint(tilePoint); + tile.src = this.getTileUrl(tilePoint); + + this.fire('tileloadstart', { + tile: tile, + url: tile.src + }); + }, + + _tileLoaded: function () { + this._tilesToLoad--; + + if (this._animated) { + L.DomUtil.addClass(this._tileContainer, 'leaflet-zoom-animated'); + } + + if (!this._tilesToLoad) { + this.fire('load'); + + if (this._animated) { + // clear scaled tiles after all new tiles are loaded (for performance) + clearTimeout(this._clearBgBufferTimer); + this._clearBgBufferTimer = setTimeout(L.bind(this._clearBgBuffer, this), 500); + } + } + }, + + _tileOnLoad: function () { + var layer = this._layer; + + //Only if we are loading an actual image + if (this.src !== L.Util.emptyImageUrl) { + L.DomUtil.addClass(this, 'leaflet-tile-loaded'); + + layer.fire('tileload', { + tile: this, + url: this.src + }); + } + + layer._tileLoaded(); + }, + + _tileOnError: function () { + var layer = this._layer; + + layer.fire('tileerror', { + tile: this, + url: this.src + }); + + var newUrl = layer.options.errorTileUrl; + if (newUrl) { + this.src = newUrl; + } + + layer._tileLoaded(); + } +}); + +L.tileLayer = function (url, options) { + return new L.TileLayer(url, options); +}; + + +/* + * L.TileLayer.WMS is used for putting WMS tile layers on the map. + */ + +L.TileLayer.WMS = L.TileLayer.extend({ + + defaultWmsParams: { + service: 'WMS', + request: 'GetMap', + version: '1.1.1', + layers: '', + styles: '', + format: 'image/jpeg', + transparent: false + }, + + initialize: function (url, options) { // (String, Object) + + this._url = url; + + var wmsParams = L.extend({}, this.defaultWmsParams), + tileSize = options.tileSize || this.options.tileSize; + + if (options.detectRetina && L.Browser.retina) { + wmsParams.width = wmsParams.height = tileSize * 2; + } else { + wmsParams.width = wmsParams.height = tileSize; + } + + for (var i in options) { + // all keys that are not TileLayer options go to WMS params + if (!this.options.hasOwnProperty(i) && i !== 'crs') { + wmsParams[i] = options[i]; + } + } + + this.wmsParams = wmsParams; + + L.setOptions(this, options); + }, + + onAdd: function (map) { + + this._crs = this.options.crs || map.options.crs; + + this._wmsVersion = parseFloat(this.wmsParams.version); + + var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs'; + this.wmsParams[projectionKey] = this._crs.code; + + L.TileLayer.prototype.onAdd.call(this, map); + }, + + getTileUrl: function (tilePoint) { // (Point, Number) -> String + + var map = this._map, + tileSize = this.options.tileSize, + + nwPoint = tilePoint.multiplyBy(tileSize), + sePoint = nwPoint.add([tileSize, tileSize]), + + nw = this._crs.project(map.unproject(nwPoint, tilePoint.z)), + se = this._crs.project(map.unproject(sePoint, tilePoint.z)), + bbox = this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ? + [se.y, nw.x, nw.y, se.x].join(',') : + [nw.x, se.y, se.x, nw.y].join(','), + + url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)}); + + return url + L.Util.getParamString(this.wmsParams, url, true) + '&BBOX=' + bbox; + }, + + setParams: function (params, noRedraw) { + + L.extend(this.wmsParams, params); + + if (!noRedraw) { + this.redraw(); + } + + return this; + } +}); + +L.tileLayer.wms = function (url, options) { + return new L.TileLayer.WMS(url, options); +}; + + +/* + * L.TileLayer.Canvas is a class that you can use as a base for creating + * dynamically drawn Canvas-based tile layers. + */ + +L.TileLayer.Canvas = L.TileLayer.extend({ + options: { + async: false + }, + + initialize: function (options) { + L.setOptions(this, options); + }, + + redraw: function () { + if (this._map) { + this._reset({hard: true}); + this._update(); + } + + for (var i in this._tiles) { + this._redrawTile(this._tiles[i]); + } + return this; + }, + + _redrawTile: function (tile) { + this.drawTile(tile, tile._tilePoint, this._map._zoom); + }, + + _createTile: function () { + var tile = L.DomUtil.create('canvas', 'leaflet-tile'); + tile.width = tile.height = this.options.tileSize; + tile.onselectstart = tile.onmousemove = L.Util.falseFn; + return tile; + }, + + _loadTile: function (tile, tilePoint) { + tile._layer = this; + tile._tilePoint = tilePoint; + + this._redrawTile(tile); + + if (!this.options.async) { + this.tileDrawn(tile); + } + }, + + drawTile: function (/*tile, tilePoint*/) { + // override with rendering code + }, + + tileDrawn: function (tile) { + this._tileOnLoad.call(tile); + } +}); + + +L.tileLayer.canvas = function (options) { + return new L.TileLayer.Canvas(options); +}; + + +/* + * L.ImageOverlay is used to overlay images over the map (to specific geographical bounds). + */ + +L.ImageOverlay = L.Class.extend({ + includes: L.Mixin.Events, + + options: { + opacity: 1 + }, + + initialize: function (url, bounds, options) { // (String, LatLngBounds, Object) + this._url = url; + this._bounds = L.latLngBounds(bounds); + + L.setOptions(this, options); + }, + + onAdd: function (map) { + this._map = map; + + if (!this._image) { + this._initImage(); + } + + map._panes.overlayPane.appendChild(this._image); + + map.on('viewreset', this._reset, this); + + if (map.options.zoomAnimation && L.Browser.any3d) { + map.on('zoomanim', this._animateZoom, this); + } + + this._reset(); + }, + + onRemove: function (map) { + map.getPanes().overlayPane.removeChild(this._image); + + map.off('viewreset', this._reset, this); + + if (map.options.zoomAnimation) { + map.off('zoomanim', this._animateZoom, this); + } + }, + + addTo: function (map) { + map.addLayer(this); + return this; + }, + + setOpacity: function (opacity) { + this.options.opacity = opacity; + this._updateOpacity(); + return this; + }, + + // TODO remove bringToFront/bringToBack duplication from TileLayer/Path + bringToFront: function () { + if (this._image) { + this._map._panes.overlayPane.appendChild(this._image); + } + return this; + }, + + bringToBack: function () { + var pane = this._map._panes.overlayPane; + if (this._image) { + pane.insertBefore(this._image, pane.firstChild); + } + return this; + }, + + setUrl: function (url) { + this._url = url; + this._image.src = this._url; + }, + + getAttribution: function () { + return this.options.attribution; + }, + + _initImage: function () { + this._image = L.DomUtil.create('img', 'leaflet-image-layer'); + + if (this._map.options.zoomAnimation && L.Browser.any3d) { + L.DomUtil.addClass(this._image, 'leaflet-zoom-animated'); + } else { + L.DomUtil.addClass(this._image, 'leaflet-zoom-hide'); + } + + this._updateOpacity(); + + //TODO createImage util method to remove duplication + L.extend(this._image, { + galleryimg: 'no', + onselectstart: L.Util.falseFn, + onmousemove: L.Util.falseFn, + onload: L.bind(this._onImageLoad, this), + src: this._url + }); + }, + + _animateZoom: function (e) { + var map = this._map, + image = this._image, + scale = map.getZoomScale(e.zoom), + nw = this._bounds.getNorthWest(), + se = this._bounds.getSouthEast(), + + topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center), + size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft), + origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale))); + + image.style[L.DomUtil.TRANSFORM] = + L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') '; + }, + + _reset: function () { + var image = this._image, + topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()), + size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft); + + L.DomUtil.setPosition(image, topLeft); + + image.style.width = size.x + 'px'; + image.style.height = size.y + 'px'; + }, + + _onImageLoad: function () { + this.fire('load'); + }, + + _updateOpacity: function () { + L.DomUtil.setOpacity(this._image, this.options.opacity); + } +}); + +L.imageOverlay = function (url, bounds, options) { + return new L.ImageOverlay(url, bounds, options); +}; + + +/* + * L.Icon is an image-based icon class that you can use with L.Marker for custom markers. + */ + +L.Icon = L.Class.extend({ + options: { + /* + iconUrl: (String) (required) + iconRetinaUrl: (String) (optional, used for retina devices if detected) + iconSize: (Point) (can be set through CSS) + iconAnchor: (Point) (centered by default, can be set in CSS with negative margins) + popupAnchor: (Point) (if not specified, popup opens in the anchor point) + shadowUrl: (String) (no shadow by default) + shadowRetinaUrl: (String) (optional, used for retina devices if detected) + shadowSize: (Point) + shadowAnchor: (Point) + */ + className: '' + }, + + initialize: function (options) { + L.setOptions(this, options); + }, + + createIcon: function (oldIcon) { + return this._createIcon('icon', oldIcon); + }, + + createShadow: function (oldIcon) { + return this._createIcon('shadow', oldIcon); + }, + + _createIcon: function (name, oldIcon) { + var src = this._getIconUrl(name); + + if (!src) { + if (name === 'icon') { + throw new Error('iconUrl not set in Icon options (see the docs).'); + } + return null; + } + + var img; + if (!oldIcon || oldIcon.tagName !== 'IMG') { + img = this._createImg(src); + } else { + img = this._createImg(src, oldIcon); + } + this._setIconStyles(img, name); + + return img; + }, + + _setIconStyles: function (img, name) { + var options = this.options, + size = L.point(options[name + 'Size']), + anchor; + + if (name === 'shadow') { + anchor = L.point(options.shadowAnchor || options.iconAnchor); + } else { + anchor = L.point(options.iconAnchor); + } + + if (!anchor && size) { + anchor = size.divideBy(2, true); + } + + img.className = 'leaflet-marker-' + name + ' ' + options.className; + + if (anchor) { + img.style.marginLeft = (-anchor.x) + 'px'; + img.style.marginTop = (-anchor.y) + 'px'; + } + + if (size) { + img.style.width = size.x + 'px'; + img.style.height = size.y + 'px'; + } + }, + + _createImg: function (src, el) { + el = el || document.createElement('img'); + el.src = src; + return el; + }, + + _getIconUrl: function (name) { + if (L.Browser.retina && this.options[name + 'RetinaUrl']) { + return this.options[name + 'RetinaUrl']; + } + return this.options[name + 'Url']; + } +}); + +L.icon = function (options) { + return new L.Icon(options); +}; + + +/* + * 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'; + } + } +}()); + + +/* + * L.Marker is used to display clickable/draggable icons on the map. + */ + +L.Marker = L.Class.extend({ + + includes: L.Mixin.Events, + + options: { + icon: new L.Icon.Default(), + title: '', + alt: '', + clickable: true, + draggable: false, + keyboard: true, + zIndexOffset: 0, + opacity: 1, + riseOnHover: false, + riseOffset: 250 + }, + + initialize: function (latlng, options) { + L.setOptions(this, options); + this._latlng = L.latLng(latlng); + }, + + onAdd: function (map) { + this._map = map; + + map.on('viewreset', this.update, this); + + this._initIcon(); + this.update(); + this.fire('add'); + + if (map.options.zoomAnimation && map.options.markerZoomAnimation) { + map.on('zoomanim', this._animateZoom, this); + } + }, + + addTo: function (map) { + map.addLayer(this); + return this; + }, + + onRemove: function (map) { + if (this.dragging) { + this.dragging.disable(); + } + + this._removeIcon(); + this._removeShadow(); + + this.fire('remove'); + + map.off({ + 'viewreset': this.update, + 'zoomanim': this._animateZoom + }, this); + + this._map = null; + }, + + getLatLng: function () { + return this._latlng; + }, + + setLatLng: function (latlng) { + this._latlng = L.latLng(latlng); + + this.update(); + + return this.fire('move', { latlng: this._latlng }); + }, + + setZIndexOffset: function (offset) { + this.options.zIndexOffset = offset; + this.update(); + + return this; + }, + + setIcon: function (icon) { + + this.options.icon = icon; + + if (this._map) { + this._initIcon(); + this.update(); + } + + if (this._popup) { + this.bindPopup(this._popup); + } + + return this; + }, + + update: function () { + if (this._icon) { + var pos = this._map.latLngToLayerPoint(this._latlng).round(); + this._setPos(pos); + } + + return this; + }, + + _initIcon: function () { + var options = this.options, + map = this._map, + animation = (map.options.zoomAnimation && map.options.markerZoomAnimation), + classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide'; + + var icon = options.icon.createIcon(this._icon), + addIcon = false; + + // if we're not reusing the icon, remove the old one and init new one + if (icon !== this._icon) { + if (this._icon) { + this._removeIcon(); + } + addIcon = true; + + if (options.title) { + icon.title = options.title; + } + + if (options.alt) { + icon.alt = options.alt; + } + } + + L.DomUtil.addClass(icon, classToAdd); + + if (options.keyboard) { + icon.tabIndex = '0'; + } + + this._icon = icon; + + this._initInteraction(); + + if (options.riseOnHover) { + L.DomEvent + .on(icon, 'mouseover', this._bringToFront, this) + .on(icon, 'mouseout', this._resetZIndex, this); + } + + var newShadow = options.icon.createShadow(this._shadow), + addShadow = false; + + if (newShadow !== this._shadow) { + this._removeShadow(); + addShadow = true; + } + + if (newShadow) { + L.DomUtil.addClass(newShadow, classToAdd); + } + this._shadow = newShadow; + + + if (options.opacity < 1) { + this._updateOpacity(); + } + + + var panes = this._map._panes; + + if (addIcon) { + panes.markerPane.appendChild(this._icon); + } + + if (newShadow && addShadow) { + panes.shadowPane.appendChild(this._shadow); + } + }, + + _removeIcon: function () { + if (this.options.riseOnHover) { + L.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 () { + if (this._shadow) { + this._map._panes.shadowPane.removeChild(this._shadow); + } + this._shadow = null; + }, + + _setPos: function (pos) { + L.DomUtil.setPosition(this._icon, pos); + + if (this._shadow) { + L.DomUtil.setPosition(this._shadow, pos); + } + + this._zIndex = pos.y + this.options.zIndexOffset; + + this._resetZIndex(); + }, + + _updateZIndex: function (offset) { + this._icon.style.zIndex = this._zIndex + offset; + }, + + _animateZoom: function (opt) { + var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); + + this._setPos(pos); + }, + + _initInteraction: function () { + + if (!this.options.clickable) { return; } + + // TODO refactor into something shared with Map/Path/etc. to DRY it up + + var icon = this._icon, + events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu']; + + L.DomUtil.addClass(icon, 'leaflet-clickable'); + L.DomEvent.on(icon, 'click', this._onMouseClick, this); + L.DomEvent.on(icon, 'keypress', this._onKeyPress, this); + + for (var i = 0; i < events.length; i++) { + L.DomEvent.on(icon, events[i], this._fireMouseEvent, this); + } + + if (L.Handler.MarkerDrag) { + this.dragging = new L.Handler.MarkerDrag(this); + + if (this.options.draggable) { + this.dragging.enable(); + } + } + }, + + _onMouseClick: function (e) { + var wasDragged = this.dragging && this.dragging.moved(); + + if (this.hasEventListeners(e.type) || wasDragged) { + L.DomEvent.stopPropagation(e); + } + + if (wasDragged) { return; } + + if ((!this.dragging || !this.dragging._enabled) && this._map.dragging && this._map.dragging.moved()) { return; } + + this.fire(e.type, { + originalEvent: e, + latlng: this._latlng + }); + }, + + _onKeyPress: function (e) { + if (e.keyCode === 13) { + this.fire('click', { + originalEvent: e, + latlng: this._latlng + }); + } + }, + + _fireMouseEvent: function (e) { + + this.fire(e.type, { + originalEvent: e, + latlng: this._latlng + }); + + // TODO proper custom event propagation + // this line will always be called if marker is in a FeatureGroup + if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) { + L.DomEvent.preventDefault(e); + } + if (e.type !== 'mousedown') { + L.DomEvent.stopPropagation(e); + } else { + L.DomEvent.preventDefault(e); + } + }, + + setOpacity: function (opacity) { + this.options.opacity = opacity; + if (this._map) { + this._updateOpacity(); + } + + return this; + }, + + _updateOpacity: function () { + L.DomUtil.setOpacity(this._icon, this.options.opacity); + if (this._shadow) { + L.DomUtil.setOpacity(this._shadow, this.options.opacity); + } + }, + + _bringToFront: function () { + this._updateZIndex(this.options.riseOffset); + }, + + _resetZIndex: function () { + this._updateZIndex(0); + } +}); + +L.marker = function (latlng, options) { + return new L.Marker(latlng, options); +}; + + +/* + * 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); +}; + + +/* + * L.Popup is used for displaying popups on the map. + */ + +L.Map.mergeOptions({ + closePopupOnClick: true +}); + +L.Popup = L.Class.extend({ + includes: L.Mixin.Events, + + options: { + minWidth: 50, + maxWidth: 300, + // maxHeight: null, + autoPan: true, + closeButton: true, + offset: [0, 7], + autoPanPadding: [5, 5], + // autoPanPaddingTopLeft: null, + // autoPanPaddingBottomRight: null, + keepInView: false, + className: '', + zoomAnimation: true + }, + + initialize: function (options, source) { + L.setOptions(this, options); + + this._source = source; + this._animated = L.Browser.any3d && this.options.zoomAnimation; + this._isOpen = false; + }, + + onAdd: function (map) { + this._map = map; + + if (!this._container) { + this._initLayout(); + } + + var animFade = map.options.fadeAnimation; + + if (animFade) { + L.DomUtil.setOpacity(this._container, 0); + } + map._panes.popupPane.appendChild(this._container); + + map.on(this._getEvents(), this); + + this.update(); + + if (animFade) { + L.DomUtil.setOpacity(this._container, 1); + } + + this.fire('open'); + + map.fire('popupopen', {popup: this}); + + if (this._source) { + this._source.fire('popupopen', {popup: this}); + } + }, + + addTo: function (map) { + map.addLayer(this); + return this; + }, + + openOn: function (map) { + map.openPopup(this); + return this; + }, + + onRemove: function (map) { + map._panes.popupPane.removeChild(this._container); + + L.Util.falseFn(this._container.offsetWidth); // force reflow + + map.off(this._getEvents(), this); + + if (map.options.fadeAnimation) { + L.DomUtil.setOpacity(this._container, 0); + } + + this._map = null; + + this.fire('close'); + + map.fire('popupclose', {popup: this}); + + if (this._source) { + this._source.fire('popupclose', {popup: this}); + } + }, + + getLatLng: function () { + return this._latlng; + }, + + setLatLng: function (latlng) { + this._latlng = L.latLng(latlng); + if (this._map) { + this._updatePosition(); + this._adjustPan(); + } + return this; + }, + + getContent: function () { + return this._content; + }, + + setContent: function (content) { + this._content = content; + this.update(); + return this; + }, + + update: function () { + if (!this._map) { return; } + + this._container.style.visibility = 'hidden'; + + this._updateContent(); + this._updateLayout(); + this._updatePosition(); + + this._container.style.visibility = ''; + + this._adjustPan(); + }, + + _getEvents: function () { + var events = { + viewreset: this._updatePosition + }; + + if (this._animated) { + events.zoomanim = this._zoomAnimation; + } + if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) { + events.preclick = this._close; + } + if (this.options.keepInView) { + events.moveend = this._adjustPan; + } + + return events; + }, + + _close: function () { + if (this._map) { + this._map.closePopup(this); + } + }, + + _initLayout: function () { + var prefix = 'leaflet-popup', + containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-' + + (this._animated ? 'animated' : 'hide'), + container = this._container = L.DomUtil.create('div', containerClass), + closeButton; + + if (this.options.closeButton) { + closeButton = this._closeButton = + L.DomUtil.create('a', prefix + '-close-button', container); + closeButton.href = '#close'; + closeButton.innerHTML = '×'; + L.DomEvent.disableClickPropagation(closeButton); + + L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this); + } + + var wrapper = this._wrapper = + L.DomUtil.create('div', prefix + '-content-wrapper', container); + L.DomEvent.disableClickPropagation(wrapper); + + this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper); + + L.DomEvent.disableScrollPropagation(this._contentNode); + L.DomEvent.on(wrapper, 'contextmenu', L.DomEvent.stopPropagation); + + this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container); + this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer); + }, + + _updateContent: function () { + if (!this._content) { return; } + + if (typeof this._content === 'string') { + this._contentNode.innerHTML = this._content; + } else { + while (this._contentNode.hasChildNodes()) { + this._contentNode.removeChild(this._contentNode.firstChild); + } + this._contentNode.appendChild(this._content); + } + this.fire('contentupdate'); + }, + + _updateLayout: function () { + var container = this._contentNode, + style = container.style; + + style.width = ''; + style.whiteSpace = 'nowrap'; + + var width = container.offsetWidth; + width = Math.min(width, this.options.maxWidth); + width = Math.max(width, this.options.minWidth); + + style.width = (width + 1) + 'px'; + style.whiteSpace = ''; + + style.height = ''; + + var height = container.offsetHeight, + maxHeight = this.options.maxHeight, + scrolledClass = 'leaflet-popup-scrolled'; + + if (maxHeight && height > maxHeight) { + style.height = maxHeight + 'px'; + L.DomUtil.addClass(container, scrolledClass); + } else { + L.DomUtil.removeClass(container, scrolledClass); + } + + this._containerWidth = this._container.offsetWidth; + }, + + _updatePosition: function () { + if (!this._map) { return; } + + var pos = this._map.latLngToLayerPoint(this._latlng), + animated = this._animated, + offset = L.point(this.options.offset); + + if (animated) { + L.DomUtil.setPosition(this._container, pos); + } + + this._containerBottom = -offset.y - (animated ? 0 : pos.y); + this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (animated ? 0 : pos.x); + + // bottom position the popup in case the height of the popup changes (images loading etc) + this._container.style.bottom = this._containerBottom + 'px'; + this._container.style.left = this._containerLeft + 'px'; + }, + + _zoomAnimation: function (opt) { + var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center); + + L.DomUtil.setPosition(this._container, pos); + }, + + _adjustPan: function () { + if (!this.options.autoPan) { return; } + + var map = this._map, + containerHeight = this._container.offsetHeight, + containerWidth = this._containerWidth, + + layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom); + + if (this._animated) { + layerPos._add(L.DomUtil.getPosition(this._container)); + } + + var containerPos = map.layerPointToContainerPoint(layerPos), + padding = L.point(this.options.autoPanPadding), + paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding), + paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding), + size = map.getSize(), + dx = 0, + dy = 0; + + if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right + dx = containerPos.x + containerWidth - size.x + paddingBR.x; + } + if (containerPos.x - dx - paddingTL.x < 0) { // left + dx = containerPos.x - paddingTL.x; + } + if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom + dy = containerPos.y + containerHeight - size.y + paddingBR.y; + } + if (containerPos.y - dy - paddingTL.y < 0) { // top + dy = containerPos.y - paddingTL.y; + } + + if (dx || dy) { + map + .fire('autopanstart') + .panBy([dx, dy]); + } + }, + + _onCloseButtonClick: function (e) { + this._close(); + L.DomEvent.stop(e); + } +}); + +L.popup = function (options, source) { + return new L.Popup(options, source); +}; + + +L.Map.include({ + openPopup: function (popup, latlng, options) { // (Popup) or (String || HTMLElement, LatLng[, Object]) + this.closePopup(); + + if (!(popup instanceof L.Popup)) { + var content = popup; + + popup = new L.Popup(options) + .setLatLng(latlng) + .setContent(content); + } + popup._isOpen = true; + + this._popup = popup; + return this.addLayer(popup); + }, + + closePopup: function (popup) { + if (!popup || popup === this._popup) { + popup = this._popup; + this._popup = null; + } + if (popup) { + this.removeLayer(popup); + popup._isOpen = false; + } + return this; + } +}); + + +/* + * Popup extension to L.Marker, adding popup-related methods. + */ + +L.Marker.include({ + openPopup: function () { + if (this._popup && this._map && !this._map.hasLayer(this._popup)) { + this._popup.setLatLng(this._latlng); + this._map.openPopup(this._popup); + } + + return this; + }, + + closePopup: function () { + if (this._popup) { + this._popup._close(); + } + return this; + }, + + togglePopup: function () { + if (this._popup) { + if (this._popup._isOpen) { + this.closePopup(); + } else { + this.openPopup(); + } + } + return this; + }, + + bindPopup: function (content, options) { + var anchor = L.point(this.options.icon.options.popupAnchor || [0, 0]); + + anchor = anchor.add(L.Popup.prototype.options.offset); + + if (options && options.offset) { + anchor = anchor.add(options.offset); + } + + options = L.extend({offset: anchor}, options); + + if (!this._popupHandlersAdded) { + this + .on('click', this.togglePopup, this) + .on('remove', this.closePopup, this) + .on('move', this._movePopup, this); + this._popupHandlersAdded = true; + } + + if (content instanceof L.Popup) { + L.setOptions(content, options); + this._popup = content; + } else { + this._popup = new L.Popup(options, this) + .setContent(content); + } + + return this; + }, + + setPopupContent: function (content) { + if (this._popup) { + this._popup.setContent(content); + } + return this; + }, + + unbindPopup: function () { + if (this._popup) { + this._popup = null; + this + .off('click', this.togglePopup, this) + .off('remove', this.closePopup, this) + .off('move', this._movePopup, this); + this._popupHandlersAdded = false; + } + return this; + }, + + getPopup: function () { + return this._popup; + }, + + _movePopup: function (e) { + this._popup.setLatLng(e.latlng); + } +}); + + +/* + * L.LayerGroup is a class to combine several layers into one so that + * you can manipulate the group (e.g. add/remove it) as one layer. + */ + +L.LayerGroup = L.Class.extend({ + initialize: function (layers) { + this._layers = {}; + + var i, len; + + if (layers) { + for (i = 0, len = layers.length; i < len; i++) { + this.addLayer(layers[i]); + } + } + }, + + addLayer: function (layer) { + var id = this.getLayerId(layer); + + this._layers[id] = layer; + + if (this._map) { + this._map.addLayer(layer); + } + + return this; + }, + + removeLayer: function (layer) { + var id = layer in this._layers ? layer : this.getLayerId(layer); + + if (this._map && this._layers[id]) { + this._map.removeLayer(this._layers[id]); + } + + delete this._layers[id]; + + return this; + }, + + hasLayer: function (layer) { + if (!layer) { return false; } + + return (layer in this._layers || this.getLayerId(layer) in this._layers); + }, + + clearLayers: function () { + this.eachLayer(this.removeLayer, this); + return this; + }, + + invoke: function (methodName) { + var args = Array.prototype.slice.call(arguments, 1), + i, layer; + + for (i in this._layers) { + layer = this._layers[i]; + + if (layer[methodName]) { + layer[methodName].apply(layer, args); + } + } + + return this; + }, + + onAdd: function (map) { + this._map = map; + this.eachLayer(map.addLayer, map); + }, + + onRemove: function (map) { + this.eachLayer(map.removeLayer, map); + this._map = null; + }, + + addTo: function (map) { + map.addLayer(this); + return this; + }, + + eachLayer: function (method, context) { + for (var i in this._layers) { + method.call(context, this._layers[i]); + } + return this; + }, + + getLayer: function (id) { + return this._layers[id]; + }, + + getLayers: function () { + var layers = []; + + for (var i in this._layers) { + layers.push(this._layers[i]); + } + return layers; + }, + + setZIndex: function (zIndex) { + return this.invoke('setZIndex', zIndex); + }, + + getLayerId: function (layer) { + return L.stamp(layer); + } +}); + +L.layerGroup = function (layers) { + return new L.LayerGroup(layers); +}; + + +/* + * L.FeatureGroup extends L.LayerGroup by introducing mouse events and additional methods + * shared between a group of interactive layers (like vectors or markers). + */ + +L.FeatureGroup = L.LayerGroup.extend({ + includes: L.Mixin.Events, + + statics: { + EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose' + }, + + addLayer: function (layer) { + if (this.hasLayer(layer)) { + return this; + } + + if ('on' in layer) { + layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this); + } + + L.LayerGroup.prototype.addLayer.call(this, layer); + + if (this._popupContent && layer.bindPopup) { + layer.bindPopup(this._popupContent, this._popupOptions); + } + + return this.fire('layeradd', {layer: layer}); + }, + + removeLayer: function (layer) { + if (!this.hasLayer(layer)) { + return this; + } + if (layer in this._layers) { + layer = this._layers[layer]; + } + + layer.off(L.FeatureGroup.EVENTS, this._propagateEvent, this); + + L.LayerGroup.prototype.removeLayer.call(this, layer); + + if (this._popupContent) { + this.invoke('unbindPopup'); + } + + return this.fire('layerremove', {layer: layer}); + }, + + bindPopup: function (content, options) { + this._popupContent = content; + this._popupOptions = options; + return this.invoke('bindPopup', content, options); + }, + + openPopup: function (latlng) { + // open popup on the first layer + for (var id in this._layers) { + this._layers[id].openPopup(latlng); + break; + } + return this; + }, + + setStyle: function (style) { + return this.invoke('setStyle', style); + }, + + bringToFront: function () { + return this.invoke('bringToFront'); + }, + + bringToBack: function () { + return this.invoke('bringToBack'); + }, + + getBounds: function () { + var bounds = new L.LatLngBounds(); + + this.eachLayer(function (layer) { + bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds()); + }); + + return bounds; + }, + + _propagateEvent: function (e) { + e = L.extend({ + layer: e.target, + target: this + }, e); + this.fire(e.type, e); + } +}); + +L.featureGroup = function (layers) { + return new L.FeatureGroup(layers); +}; + + +/* + * L.Path is a base class for rendering vector paths on a map. Inherited by Polyline, Circle, etc. + */ + +L.Path = L.Class.extend({ + includes: [L.Mixin.Events], + + statics: { + // how much to extend the clip area around the map view + // (relative to its size, e.g. 0.5 is half the screen in each direction) + // set it so that SVG element doesn't exceed 1280px (vectors flicker on dragend if it is) + CLIP_PADDING: (function () { + var max = L.Browser.mobile ? 1280 : 2000, + target = (max / Math.max(window.outerWidth, window.outerHeight) - 1) / 2; + return Math.max(0, Math.min(0.5, target)); + })() + }, + + options: { + stroke: true, + color: '#0033ff', + dashArray: null, + lineCap: null, + lineJoin: null, + weight: 5, + opacity: 0.5, + + fill: false, + fillColor: null, //same as color by default + fillOpacity: 0.2, + + clickable: true + }, + + initialize: function (options) { + L.setOptions(this, options); + }, + + onAdd: function (map) { + this._map = map; + + if (!this._container) { + this._initElements(); + this._initEvents(); + } + + this.projectLatlngs(); + this._updatePath(); + + if (this._container) { + this._map._pathRoot.appendChild(this._container); + } + + this.fire('add'); + + map.on({ + 'viewreset': this.projectLatlngs, + 'moveend': this._updatePath + }, this); + }, + + addTo: function (map) { + map.addLayer(this); + return this; + }, + + onRemove: function (map) { + map._pathRoot.removeChild(this._container); + + // Need to fire remove event before we set _map to null as the event hooks might need the object + this.fire('remove'); + this._map = null; + + if (L.Browser.vml) { + this._container = null; + this._stroke = null; + this._fill = null; + } + + map.off({ + 'viewreset': this.projectLatlngs, + 'moveend': this._updatePath + }, this); + }, + + projectLatlngs: function () { + // do all projection stuff here + }, + + setStyle: function (style) { + L.setOptions(this, style); + + if (this._container) { + this._updateStyle(); + } + + return this; + }, + + redraw: function () { + if (this._map) { + this.projectLatlngs(); + this._updatePath(); + } + return this; + } +}); + +L.Map.include({ + _updatePathViewport: function () { + var p = L.Path.CLIP_PADDING, + size = this.getSize(), + panePos = L.DomUtil.getPosition(this._mapPane), + min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)._round()), + max = min.add(size.multiplyBy(1 + p * 2)._round()); + + this._pathViewport = new L.Bounds(min, max); + } +}); + + +/* + * Extends L.Path with SVG-specific rendering code. + */ + +L.Path.SVG_NS = 'http://www.w3.org/2000/svg'; + +L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect); + +L.Path = L.Path.extend({ + statics: { + SVG: L.Browser.svg + }, + + bringToFront: function () { + var root = this._map._pathRoot, + path = this._container; + + if (path && root.lastChild !== path) { + root.appendChild(path); + } + return this; + }, + + bringToBack: function () { + var root = this._map._pathRoot, + path = this._container, + first = root.firstChild; + + if (path && first !== path) { + root.insertBefore(path, first); + } + return this; + }, + + getPathString: function () { + // form path string here + }, + + _createElement: function (name) { + return document.createElementNS(L.Path.SVG_NS, name); + }, + + _initElements: function () { + this._map._initPathRoot(); + this._initPath(); + this._initStyle(); + }, + + _initPath: function () { + this._container = this._createElement('g'); + + this._path = this._createElement('path'); + + if (this.options.className) { + L.DomUtil.addClass(this._path, this.options.className); + } + + this._container.appendChild(this._path); + }, + + _initStyle: function () { + if (this.options.stroke) { + this._path.setAttribute('stroke-linejoin', 'round'); + this._path.setAttribute('stroke-linecap', 'round'); + } + if (this.options.fill) { + this._path.setAttribute('fill-rule', 'evenodd'); + } + if (this.options.pointerEvents) { + this._path.setAttribute('pointer-events', this.options.pointerEvents); + } + if (!this.options.clickable && !this.options.pointerEvents) { + this._path.setAttribute('pointer-events', 'none'); + } + this._updateStyle(); + }, + + _updateStyle: function () { + if (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); + if (this.options.dashArray) { + this._path.setAttribute('stroke-dasharray', this.options.dashArray); + } else { + this._path.removeAttribute('stroke-dasharray'); + } + if (this.options.lineCap) { + this._path.setAttribute('stroke-linecap', this.options.lineCap); + } + if (this.options.lineJoin) { + this._path.setAttribute('stroke-linejoin', this.options.lineJoin); + } + } else { + this._path.setAttribute('stroke', 'none'); + } + if (this.options.fill) { + this._path.setAttribute('fill', this.options.fillColor || this.options.color); + this._path.setAttribute('fill-opacity', this.options.fillOpacity); + } else { + this._path.setAttribute('fill', 'none'); + } + }, + + _updatePath: function () { + var str = this.getPathString(); + if (!str) { + // fix webkit empty string parsing bug + str = 'M0 0'; + } + this._path.setAttribute('d', str); + }, + + // TODO remove duplication with L.Map + _initEvents: function () { + if (this.options.clickable) { + if (L.Browser.svg || !L.Browser.vml) { + L.DomUtil.addClass(this._path, 'leaflet-clickable'); + } + + L.DomEvent.on(this._container, 'click', this._onMouseClick, this); + + var events = ['dblclick', 'mousedown', 'mouseover', + 'mouseout', 'mousemove', 'contextmenu']; + for (var i = 0; i < events.length; i++) { + L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this); + } + } + }, + + _onMouseClick: function (e) { + if (this._map.dragging && this._map.dragging.moved()) { return; } + + this._fireMouseEvent(e); + }, + + _fireMouseEvent: function (e) { + if (!this.hasEventListeners(e.type)) { return; } + + var map = this._map, + containerPoint = map.mouseEventToContainerPoint(e), + layerPoint = map.containerPointToLayerPoint(containerPoint), + latlng = map.layerPointToLatLng(layerPoint); + + this.fire(e.type, { + latlng: latlng, + layerPoint: layerPoint, + containerPoint: containerPoint, + originalEvent: e + }); + + if (e.type === 'contextmenu') { + L.DomEvent.preventDefault(e); + } + if (e.type !== 'mousemove') { + L.DomEvent.stopPropagation(e); + } + } +}); + +L.Map.include({ + _initPathRoot: function () { + if (!this._pathRoot) { + this._pathRoot = L.Path.prototype._createElement('svg'); + this._panes.overlayPane.appendChild(this._pathRoot); + + if (this.options.zoomAnimation && L.Browser.any3d) { + L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-animated'); + + this.on({ + 'zoomanim': this._animatePathZoom, + 'zoomend': this._endPathZoom + }); + } else { + L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-hide'); + } + + this.on('moveend', this._updateSvgViewport); + this._updateSvgViewport(); + } + }, + + _animatePathZoom: function (e) { + var scale = this.getZoomScale(e.zoom), + offset = this._getCenterOffset(e.center)._multiplyBy(-scale)._add(this._pathViewport.min); + + this._pathRoot.style[L.DomUtil.TRANSFORM] = + L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ') '; + + this._pathZooming = true; + }, + + _endPathZoom: function () { + this._pathZooming = false; + }, + + _updateSvgViewport: function () { + + if (this._pathZooming) { + // Do not update SVGs while a zoom animation is going on otherwise the animation will break. + // When the zoom animation ends we will be updated again anyway + // This fixes the case where you do a momentum move and zoom while the move is still ongoing. + return; + } + + this._updatePathViewport(); + + var vp = this._pathViewport, + min = vp.min, + max = vp.max, + width = max.x - min.x, + height = max.y - min.y, + root = this._pathRoot, + pane = this._panes.overlayPane; + + // Hack to make flicker on drag end on mobile webkit less irritating + if (L.Browser.mobileWebkit) { + pane.removeChild(root); + } + + L.DomUtil.setPosition(root, min); + root.setAttribute('width', width); + root.setAttribute('height', height); + root.setAttribute('viewBox', [min.x, min.y, width, height].join(' ')); + + if (L.Browser.mobileWebkit) { + pane.appendChild(root); + } + } +}); + + +/* + * Popup extension to L.Path (polylines, polygons, circles), adding popup-related methods. + */ + +L.Path.include({ + + bindPopup: function (content, options) { + + if (content instanceof L.Popup) { + this._popup = content; + } else { + if (!this._popup || options) { + this._popup = new L.Popup(options, this); + } + this._popup.setContent(content); + } + + if (!this._popupHandlersAdded) { + this + .on('click', this._openPopup, this) + .on('remove', this.closePopup, this); + + this._popupHandlersAdded = true; + } + + return this; + }, + + unbindPopup: function () { + if (this._popup) { + this._popup = null; + this + .off('click', this._openPopup) + .off('remove', this.closePopup); + + this._popupHandlersAdded = false; + } + return this; + }, + + openPopup: function (latlng) { + + if (this._popup) { + // open the popup from one of the path's points if not specified + latlng = latlng || this._latlng || + this._latlngs[Math.floor(this._latlngs.length / 2)]; + + this._openPopup({latlng: latlng}); + } + + return this; + }, + + closePopup: function () { + if (this._popup) { + this._popup._close(); + } + return this; + }, + + _openPopup: function (e) { + this._popup.setLatLng(e.latlng); + this._map.openPopup(this._popup); + } +}); + + +/* + * Vector rendering for IE6-8 through VML. + * Thanks to Dmitry Baranovsky and his Raphael library for inspiration! + */ + +L.Browser.vml = !L.Browser.svg && (function () { + try { + var div = document.createElement('div'); + div.innerHTML = ''; + + var shape = div.firstChild; + shape.style.behavior = 'url(#default#VML)'; + + return shape && (typeof shape.adj === 'object'); + + } catch (e) { + return false; + } +}()); + +L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({ + statics: { + VML: true, + CLIP_PADDING: 0.02 + }, + + _createElement: (function () { + try { + document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml'); + return function (name) { + return document.createElement(''); + }; + } catch (e) { + return function (name) { + return document.createElement( + '<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">'); + }; + } + }()), + + _initPath: function () { + var container = this._container = this._createElement('shape'); + + L.DomUtil.addClass(container, 'leaflet-vml-shape' + + (this.options.className ? ' ' + this.options.className : '')); + + if (this.options.clickable) { + L.DomUtil.addClass(container, 'leaflet-clickable'); + } + + container.coordsize = '1 1'; + + this._path = this._createElement('path'); + container.appendChild(this._path); + + this._map._pathRoot.appendChild(container); + }, + + _initStyle: function () { + this._updateStyle(); + }, + + _updateStyle: function () { + var stroke = this._stroke, + fill = this._fill, + options = this.options, + container = this._container; + + container.stroked = options.stroke; + container.filled = options.fill; + + if (options.stroke) { + if (!stroke) { + stroke = this._stroke = this._createElement('stroke'); + stroke.endcap = 'round'; + container.appendChild(stroke); + } + stroke.weight = options.weight + 'px'; + stroke.color = options.color; + stroke.opacity = options.opacity; + + if (options.dashArray) { + stroke.dashStyle = L.Util.isArray(options.dashArray) ? + options.dashArray.join(' ') : + options.dashArray.replace(/( *, *)/g, ' '); + } else { + stroke.dashStyle = ''; + } + if (options.lineCap) { + stroke.endcap = options.lineCap.replace('butt', 'flat'); + } + if (options.lineJoin) { + stroke.joinstyle = options.lineJoin; + } + + } else if (stroke) { + container.removeChild(stroke); + this._stroke = null; + } + + if (options.fill) { + if (!fill) { + fill = this._fill = this._createElement('fill'); + container.appendChild(fill); + } + fill.color = options.fillColor || options.color; + fill.opacity = options.fillOpacity; + + } else if (fill) { + container.removeChild(fill); + this._fill = null; + } + }, + + _updatePath: function () { + var style = this._container.style; + + style.display = 'none'; + this._path.v = this.getPathString() + ' '; // the space fixes IE empty path string bug + style.display = ''; + } +}); + +L.Map.include(L.Browser.svg || !L.Browser.vml ? {} : { + _initPathRoot: function () { + if (this._pathRoot) { return; } + + var root = this._pathRoot = document.createElement('div'); + root.className = 'leaflet-vml-container'; + this._panes.overlayPane.appendChild(root); + + this.on('moveend', this._updatePathViewport); + this._updatePathViewport(); + } +}); + + +/* + * Vector rendering for all browsers that support canvas. + */ + +L.Browser.canvas = (function () { + return !!document.createElement('canvas').getContext; +}()); + +L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({ + statics: { + //CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value + CANVAS: true, + SVG: false + }, + + redraw: function () { + if (this._map) { + this.projectLatlngs(); + this._requestUpdate(); + } + return this; + }, + + setStyle: function (style) { + L.setOptions(this, style); + + if (this._map) { + this._updateStyle(); + this._requestUpdate(); + } + return this; + }, + + onRemove: function (map) { + map + .off('viewreset', this.projectLatlngs, this) + .off('moveend', this._updatePath, this); + + if (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 () { + if (this._map && !L.Path._updateRequest) { + L.Path._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map); + } + }, + + _fireMapMoveEnd: function () { + L.Path._updateRequest = null; + this.fire('moveend'); + }, + + _initElements: function () { + this._map._initPathRoot(); + this._ctx = this._map._canvasCtx; + }, + + _updateStyle: function () { + var options = this.options; + + if (options.stroke) { + this._ctx.lineWidth = options.weight; + this._ctx.strokeStyle = options.color; + } + if (options.fill) { + this._ctx.fillStyle = options.fillColor || options.color; + } + }, + + _drawPath: function () { + var i, j, len, len2, point, drawMethod; + + this._ctx.beginPath(); + + for (i = 0, len = this._parts.length; i < len; i++) { + for (j = 0, len2 = this._parts[i].length; j < len2; j++) { + point = this._parts[i][j]; + drawMethod = (j === 0 ? 'move' : 'line') + 'To'; + + this._ctx[drawMethod](point.x, point.y); + } + // TODO refactor ugly hack + if (this instanceof L.Polygon) { + this._ctx.closePath(); + } + } + }, + + _checkIfEmpty: function () { + return !this._parts.length; + }, + + _updatePath: function () { + if (this._checkIfEmpty()) { return; } + + var ctx = this._ctx, + options = this.options; + + this._drawPath(); + ctx.save(); + this._updateStyle(); + + if (options.fill) { + ctx.globalAlpha = options.fillOpacity; + ctx.fill(); + } + + if (options.stroke) { + ctx.globalAlpha = options.opacity; + ctx.stroke(); + } + + ctx.restore(); + + // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature + }, + + _initEvents: function () { + if (this.options.clickable) { + // TODO dblclick + this._map.on('mousemove', this._onMouseMove, this); + this._map.on('click', this._onClick, this); + } + }, + + _onClick: function (e) { + if (this._containsPoint(e.layerPoint)) { + this.fire('click', e); + } + }, + + _onMouseMove: function (e) { + if (!this._map || this._map._animatingZoom) { return; } + + // TODO don't do on each move + if (this._containsPoint(e.layerPoint)) { + this._ctx.canvas.style.cursor = 'pointer'; + this._mouseInside = true; + this.fire('mouseover', e); + + } else if (this._mouseInside) { + this._ctx.canvas.style.cursor = ''; + this._mouseInside = false; + this.fire('mouseout', e); + } + } +}); + +L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : { + _initPathRoot: function () { + var root = this._pathRoot, + ctx; + + if (!root) { + root = this._pathRoot = document.createElement('canvas'); + root.style.position = 'absolute'; + ctx = this._canvasCtx = root.getContext('2d'); + + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + + this._panes.overlayPane.appendChild(root); + + if (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 () { + // don't redraw while zooming. See _updateSvgViewport for more details + if (this._pathZooming) { return; } + this._updatePathViewport(); + + var vp = this._pathViewport, + min = vp.min, + size = vp.max.subtract(min), + root = this._pathRoot; + + //TODO check if this works properly on mobile webkit + L.DomUtil.setPosition(root, min); + root.width = size.x; + root.height = size.y; + root.getContext('2d').translate(-min.x, -min.y); + } +}); + + +/* + * L.LineUtil contains different utility functions for line segments + * and polylines (clipping, simplification, distances, etc.) + */ + +/*jshint bitwise:false */ // allow bitwise operations for this file + +L.LineUtil = { + + // Simplify polyline with vertex reduction and Douglas-Peucker simplification. + // Improves rendering performance dramatically by lessening the number of points to draw. + + simplify: function (/*Point[]*/ points, /*Number*/ tolerance) { + if (!tolerance || !points.length) { + return points.slice(); + } + + var sqTolerance = tolerance * tolerance; + + // stage 1: vertex reduction + points = this._reducePoints(points, sqTolerance); + + // stage 2: Douglas-Peucker simplification + points = this._simplifyDP(points, sqTolerance); + + return points; + }, + + // distance from a point to a segment between two points + pointToSegmentDistance: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) { + return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true)); + }, + + closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) { + return this._sqClosestPointOnSegment(p, p1, p2); + }, + + // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm + _simplifyDP: function (points, sqTolerance) { + + var len = points.length, + ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array, + markers = new ArrayConstructor(len); + + markers[0] = markers[len - 1] = 1; + + this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1); + + var i, + newPoints = []; + + for (i = 0; i < len; i++) { + if (markers[i]) { + newPoints.push(points[i]); + } + } + + return newPoints; + }, + + _simplifyDPStep: function (points, markers, sqTolerance, first, last) { + + var maxSqDist = 0, + index, i, sqDist; + + for (i = first + 1; i <= last - 1; i++) { + sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true); + + if (sqDist > maxSqDist) { + index = i; + maxSqDist = sqDist; + } + } + + if (maxSqDist > sqTolerance) { + markers[index] = 1; + + this._simplifyDPStep(points, markers, sqTolerance, first, index); + this._simplifyDPStep(points, markers, sqTolerance, index, last); + } + }, + + // reduce points that are too close to each other to a single point + _reducePoints: function (points, sqTolerance) { + var reducedPoints = [points[0]]; + + for (var i = 1, prev = 0, len = points.length; i < len; i++) { + if (this._sqDist(points[i], points[prev]) > sqTolerance) { + reducedPoints.push(points[i]); + prev = i; + } + } + if (prev < len - 1) { + reducedPoints.push(points[len - 1]); + } + return reducedPoints; + }, + + // Cohen-Sutherland line clipping algorithm. + // Used to avoid rendering parts of a polyline that are not currently visible. + + clipSegment: function (a, b, bounds, useLastCode) { + var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds), + codeB = this._getBitCode(b, bounds), + + codeOut, p, newCode; + + // save 2nd code to avoid calculating it on the next segment + this._lastCode = codeB; + + while (true) { + // if a,b is inside the clip window (trivial accept) + if (!(codeA | codeB)) { + return [a, b]; + // if a,b is outside the clip window (trivial reject) + } else if (codeA & codeB) { + return false; + // other cases + } else { + codeOut = codeA || codeB; + p = this._getEdgeIntersection(a, b, codeOut, bounds); + newCode = this._getBitCode(p, bounds); + + if (codeOut === codeA) { + a = p; + codeA = newCode; + } else { + b = p; + codeB = newCode; + } + } + } + }, + + _getEdgeIntersection: function (a, b, code, bounds) { + var dx = b.x - a.x, + dy = b.y - a.y, + min = bounds.min, + max = bounds.max; + + if (code & 8) { // top + return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y); + } else if (code & 4) { // bottom + return new L.Point(a.x + dx * (min.y - a.y) / dy, min.y); + } else if (code & 2) { // right + return new L.Point(max.x, a.y + dy * (max.x - a.x) / dx); + } else if (code & 1) { // left + return new L.Point(min.x, a.y + dy * (min.x - a.x) / dx); + } + }, + + _getBitCode: function (/*Point*/ p, bounds) { + var code = 0; + + if (p.x < bounds.min.x) { // left + code |= 1; + } else if (p.x > bounds.max.x) { // right + code |= 2; + } + if (p.y < bounds.min.y) { // bottom + code |= 4; + } else if (p.y > bounds.max.y) { // top + code |= 8; + } + + return code; + }, + + // square distance (to avoid unnecessary Math.sqrt calls) + _sqDist: function (p1, p2) { + var dx = p2.x - p1.x, + dy = p2.y - p1.y; + return dx * dx + dy * dy; + }, + + // return closest point on segment or distance to that point + _sqClosestPointOnSegment: function (p, p1, p2, sqDist) { + var x = p1.x, + y = p1.y, + dx = p2.x - x, + dy = p2.y - y, + dot = dx * dx + dy * dy, + t; + + if (dot > 0) { + t = ((p.x - x) * dx + (p.y - y) * dy) / dot; + + if (t > 1) { + x = p2.x; + y = p2.y; + } else if (t > 0) { + x += dx * t; + y += dy * t; + } + } + + dx = p.x - x; + dy = p.y - y; + + return sqDist ? dx * dx + dy * dy : new L.Point(x, y); + } +}; + + +/* + * L.Polyline is used to display polylines on a map. + */ + +L.Polyline = L.Path.extend({ + initialize: function (latlngs, options) { + L.Path.prototype.initialize.call(this, options); + + this._latlngs = this._convertLatLngs(latlngs); + }, + + options: { + // how much to simplify the polyline on each zoom level + // more = better performance and smoother look, less = more accurate + smoothFactor: 1.0, + noClip: false + }, + + projectLatlngs: function () { + this._originalPoints = []; + + for (var i = 0, len = this._latlngs.length; i < len; i++) { + this._originalPoints[i] = this._map.latLngToLayerPoint(this._latlngs[i]); + } + }, + + getPathString: function () { + for (var i = 0, len = this._parts.length, str = ''; i < len; i++) { + str += this._getPathPartStr(this._parts[i]); + } + return str; + }, + + getLatLngs: function () { + return this._latlngs; + }, + + setLatLngs: function (latlngs) { + this._latlngs = this._convertLatLngs(latlngs); + return this.redraw(); + }, + + addLatLng: function (latlng) { + this._latlngs.push(L.latLng(latlng)); + return this.redraw(); + }, + + spliceLatLngs: function () { // (Number index, Number howMany) + var removed = [].splice.apply(this._latlngs, arguments); + this._convertLatLngs(this._latlngs, true); + this.redraw(); + return removed; + }, + + closestLayerPoint: function (p) { + var minDistance = Infinity, parts = this._parts, p1, p2, minPoint = null; + + for (var j = 0, jLen = parts.length; j < jLen; j++) { + var points = parts[j]; + for (var i = 1, len = points.length; i < len; i++) { + p1 = points[i - 1]; + p2 = points[i]; + var sqDist = L.LineUtil._sqClosestPointOnSegment(p, p1, p2, true); + if (sqDist < minDistance) { + minDistance = sqDist; + minPoint = L.LineUtil._sqClosestPointOnSegment(p, p1, p2); + } + } + } + if (minPoint) { + minPoint.distance = Math.sqrt(minDistance); + } + return minPoint; + }, + + getBounds: function () { + return new L.LatLngBounds(this.getLatLngs()); + }, + + _convertLatLngs: function (latlngs, overwrite) { + var i, len, target = overwrite ? latlngs : []; + + for (i = 0, len = latlngs.length; i < len; i++) { + if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') { + return; + } + target[i] = L.latLng(latlngs[i]); + } + return target; + }, + + _initEvents: function () { + L.Path.prototype._initEvents.call(this); + }, + + _getPathPartStr: function (points) { + var round = L.Path.VML; + + for (var j = 0, len2 = points.length, str = '', p; j < len2; j++) { + p = points[j]; + if (round) { + p._round(); + } + str += (j ? 'L' : 'M') + p.x + ' ' + p.y; + } + return str; + }, + + _clipPoints: function () { + var points = this._originalPoints, + len = points.length, + i, k, segment; + + if (this.options.noClip) { + this._parts = [points]; + return; + } + + this._parts = []; + + var parts = this._parts, + vp = this._map._pathViewport, + lu = L.LineUtil; + + for (i = 0, k = 0; i < len - 1; i++) { + segment = lu.clipSegment(points[i], points[i + 1], vp, i); + if (!segment) { + continue; + } + + parts[k] = parts[k] || []; + parts[k].push(segment[0]); + + // if segment goes out of screen, or it's the last one, it's the end of the line part + if ((segment[1] !== points[i + 1]) || (i === len - 2)) { + parts[k].push(segment[1]); + k++; + } + } + }, + + // simplify each clipped part of the polyline + _simplifyPoints: function () { + var parts = this._parts, + lu = L.LineUtil; + + for (var i = 0, len = parts.length; i < len; i++) { + parts[i] = lu.simplify(parts[i], this.options.smoothFactor); + } + }, + + _updatePath: function () { + if (!this._map) { return; } + + this._clipPoints(); + this._simplifyPoints(); + + L.Path.prototype._updatePath.call(this); + } +}); + +L.polyline = function (latlngs, options) { + return new L.Polyline(latlngs, options); +}; + + +/* + * L.PolyUtil contains utility functions for polygons (clipping, etc.). + */ + +/*jshint bitwise:false */ // allow bitwise operations here + +L.PolyUtil = {}; + +/* + * Sutherland-Hodgeman polygon clipping algorithm. + * Used to avoid rendering parts of a polygon that are not currently visible. + */ +L.PolyUtil.clipPolygon = function (points, bounds) { + var clippedPoints, + edges = [1, 4, 2, 8], + i, j, k, + a, b, + len, edge, p, + lu = L.LineUtil; + + for (i = 0, len = points.length; i < len; i++) { + points[i]._code = lu._getBitCode(points[i], bounds); + } + + // for each edge (left, bottom, right, top) + for (k = 0; k < 4; k++) { + edge = edges[k]; + clippedPoints = []; + + for (i = 0, len = points.length, j = len - 1; i < len; j = i++) { + a = points[i]; + b = points[j]; + + // if a is inside the clip window + if (!(a._code & edge)) { + // if b is outside the clip window (a->b goes out of screen) + if (b._code & edge) { + p = lu._getEdgeIntersection(b, a, edge, bounds); + p._code = lu._getBitCode(p, bounds); + clippedPoints.push(p); + } + clippedPoints.push(a); + + // else if b is inside the clip window (a->b enters the screen) + } else if (!(b._code & edge)) { + p = lu._getEdgeIntersection(b, a, edge, bounds); + p._code = lu._getBitCode(p, bounds); + clippedPoints.push(p); + } + } + points = clippedPoints; + } + + return points; +}; + + +/* + * L.Polygon is used to display polygons on a map. + */ + +L.Polygon = L.Polyline.extend({ + options: { + fill: true + }, + + initialize: function (latlngs, options) { + L.Polyline.prototype.initialize.call(this, latlngs, options); + this._initWithHoles(latlngs); + }, + + _initWithHoles: function (latlngs) { + var i, len, hole; + if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) { + this._latlngs = this._convertLatLngs(latlngs[0]); + this._holes = latlngs.slice(1); + + for (i = 0, len = this._holes.length; i < len; i++) { + hole = this._holes[i] = this._convertLatLngs(this._holes[i]); + if (hole[0].equals(hole[hole.length - 1])) { + hole.pop(); + } + } + } + + // filter out last point if its equal to the first one + latlngs = this._latlngs; + + if (latlngs.length >= 2 && latlngs[0].equals(latlngs[latlngs.length - 1])) { + latlngs.pop(); + } + }, + + projectLatlngs: function () { + L.Polyline.prototype.projectLatlngs.call(this); + + // project polygon holes points + // TODO move this logic to Polyline to get rid of duplication + this._holePoints = []; + + if (!this._holes) { return; } + + var i, j, len, len2; + + for (i = 0, len = this._holes.length; i < len; i++) { + this._holePoints[i] = []; + + for (j = 0, len2 = this._holes[i].length; j < len2; j++) { + this._holePoints[i][j] = this._map.latLngToLayerPoint(this._holes[i][j]); + } + } + }, + + setLatLngs: function (latlngs) { + if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) { + this._initWithHoles(latlngs); + return this.redraw(); + } else { + return L.Polyline.prototype.setLatLngs.call(this, latlngs); + } + }, + + _clipPoints: function () { + var points = this._originalPoints, + newParts = []; + + this._parts = [points].concat(this._holePoints); + + if (this.options.noClip) { return; } + + for (var i = 0, len = this._parts.length; i < len; i++) { + var clipped = L.PolyUtil.clipPolygon(this._parts[i], this._map._pathViewport); + if (clipped.length) { + newParts.push(clipped); + } + } + + this._parts = newParts; + }, + + _getPathPartStr: function (points) { + var str = L.Polyline.prototype._getPathPartStr.call(this, points); + return str + (L.Browser.svg ? 'z' : 'x'); + } +}); + +L.polygon = function (latlngs, options) { + return new L.Polygon(latlngs, options); +}; + + +/* + * Contains L.MultiPolyline and L.MultiPolygon layers. + */ + +(function () { + function createMulti(Klass) { + + return L.FeatureGroup.extend({ + + initialize: function (latlngs, options) { + this._layers = {}; + this._options = options; + this.setLatLngs(latlngs); + }, + + setLatLngs: function (latlngs) { + var i = 0, + len = latlngs.length; + + this.eachLayer(function (layer) { + if (i < len) { + layer.setLatLngs(latlngs[i++]); + } else { + this.removeLayer(layer); + } + }, this); + + while (i < len) { + this.addLayer(new Klass(latlngs[i++], this._options)); + } + + return this; + }, + + getLatLngs: function () { + var latlngs = []; + + this.eachLayer(function (layer) { + latlngs.push(layer.getLatLngs()); + }); + + return latlngs; + } + }); + } + + L.MultiPolyline = createMulti(L.Polyline); + L.MultiPolygon = createMulti(L.Polygon); + + L.multiPolyline = function (latlngs, options) { + return new L.MultiPolyline(latlngs, options); + }; + + L.multiPolygon = function (latlngs, options) { + return new L.MultiPolygon(latlngs, options); + }; +}()); + + +/* + * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object. + */ + +L.Rectangle = L.Polygon.extend({ + initialize: function (latLngBounds, options) { + L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options); + }, + + setBounds: function (latLngBounds) { + this.setLatLngs(this._boundsToLatLngs(latLngBounds)); + }, + + _boundsToLatLngs: function (latLngBounds) { + latLngBounds = L.latLngBounds(latLngBounds); + return [ + latLngBounds.getSouthWest(), + latLngBounds.getNorthWest(), + latLngBounds.getNorthEast(), + latLngBounds.getSouthEast() + ]; + } +}); + +L.rectangle = function (latLngBounds, options) { + return new L.Rectangle(latLngBounds, options); +}; + + +/* + * L.Circle is a circle overlay (with a certain radius in meters). + */ + +L.Circle = L.Path.extend({ + initialize: function (latlng, radius, options) { + L.Path.prototype.initialize.call(this, options); + + this._latlng = L.latLng(latlng); + this._mRadius = radius; + }, + + options: { + fill: true + }, + + setLatLng: function (latlng) { + this._latlng = L.latLng(latlng); + return this.redraw(); + }, + + setRadius: function (radius) { + this._mRadius = radius; + return this.redraw(); + }, + + projectLatlngs: function () { + var lngRadius = this._getLngRadius(), + latlng = this._latlng, + pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]); + + this._point = this._map.latLngToLayerPoint(latlng); + this._radius = Math.max(this._point.x - pointLeft.x, 1); + }, + + getBounds: function () { + var lngRadius = this._getLngRadius(), + latRadius = (this._mRadius / 40075017) * 360, + latlng = this._latlng; + + return new L.LatLngBounds( + [latlng.lat - latRadius, latlng.lng - lngRadius], + [latlng.lat + latRadius, latlng.lng + lngRadius]); + }, + + getLatLng: function () { + return this._latlng; + }, + + getPathString: function () { + var p = this._point, + r = this._radius; + + if (this._checkIfEmpty()) { + return ''; + } + + if (L.Browser.svg) { + return 'M' + p.x + ',' + (p.y - r) + + 'A' + r + ',' + r + ',0,1,1,' + + (p.x - 0.1) + ',' + (p.y - r) + ' z'; + } else { + p._round(); + r = Math.round(r); + return 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r + ' 0,' + (65535 * 360); + } + }, + + getRadius: function () { + return this._mRadius; + }, + + // TODO Earth hardcoded, move into projection code! + + _getLatRadius: function () { + return (this._mRadius / 40075017) * 360; + }, + + _getLngRadius: function () { + return this._getLatRadius() / Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat); + }, + + _checkIfEmpty: function () { + if (!this._map) { + return false; + } + var vp = this._map._pathViewport, + r = this._radius, + p = this._point; + + return p.x - r > vp.max.x || p.y - r > vp.max.y || + p.x + r < vp.min.x || p.y + r < vp.min.y; + } +}); + +L.circle = function (latlng, radius, options) { + return new L.Circle(latlng, radius, options); +}; + + +/* + * L.CircleMarker is a circle overlay with a permanent pixel radius. + */ + +L.CircleMarker = L.Circle.extend({ + options: { + radius: 10, + weight: 2 + }, + + initialize: function (latlng, options) { + L.Circle.prototype.initialize.call(this, latlng, null, options); + this._radius = this.options.radius; + }, + + projectLatlngs: function () { + this._point = this._map.latLngToLayerPoint(this._latlng); + }, + + _updateStyle : function () { + L.Circle.prototype._updateStyle.call(this); + this.setRadius(this.options.radius); + }, + + setLatLng: function (latlng) { + L.Circle.prototype.setLatLng.call(this, latlng); + if (this._popup && this._popup._isOpen) { + this._popup.setLatLng(latlng); + } + return this; + }, + + setRadius: function (radius) { + this.options.radius = this._radius = radius; + return this.redraw(); + }, + + getRadius: function () { + return this._radius; + } +}); + +L.circleMarker = function (latlng, options) { + return new L.CircleMarker(latlng, options); +}; + + +/* + * Extends L.Polyline to be able to manually detect clicks on Canvas-rendered polylines. + */ + +L.Polyline.include(!L.Path.CANVAS ? {} : { + _containsPoint: function (p, closed) { + var i, j, k, len, len2, dist, part, + w = this.options.weight / 2; + + if (L.Browser.touch) { + w += 10; // polyline click tolerance on touch devices + } + + for (i = 0, len = this._parts.length; i < len; i++) { + part = this._parts[i]; + for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { + if (!closed && (j === 0)) { + continue; + } + + dist = L.LineUtil.pointToSegmentDistance(p, part[k], part[j]); + + if (dist <= w) { + return true; + } + } + } + return false; + } +}); + + +/* + * Extends L.Polygon to be able to manually detect clicks on Canvas-rendered polygons. + */ + +L.Polygon.include(!L.Path.CANVAS ? {} : { + _containsPoint: function (p) { + var inside = false, + part, p1, p2, + i, j, k, + len, len2; + + // TODO optimization: check if within bounds first + + if (L.Polyline.prototype._containsPoint.call(this, p, true)) { + // click on polygon border + return true; + } + + // ray casting algorithm for detecting if point is in polygon + + for (i = 0, len = this._parts.length; i < len; i++) { + part = this._parts[i]; + + for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { + p1 = part[j]; + p2 = part[k]; + + if (((p1.y > p.y) !== (p2.y > p.y)) && + (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) { + inside = !inside; + } + } + } + + return inside; + } +}); + + +/* + * Extends L.Circle with Canvas-specific code. + */ + +L.Circle.include(!L.Path.CANVAS ? {} : { + _drawPath: function () { + var p = this._point; + this._ctx.beginPath(); + this._ctx.arc(p.x, p.y, this._radius, 0, Math.PI * 2, false); + }, + + _containsPoint: function (p) { + var center = this._point, + w2 = this.options.stroke ? this.options.weight / 2 : 0; + + return (p.distanceTo(center) <= this._radius + w2); + } +}); + + +/* + * CircleMarker canvas specific drawing parts. + */ + +L.CircleMarker.include(!L.Path.CANVAS ? {} : { + _updateStyle: function () { + L.Path.prototype._updateStyle.call(this); + } +}); + + +/* + * L.GeoJSON turns any GeoJSON data into a Leaflet layer. + */ + +L.GeoJSON = L.FeatureGroup.extend({ + + initialize: function (geojson, options) { + L.setOptions(this, options); + + this._layers = {}; + + if (geojson) { + this.addData(geojson); + } + }, + + addData: function (geojson) { + var features = L.Util.isArray(geojson) ? geojson : geojson.features, + i, len, feature; + + if (features) { + for (i = 0, len = features.length; i < len; i++) { + // Only add this if geometry or geometries are set and not null + feature = features[i]; + if (feature.geometries || feature.geometry || feature.features || feature.coordinates) { + this.addData(features[i]); + } + } + return this; + } + + var options = this.options; + + if (options.filter && !options.filter(geojson)) { return; } + + var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer, options.coordsToLatLng, options); + layer.feature = L.GeoJSON.asFeature(geojson); + + layer.defaultOptions = layer.options; + this.resetStyle(layer); + + if (options.onEachFeature) { + options.onEachFeature(geojson, layer); + } + + return this.addLayer(layer); + }, + + resetStyle: function (layer) { + var style = this.options.style; + if (style) { + // reset any custom styles + L.Util.extend(layer.options, layer.defaultOptions); + + this._setLayerStyle(layer, style); + } + }, + + setStyle: function (style) { + this.eachLayer(function (layer) { + this._setLayerStyle(layer, style); + }, this); + }, + + _setLayerStyle: function (layer, style) { + if (typeof style === 'function') { + style = style(layer.feature); + } + if (layer.setStyle) { + layer.setStyle(style); + } + } +}); + +L.extend(L.GeoJSON, { + geometryToLayer: function (geojson, pointToLayer, coordsToLatLng, vectorOptions) { + var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, + coords = geometry.coordinates, + layers = [], + latlng, latlngs, i, len; + + coordsToLatLng = coordsToLatLng || this.coordsToLatLng; + + switch (geometry.type) { + case 'Point': + latlng = coordsToLatLng(coords); + return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng); + + case 'MultiPoint': + for (i = 0, len = coords.length; i < len; i++) { + latlng = coordsToLatLng(coords[i]); + layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng)); + } + return new L.FeatureGroup(layers); + + case 'LineString': + latlngs = this.coordsToLatLngs(coords, 0, coordsToLatLng); + return new L.Polyline(latlngs, vectorOptions); + + case 'Polygon': + if (coords.length === 2 && !coords[1].length) { + throw new Error('Invalid GeoJSON object.'); + } + latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng); + return new L.Polygon(latlngs, vectorOptions); + + case 'MultiLineString': + latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng); + return new L.MultiPolyline(latlngs, vectorOptions); + + case 'MultiPolygon': + latlngs = this.coordsToLatLngs(coords, 2, coordsToLatLng); + return new L.MultiPolygon(latlngs, vectorOptions); + + case 'GeometryCollection': + for (i = 0, len = geometry.geometries.length; i < len; i++) { + + layers.push(this.geometryToLayer({ + geometry: geometry.geometries[i], + type: 'Feature', + properties: geojson.properties + }, pointToLayer, coordsToLatLng, vectorOptions)); + } + return new L.FeatureGroup(layers); + + default: + throw new Error('Invalid GeoJSON object.'); + } + }, + + coordsToLatLng: function (coords) { // (Array[, Boolean]) -> LatLng + return new L.LatLng(coords[1], coords[0], coords[2]); + }, + + coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) { // (Array[, Number, Function]) -> Array + var latlng, i, len, + latlngs = []; + + for (i = 0, len = coords.length; i < len; i++) { + latlng = levelsDeep ? + this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) : + (coordsToLatLng || this.coordsToLatLng)(coords[i]); + + latlngs.push(latlng); + } + + return latlngs; + }, + + latLngToCoords: function (latlng) { + var coords = [latlng.lng, latlng.lat]; + + if (latlng.alt !== undefined) { + coords.push(latlng.alt); + } + return coords; + }, + + latLngsToCoords: function (latLngs) { + var coords = []; + + for (var i = 0, len = latLngs.length; i < len; i++) { + coords.push(L.GeoJSON.latLngToCoords(latLngs[i])); + } + + return coords; + }, + + getFeature: function (layer, newGeometry) { + return layer.feature ? L.extend({}, layer.feature, {geometry: newGeometry}) : L.GeoJSON.asFeature(newGeometry); + }, + + asFeature: function (geoJSON) { + if (geoJSON.type === 'Feature') { + return geoJSON; + } + + return { + type: 'Feature', + properties: {}, + geometry: geoJSON + }; + } +}); + +var PointToGeoJSON = { + toGeoJSON: function () { + return L.GeoJSON.getFeature(this, { + type: 'Point', + coordinates: L.GeoJSON.latLngToCoords(this.getLatLng()) + }); + } +}; + +L.Marker.include(PointToGeoJSON); +L.Circle.include(PointToGeoJSON); +L.CircleMarker.include(PointToGeoJSON); + +L.Polyline.include({ + toGeoJSON: function () { + return L.GeoJSON.getFeature(this, { + type: 'LineString', + coordinates: L.GeoJSON.latLngsToCoords(this.getLatLngs()) + }); + } +}); + +L.Polygon.include({ + toGeoJSON: function () { + var coords = [L.GeoJSON.latLngsToCoords(this.getLatLngs())], + i, len, hole; + + coords[0].push(coords[0][0]); + + if (this._holes) { + for (i = 0, len = this._holes.length; i < len; i++) { + hole = L.GeoJSON.latLngsToCoords(this._holes[i]); + hole.push(hole[0]); + coords.push(hole); + } + } + + return L.GeoJSON.getFeature(this, { + type: 'Polygon', + coordinates: coords + }); + } +}); + +(function () { + function multiToGeoJSON(type) { + return function () { + var coords = []; + + this.eachLayer(function (layer) { + coords.push(layer.toGeoJSON().geometry.coordinates); + }); + + return L.GeoJSON.getFeature(this, { + type: type, + coordinates: coords + }); + }; + } + + L.MultiPolyline.include({toGeoJSON: multiToGeoJSON('MultiLineString')}); + L.MultiPolygon.include({toGeoJSON: multiToGeoJSON('MultiPolygon')}); + + L.LayerGroup.include({ + toGeoJSON: function () { + + var geometry = this.feature && this.feature.geometry, + jsons = [], + json; + + if (geometry && geometry.type === 'MultiPoint') { + return multiToGeoJSON('MultiPoint').call(this); + } + + var isGeometryCollection = geometry && geometry.type === 'GeometryCollection'; + + this.eachLayer(function (layer) { + if (layer.toGeoJSON) { + json = layer.toGeoJSON(); + jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json)); + } + }); + + if (isGeometryCollection) { + return L.GeoJSON.getFeature(this, { + geometries: jsons, + type: 'GeometryCollection' + }); + } + + return { + type: 'FeatureCollection', + features: jsons + }; + } + }); +}()); + +L.geoJson = function (geojson, options) { + return new L.GeoJSON(geojson, options); +}; + + +/* + * L.DomEvent contains functions for working with DOM events. + */ + +L.DomEvent = { + /* inspired by John Resig, Dean Edwards and YUI addEvent implementations */ + addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object]) + + var id = L.stamp(fn), + key = '_leaflet_' + type + id, + handler, originalHandler, newType; + + if (obj[key]) { return this; } + + handler = function (e) { + return fn.call(context || obj, e || L.DomEvent._getEvent()); + }; + + if (L.Browser.pointer && type.indexOf('touch') === 0) { + return this.addPointerListener(obj, type, handler, id); + } + if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) { + this.addDoubleTapListener(obj, handler, id); + } + + if ('addEventListener' in obj) { + + if (type === 'mousewheel') { + obj.addEventListener('DOMMouseScroll', handler, false); + obj.addEventListener(type, handler, false); + + } else if ((type === 'mouseenter') || (type === 'mouseleave')) { + + originalHandler = handler; + newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout'); + + handler = function (e) { + if (!L.DomEvent._checkMouse(obj, e)) { return; } + return originalHandler(e); + }; + + obj.addEventListener(newType, handler, false); + + } else if (type === 'click' && L.Browser.android) { + originalHandler = handler; + handler = function (e) { + return L.DomEvent._filterClick(e, originalHandler); + }; + + obj.addEventListener(type, handler, false); + } else { + obj.addEventListener(type, handler, false); + } + + } else if ('attachEvent' in obj) { + obj.attachEvent('on' + type, handler); + } + + obj[key] = handler; + + return this; + }, + + removeListener: function (obj, type, fn) { // (HTMLElement, String, Function) + + var id = L.stamp(fn), + key = '_leaflet_' + type + id, + handler = obj[key]; + + if (!handler) { return this; } + + if (L.Browser.pointer && type.indexOf('touch') === 0) { + this.removePointerListener(obj, type, id); + } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) { + this.removeDoubleTapListener(obj, id); + + } else if ('removeEventListener' in obj) { + + if (type === 'mousewheel') { + obj.removeEventListener('DOMMouseScroll', handler, false); + obj.removeEventListener(type, handler, false); + + } else if ((type === 'mouseenter') || (type === 'mouseleave')) { + obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false); + } else { + obj.removeEventListener(type, handler, false); + } + } else if ('detachEvent' in obj) { + obj.detachEvent('on' + type, handler); + } + + obj[key] = null; + + return this; + }, + + stopPropagation: function (e) { + + if (e.stopPropagation) { + e.stopPropagation(); + } else { + e.cancelBubble = true; + } + L.DomEvent._skipped(e); + + return this; + }, + + disableScrollPropagation: function (el) { + var stop = L.DomEvent.stopPropagation; + + return L.DomEvent + .on(el, 'mousewheel', stop) + .on(el, 'MozMousePixelScroll', stop); + }, + + disableClickPropagation: function (el) { + var stop = L.DomEvent.stopPropagation; + + for (var i = L.Draggable.START.length - 1; i >= 0; i--) { + L.DomEvent.on(el, L.Draggable.START[i], stop); + } + + return L.DomEvent + .on(el, 'click', L.DomEvent._fakeStop) + .on(el, 'dblclick', stop); + }, + + preventDefault: function (e) { + + if (e.preventDefault) { + e.preventDefault(); + } else { + e.returnValue = false; + } + return this; + }, + + stop: function (e) { + return L.DomEvent + .preventDefault(e) + .stopPropagation(e); + }, + + getMousePosition: function (e, container) { + if (!container) { + return new L.Point(e.clientX, e.clientY); + } + + var rect = container.getBoundingClientRect(); + + return new L.Point( + e.clientX - rect.left - container.clientLeft, + e.clientY - rect.top - container.clientTop); + }, + + getWheelDelta: function (e) { + + var delta = 0; + + if (e.wheelDelta) { + delta = e.wheelDelta / 120; + } + if (e.detail) { + delta = -e.detail / 3; + } + return delta; + }, + + _skipEvents: {}, + + _fakeStop: function (e) { + // fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e) + L.DomEvent._skipEvents[e.type] = true; + }, + + _skipped: function (e) { + var skipped = this._skipEvents[e.type]; + // reset when checking, as it's only used in map container and propagates outside of the map + this._skipEvents[e.type] = false; + return skipped; + }, + + // check if element really left/entered the event target (for mouseenter/mouseleave) + _checkMouse: function (el, e) { + + var related = e.relatedTarget; + + if (!related) { return true; } + + try { + while (related && (related !== el)) { + related = related.parentNode; + } + } catch (err) { + return false; + } + return (related !== el); + }, + + _getEvent: function () { // evil magic for IE + /*jshint noarg:false */ + var e = window.event; + if (!e) { + var caller = arguments.callee.caller; + while (caller) { + e = caller['arguments'][0]; + if (e && window.Event === e.constructor) { + break; + } + caller = caller.caller; + } + } + return e; + }, + + // this is a horrible workaround for a bug in Android where a single touch triggers two click events + _filterClick: function (e, handler) { + var timeStamp = (e.timeStamp || e.originalEvent.timeStamp), + elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick); + + // are they closer together than 500ms yet more than 100ms? + // Android typically triggers them ~300ms apart while multiple listeners + // on the same event should be triggered far faster; + // or check if click is simulated on the element, and if it is, reject any non-simulated events + + if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) { + L.DomEvent.stop(e); + return; + } + L.DomEvent._lastClick = timeStamp; + + return handler(e); + } +}; + +L.DomEvent.on = L.DomEvent.addListener; +L.DomEvent.off = L.DomEvent.removeListener; + + +/* + * L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too. + */ + +L.Draggable = L.Class.extend({ + includes: L.Mixin.Events, + + statics: { + START: L.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 (element, dragStartTarget) { + this._element = element; + this._dragStartTarget = dragStartTarget || element; + }, + + enable: function () { + if (this._enabled) { return; } + + for (var i = L.Draggable.START.length - 1; i >= 0; i--) { + L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this); + } + + this._enabled = true; + }, + + disable: function () { + if (!this._enabled) { return; } + + for (var i = L.Draggable.START.length - 1; i >= 0; i--) { + L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this); + } + + this._enabled = false; + this._moved = false; + }, + + _onDown: function (e) { + this._moved = false; + + if (e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } + + L.DomEvent.stopPropagation(e); + + if (L.Draggable._disabled) { return; } + + L.DomUtil.disableImageDrag(); + L.DomUtil.disableTextSelection(); + + if (this._moving) { return; } + + var first = e.touches ? e.touches[0] : e; + + this._startPoint = new L.Point(first.clientX, first.clientY); + this._startPos = this._newPos = L.DomUtil.getPosition(this._element); + + L.DomEvent + .on(document, L.Draggable.MOVE[e.type], this._onMove, this) + .on(document, L.Draggable.END[e.type], this._onUp, this); + }, + + _onMove: function (e) { + if (e.touches && e.touches.length > 1) { + this._moved = true; + return; + } + + var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), + newPoint = new L.Point(first.clientX, first.clientY), + offset = newPoint.subtract(this._startPoint); + + if (!offset.x && !offset.y) { return; } + if (L.Browser.touch && Math.abs(offset.x) + Math.abs(offset.y) < 3) { return; } + + L.DomEvent.preventDefault(e); + + if (!this._moved) { + this.fire('dragstart'); + + this._moved = true; + this._startPos = L.DomUtil.getPosition(this._element).subtract(offset); + + L.DomUtil.addClass(document.body, 'leaflet-dragging'); + this._lastTarget = e.target || e.srcElement; + L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target'); + } + + this._newPos = this._startPos.add(offset); + this._moving = true; + + L.Util.cancelAnimFrame(this._animRequest); + this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget); + }, + + _updatePosition: function () { + this.fire('predrag'); + L.DomUtil.setPosition(this._element, this._newPos); + this.fire('drag'); + }, + + _onUp: function () { + L.DomUtil.removeClass(document.body, 'leaflet-dragging'); + + if (this._lastTarget) { + L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target'); + this._lastTarget = null; + } + + for (var i in L.Draggable.MOVE) { + L.DomEvent + .off(document, L.Draggable.MOVE[i], this._onMove) + .off(document, L.Draggable.END[i], this._onUp); + } + + L.DomUtil.enableImageDrag(); + L.DomUtil.enableTextSelection(); + + if (this._moved && this._moving) { + // ensure drag is not fired after dragend + L.Util.cancelAnimFrame(this._animRequest); + + this.fire('dragend', { + distance: this._newPos.distanceTo(this._startPos) + }); + } + + this._moving = false; + } +}); + + +/* + 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); + + +/* + * Extends the event handling code with double tap support for mobile browsers. + */ + +L.extend(L.DomEvent, { + + _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart', + _touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend', + + // inspired by Zepto touch code by Thomas Fuchs + addDoubleTapListener: function (obj, handler, id) { + var last, + doubleTap = false, + delay = 250, + touch, + pre = '_leaflet_', + touchstart = this._touchstart, + touchend = this._touchend, + trackedTouches = []; + + function onTouchStart(e) { + var count; + + if (L.Browser.pointer) { + trackedTouches.push(e.pointerId); + count = trackedTouches.length; + } else { + count = e.touches.length; + } + if (count > 1) { + return; + } + + var now = Date.now(), + delta = now - (last || now); + + touch = e.touches ? e.touches[0] : e; + doubleTap = (delta > 0 && delta <= delay); + last = now; + } + + function onTouchEnd(e) { + if (L.Browser.pointer) { + var idx = trackedTouches.indexOf(e.pointerId); + if (idx === -1) { + return; + } + trackedTouches.splice(idx, 1); + } + + if (doubleTap) { + if (L.Browser.pointer) { + // work around .type being readonly with MSPointer* events + var newTouch = { }, + prop; + + // jshint forin:false + for (var i in touch) { + prop = touch[i]; + if (typeof prop === 'function') { + newTouch[i] = prop.bind(touch); + } else { + newTouch[i] = prop; + } + } + touch = newTouch; + } + touch.type = 'dblclick'; + handler(touch); + last = null; + } + } + obj[pre + touchstart + id] = onTouchStart; + obj[pre + touchend + id] = onTouchEnd; + + // on pointer we need to listen on the document, otherwise a drag starting on the map and moving off screen + // will not come through to us, so we will lose track of how many touches are ongoing + var endElement = L.Browser.pointer ? document.documentElement : obj; + + obj.addEventListener(touchstart, onTouchStart, false); + endElement.addEventListener(touchend, onTouchEnd, false); + + if (L.Browser.pointer) { + endElement.addEventListener(L.DomEvent.POINTER_CANCEL, onTouchEnd, false); + } + + return this; + }, + + removeDoubleTapListener: function (obj, id) { + var pre = '_leaflet_'; + + obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false); + (L.Browser.pointer ? document.documentElement : obj).removeEventListener( + this._touchend, obj[pre + this._touchend + id], false); + + if (L.Browser.pointer) { + document.documentElement.removeEventListener(L.DomEvent.POINTER_CANCEL, obj[pre + this._touchend + id], + false); + } + + return this; + } +}); + + +/* + * 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); + } +}); + + +/* + * L.Control is a base class for implementing map controls. Handles positioning. + * All other controls extend from this class. + */ + +L.Control = L.Class.extend({ + options: { + position: 'topright' + }, + + initialize: function (options) { + L.setOptions(this, options); + }, + + getPosition: function () { + return this.options.position; + }, + + setPosition: function (position) { + var map = this._map; + + if (map) { + map.removeControl(this); + } + + this.options.position = position; + + if (map) { + map.addControl(this); + } + + return this; + }, + + getContainer: function () { + return this._container; + }, + + addTo: function (map) { + this._map = map; + + var container = this._container = this.onAdd(map), + pos = this.getPosition(), + corner = map._controlCorners[pos]; + + L.DomUtil.addClass(container, 'leaflet-control'); + + if (pos.indexOf('bottom') !== -1) { + corner.insertBefore(container, corner.firstChild); + } else { + corner.appendChild(container); + } + + return this; + }, + + removeFrom: function (map) { + var pos = this.getPosition(), + corner = map._controlCorners[pos]; + + corner.removeChild(this._container); + this._map = null; + + if (this.onRemove) { + this.onRemove(map); + } + + return this; + }, + + _refocusOnMap: function () { + if (this._map) { + this._map.getContainer().focus(); + } + } +}); + +L.control = function (options) { + return new L.Control(options); +}; + + +// adds control-related methods to L.Map + +L.Map.include({ + addControl: function (control) { + control.addTo(this); + return this; + }, + + removeControl: function (control) { + control.removeFrom(this); + return this; + }, + + _initControlPos: function () { + var corners = this._controlCorners = {}, + l = 'leaflet-', + container = this._controlContainer = + L.DomUtil.create('div', l + 'control-container', this._container); + + function createCorner(vSide, hSide) { + var className = l + vSide + ' ' + l + hSide; + + corners[vSide + hSide] = L.DomUtil.create('div', className, container); + } + + createCorner('top', 'left'); + createCorner('top', 'right'); + createCorner('bottom', 'left'); + createCorner('bottom', 'right'); + }, + + _clearControlPos: function () { + this._container.removeChild(this._controlContainer); + } +}); + + +/* + * L.Control.Zoom is used for the default zoom buttons on the map. + */ + +L.Control.Zoom = L.Control.extend({ + options: { + position: 'topleft', + zoomInText: '+', + zoomInTitle: 'Zoom in', + zoomOutText: '-', + zoomOutTitle: 'Zoom out' + }, + + onAdd: function (map) { + var zoomName = 'leaflet-control-zoom', + container = L.DomUtil.create('div', zoomName + ' leaflet-bar'); + + this._map = map; + + this._zoomInButton = this._createButton( + this.options.zoomInText, this.options.zoomInTitle, + zoomName + '-in', container, this._zoomIn, this); + this._zoomOutButton = this._createButton( + this.options.zoomOutText, this.options.zoomOutTitle, + zoomName + '-out', container, this._zoomOut, this); + + this._updateDisabled(); + map.on('zoomend zoomlevelschange', this._updateDisabled, this); + + return container; + }, + + onRemove: function (map) { + map.off('zoomend zoomlevelschange', this._updateDisabled, this); + }, + + _zoomIn: function (e) { + this._map.zoomIn(e.shiftKey ? 3 : 1); + }, + + _zoomOut: function (e) { + this._map.zoomOut(e.shiftKey ? 3 : 1); + }, + + _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) + .on(link, 'click', this._refocusOnMap, context); + + return link; + }, + + _updateDisabled: function () { + var map = this._map, + className = 'leaflet-disabled'; + + L.DomUtil.removeClass(this._zoomInButton, className); + L.DomUtil.removeClass(this._zoomOutButton, className); + + if (map._zoom === map.getMinZoom()) { + L.DomUtil.addClass(this._zoomOutButton, className); + } + if (map._zoom === map.getMaxZoom()) { + L.DomUtil.addClass(this._zoomInButton, className); + } + } +}); + +L.Map.mergeOptions({ + zoomControl: true +}); + +L.Map.addInitHook(function () { + if (this.options.zoomControl) { + this.zoomControl = new L.Control.Zoom(); + this.addControl(this.zoomControl); + } +}); + +L.control.zoom = function (options) { + return new L.Control.Zoom(options); +}; + + + +/* + * L.Control.Attribution is used for displaying attribution on the map (added by default). + */ + +L.Control.Attribution = L.Control.extend({ + options: { + position: 'bottomright', + prefix: 'Leaflet' + }, + + initialize: function (options) { + L.setOptions(this, options); + + this._attributions = {}; + }, + + onAdd: function (map) { + this._container = L.DomUtil.create('div', 'leaflet-control-attribution'); + L.DomEvent.disableClickPropagation(this._container); + + for (var i in map._layers) { + if (map._layers[i].getAttribution) { + this.addAttribution(map._layers[i].getAttribution()); + } + } + + map + .on('layeradd', this._onLayerAdd, this) + .on('layerremove', this._onLayerRemove, this); + + this._update(); + + return this._container; + }, + + onRemove: function (map) { + map + .off('layeradd', this._onLayerAdd) + .off('layerremove', this._onLayerRemove); + + }, + + setPrefix: function (prefix) { + this.options.prefix = prefix; + this._update(); + return this; + }, + + addAttribution: function (text) { + if (!text) { return; } + + if (!this._attributions[text]) { + this._attributions[text] = 0; + } + this._attributions[text]++; + + this._update(); + + return this; + }, + + removeAttribution: function (text) { + if (!text) { return; } + + if (this._attributions[text]) { + this._attributions[text]--; + this._update(); + } + + return this; + }, + + _update: function () { + if (!this._map) { return; } + + var attribs = []; + + for (var i in this._attributions) { + if (this._attributions[i]) { + attribs.push(i); + } + } + + var prefixAndAttribs = []; + + if (this.options.prefix) { + prefixAndAttribs.push(this.options.prefix); + } + if (attribs.length) { + prefixAndAttribs.push(attribs.join(', ')); + } + + this._container.innerHTML = prefixAndAttribs.join(' | '); + }, + + _onLayerAdd: function (e) { + if (e.layer.getAttribution) { + this.addAttribution(e.layer.getAttribution()); + } + }, + + _onLayerRemove: function (e) { + if (e.layer.getAttribution) { + this.removeAttribution(e.layer.getAttribution()); + } + } +}); + +L.Map.mergeOptions({ + attributionControl: true +}); + +L.Map.addInitHook(function () { + if (this.options.attributionControl) { + this.attributionControl = (new L.Control.Attribution()).addTo(this); + } +}); + +L.control.attribution = function (options) { + return new L.Control.Attribution(options); +}; + + +/* + * 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); +}; + + +/* + * L.Control.Layers is a control to allow users to switch between different layers on the map. + */ + +L.Control.Layers = L.Control.extend({ + options: { + collapsed: true, + position: 'topright', + autoZIndex: true + }, + + initialize: function (baseLayers, overlays, options) { + L.setOptions(this, options); + + this._layers = {}; + this._lastZIndex = 0; + this._handlingClick = false; + + for (var i in baseLayers) { + this._addLayer(baseLayers[i], i); + } + + for (i in overlays) { + this._addLayer(overlays[i], i, true); + } + }, + + onAdd: function (map) { + this._initLayout(); + this._update(); + + map + .on('layeradd', this._onLayerChange, this) + .on('layerremove', this._onLayerChange, this); + + return this._container; + }, + + onRemove: function (map) { + map + .off('layeradd', this._onLayerChange, this) + .off('layerremove', this._onLayerChange, this); + }, + + addBaseLayer: function (layer, name) { + this._addLayer(layer, name); + this._update(); + return this; + }, + + addOverlay: function (layer, name) { + this._addLayer(layer, name, true); + this._update(); + return this; + }, + + removeLayer: function (layer) { + var id = L.stamp(layer); + delete this._layers[id]; + this._update(); + return this; + }, + + _initLayout: function () { + var className = 'leaflet-control-layers', + container = this._container = L.DomUtil.create('div', className); + + //Makes this work on IE10 Touch devices by stopping it from firing a mouseout event when the touch is released + container.setAttribute('aria-haspopup', true); + + if (!L.Browser.touch) { + L.DomEvent + .disableClickPropagation(container) + .disableScrollPropagation(container); + } else { + L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation); + } + + var form = this._form = L.DomUtil.create('form', className + '-list'); + + if (this.options.collapsed) { + if (!L.Browser.android) { + L.DomEvent + .on(container, 'mouseover', this._expand, this) + .on(container, 'mouseout', this._collapse, this); + } + var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container); + link.href = '#'; + link.title = 'Layers'; + + if (L.Browser.touch) { + L.DomEvent + .on(link, 'click', L.DomEvent.stop) + .on(link, 'click', this._expand, this); + } + else { + L.DomEvent.on(link, 'focus', this._expand, this); + } + //Work around for Firefox android issue https://github.com/Leaflet/Leaflet/issues/2033 + L.DomEvent.on(form, 'click', function () { + setTimeout(L.bind(this._onInputClick, this), 0); + }, this); + + this._map.on('click', this._collapse, this); + // TODO keyboard accessibility + } else { + this._expand(); + } + + this._baseLayersList = L.DomUtil.create('div', className + '-base', form); + this._separator = L.DomUtil.create('div', className + '-separator', form); + this._overlaysList = L.DomUtil.create('div', className + '-overlays', form); + + container.appendChild(form); + }, + + _addLayer: function (layer, name, overlay) { + var id = L.stamp(layer); + + this._layers[id] = { + layer: layer, + name: name, + overlay: overlay + }; + + if (this.options.autoZIndex && layer.setZIndex) { + this._lastZIndex++; + layer.setZIndex(this._lastZIndex); + } + }, + + _update: function () { + if (!this._container) { + return; + } + + this._baseLayersList.innerHTML = ''; + this._overlaysList.innerHTML = ''; + + var baseLayersPresent = false, + overlaysPresent = false, + i, obj; + + for (i in this._layers) { + obj = this._layers[i]; + this._addItem(obj); + overlaysPresent = overlaysPresent || obj.overlay; + baseLayersPresent = baseLayersPresent || !obj.overlay; + } + + this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none'; + }, + + _onLayerChange: function (e) { + var obj = this._layers[L.stamp(e.layer)]; + + if (!obj) { return; } + + if (!this._handlingClick) { + this._update(); + } + + var type = obj.overlay ? + (e.type === 'layeradd' ? 'overlayadd' : 'overlayremove') : + (e.type === 'layeradd' ? 'baselayerchange' : null); + + if (type) { + this._map.fire(type, obj); + } + }, + + // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe) + _createRadioElement: function (name, checked) { + + var radioHtml = '= 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); + } + } + } +}); + + +/* + * Provides L.Map with convenient shortcuts for using browser geolocation features. + */ + +L.Map.include({ + _defaultLocateOptions: { + watch: false, + setView: false, + maxZoom: Infinity, + timeout: 10000, + maximumAge: 0, + enableHighAccuracy: false + }, + + locate: function (/*Object*/ options) { + + options = this._locateOptions = L.extend(this._defaultLocateOptions, options); + + if (!navigator.geolocation) { + this._handleGeolocationError({ + code: 0, + message: 'Geolocation not supported.' + }); + return this; + } + + var onResponse = L.bind(this._handleGeolocationResponse, this), + onError = L.bind(this._handleGeolocationError, this); + + if (options.watch) { + this._locationWatchId = + navigator.geolocation.watchPosition(onResponse, onError, options); + } else { + navigator.geolocation.getCurrentPosition(onResponse, onError, options); + } + return this; + }, + + stopLocate: function () { + if (navigator.geolocation) { + navigator.geolocation.clearWatch(this._locationWatchId); + } + if (this._locateOptions) { + this._locateOptions.setView = false; + } + return this; + }, + + _handleGeolocationError: function (error) { + var c = error.code, + message = error.message || + (c === 1 ? 'permission denied' : + (c === 2 ? 'position unavailable' : 'timeout')); + + if (this._locateOptions.setView && !this._loaded) { + this.fitWorld(); + } + + this.fire('locationerror', { + code: c, + message: 'Geolocation error: ' + message + '.' + }); + }, + + _handleGeolocationResponse: function (pos) { + var lat = pos.coords.latitude, + lng = pos.coords.longitude, + latlng = new L.LatLng(lat, lng), + + latAccuracy = 180 * pos.coords.accuracy / 40075017, + lngAccuracy = latAccuracy / Math.cos(L.LatLng.DEG_TO_RAD * lat), + + bounds = L.latLngBounds( + [lat - latAccuracy, lng - lngAccuracy], + [lat + latAccuracy, lng + lngAccuracy]), + + options = this._locateOptions; + + if (options.setView) { + var zoom = Math.min(this.getBoundsZoom(bounds), options.maxZoom); + this.setView(latlng, zoom); + } + + var data = { + latlng: latlng, + bounds: bounds, + timestamp: pos.timestamp + }; + + for (var i in pos.coords) { + if (typeof pos.coords[i] === 'number') { + data[i] = pos.coords[i]; + } + } + + this.fire('locationfound', data); + } +}); + + +}(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 index 0000000..ac0cd17 --- /dev/null +++ b/www/plugins/gis/lib/leaflet/dist/leaflet.css @@ -0,0 +1,478 @@ +/* required styles */ + +.leaflet-map-pane, +.leaflet-tile, +.leaflet-marker-icon, +.leaflet-marker-shadow, +.leaflet-tile-pane, +.leaflet-tile-container, +.leaflet-overlay-pane, +.leaflet-shadow-pane, +.leaflet-marker-pane, +.leaflet-popup-pane, +.leaflet-overlay-pane svg, +.leaflet-zoom-box, +.leaflet-image-layer, +.leaflet-layer { + position: absolute; + left: 0; + top: 0; + } +.leaflet-container { + overflow: hidden; + -ms-touch-action: none; + } +.leaflet-tile, +.leaflet-marker-icon, +.leaflet-marker-shadow { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-user-drag: none; + } +.leaflet-marker-icon, +.leaflet-marker-shadow { + display: block; + } +/* map is broken in FF if you have max-width: 100% on tiles */ +.leaflet-container img { + max-width: none !important; + } +/* stupid Android 2 doesn't understand "max-width: none" properly */ +.leaflet-container img.leaflet-image-layer { + max-width: 15000px !important; + } +.leaflet-tile { + filter: inherit; + visibility: hidden; + } +.leaflet-tile-loaded { + visibility: inherit; + } +.leaflet-zoom-box { + width: 0; + height: 0; + } +/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ +.leaflet-overlay-pane svg { + -moz-user-select: none; + } + +.leaflet-tile-pane { z-index: 2; } +.leaflet-objects-pane { z-index: 3; } +.leaflet-overlay-pane { z-index: 4; } +.leaflet-shadow-pane { z-index: 5; } +.leaflet-marker-pane { z-index: 6; } +.leaflet-popup-pane { z-index: 7; } + +.leaflet-vml-shape { + width: 1px; + height: 1px; + } +.lvml { + behavior: url(#default#VML); + display: inline-block; + position: absolute; + } + + +/* control positioning */ + +.leaflet-control { + position: relative; + z-index: 7; + pointer-events: auto; + } +.leaflet-top, +.leaflet-bottom { + position: absolute; + z-index: 1000; + pointer-events: none; + } +.leaflet-top { + top: 0; + } +.leaflet-right { + right: 0; + } +.leaflet-bottom { + bottom: 0; + } +.leaflet-left { + left: 0; + } +.leaflet-control { + float: left; + clear: both; + } +.leaflet-right .leaflet-control { + float: right; + } +.leaflet-top .leaflet-control { + margin-top: 10px; + } +.leaflet-bottom .leaflet-control { + margin-bottom: 10px; + } +.leaflet-left .leaflet-control { + margin-left: 10px; + } +.leaflet-right .leaflet-control { + margin-right: 10px; + } + + +/* zoom and fade animations */ + +.leaflet-fade-anim .leaflet-tile, +.leaflet-fade-anim .leaflet-popup { + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + -o-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; + } +.leaflet-fade-anim .leaflet-tile-loaded, +.leaflet-fade-anim .leaflet-map-pane .leaflet-popup { + opacity: 1; + } + +.leaflet-zoom-anim .leaflet-zoom-animated { + -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); + -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); + -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1); + transition: transform 0.25s cubic-bezier(0,0,0.25,1); + } +.leaflet-zoom-anim .leaflet-tile, +.leaflet-pan-anim .leaflet-tile, +.leaflet-touching .leaflet-zoom-animated { + -webkit-transition: none; + -moz-transition: none; + -o-transition: none; + transition: none; + } + +.leaflet-zoom-anim .leaflet-zoom-hide { + visibility: hidden; + } + + +/* cursors */ + +.leaflet-clickable { + cursor: pointer; + } +.leaflet-container { + cursor: -webkit-grab; + cursor: -moz-grab; + } +.leaflet-popup-pane, +.leaflet-control { + cursor: auto; + } +.leaflet-dragging .leaflet-container, +.leaflet-dragging .leaflet-clickable { + cursor: move; + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + } + + +/* visual tweaks */ + +.leaflet-container { + background: #ddd; + outline: 0; + } +.leaflet-container a { + color: #0078A8; + } +.leaflet-container a.leaflet-active { + outline: 2px solid orange; + } +.leaflet-zoom-box { + border: 2px dotted #38f; + background: rgba(255,255,255,0.5); + } + + +/* general typography */ +.leaflet-container { + font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; + } + + +/* general toolbar styles */ + +.leaflet-bar { + box-shadow: 0 1px 5px rgba(0,0,0,0.65); + border-radius: 4px; + } +.leaflet-bar a, +.leaflet-bar a:hover { + background-color: #fff; + border-bottom: 1px solid #ccc; + width: 26px; + height: 26px; + line-height: 26px; + display: block; + text-align: center; + text-decoration: none; + color: black; + } +.leaflet-bar a, +.leaflet-control-layers-toggle { + background-position: 50% 50%; + background-repeat: no-repeat; + display: block; + } +.leaflet-bar a:hover { + background-color: #f4f4f4; + } +.leaflet-bar a:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } +.leaflet-bar a:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom: none; + } +.leaflet-bar a.leaflet-disabled { + cursor: default; + background-color: #f4f4f4; + color: #bbb; + } + +.leaflet-touch .leaflet-bar a { + width: 30px; + height: 30px; + line-height: 30px; + } + + +/* zoom control */ + +.leaflet-control-zoom-in, +.leaflet-control-zoom-out { + font: bold 18px 'Lucida Console', Monaco, monospace; + text-indent: 1px; + } +.leaflet-control-zoom-out { + font-size: 20px; + } + +.leaflet-touch .leaflet-control-zoom-in { + font-size: 22px; + } +.leaflet-touch .leaflet-control-zoom-out { + font-size: 24px; + } + + +/* layers control */ + +.leaflet-control-layers { + box-shadow: 0 1px 5px rgba(0,0,0,0.4); + background: #fff; + border-radius: 5px; + } +.leaflet-control-layers-toggle { + background-image: url(images/layers.png); + width: 36px; + height: 36px; + } +.leaflet-retina .leaflet-control-layers-toggle { + background-image: url(images/layers-2x.png); + background-size: 26px 26px; + } +.leaflet-touch .leaflet-control-layers-toggle { + width: 44px; + height: 44px; + } +.leaflet-control-layers .leaflet-control-layers-list, +.leaflet-control-layers-expanded .leaflet-control-layers-toggle { + display: none; + } +.leaflet-control-layers-expanded .leaflet-control-layers-list { + display: block; + position: relative; + } +.leaflet-control-layers-expanded { + padding: 6px 10px 6px 6px; + color: #333; + background: #fff; + } +.leaflet-control-layers-selector { + margin-top: 2px; + position: relative; + top: 1px; + } +.leaflet-control-layers label { + display: block; + } +.leaflet-control-layers-separator { + height: 0; + border-top: 1px solid #ddd; + margin: 5px -10px 5px -6px; + } + + +/* attribution and scale controls */ + +.leaflet-container .leaflet-control-attribution { + background: #fff; + background: rgba(255, 255, 255, 0.7); + margin: 0; + } +.leaflet-control-attribution, +.leaflet-control-scale-line { + padding: 0 5px; + color: #333; + } +.leaflet-control-attribution a { + text-decoration: none; + } +.leaflet-control-attribution a:hover { + text-decoration: underline; + } +.leaflet-container .leaflet-control-attribution, +.leaflet-container .leaflet-control-scale { + font-size: 11px; + } +.leaflet-left .leaflet-control-scale { + margin-left: 5px; + } +.leaflet-bottom .leaflet-control-scale { + margin-bottom: 5px; + } +.leaflet-control-scale-line { + border: 2px solid #777; + border-top: none; + line-height: 1.1; + padding: 2px 5px 1px; + font-size: 11px; + white-space: nowrap; + overflow: hidden; + -moz-box-sizing: content-box; + box-sizing: content-box; + + background: #fff; + background: rgba(255, 255, 255, 0.5); + } +.leaflet-control-scale-line:not(:first-child) { + border-top: 2px solid #777; + border-bottom: none; + margin-top: -2px; + } +.leaflet-control-scale-line:not(:first-child):not(:last-child) { + border-bottom: 2px solid #777; + } + +.leaflet-touch .leaflet-control-attribution, +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-bar { + box-shadow: none; + } +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-bar { + border: 2px solid rgba(0,0,0,0.2); + background-clip: padding-box; + } + + +/* popup */ + +.leaflet-popup { + position: absolute; + text-align: center; + } +.leaflet-popup-content-wrapper { + padding: 1px; + text-align: left; + border-radius: 12px; + } +.leaflet-popup-content { + margin: 13px 19px; + line-height: 1.4; + } +.leaflet-popup-content p { + margin: 18px 0; + } +.leaflet-popup-tip-container { + margin: 0 auto; + width: 40px; + height: 20px; + position: relative; + overflow: hidden; + } +.leaflet-popup-tip { + width: 17px; + height: 17px; + padding: 1px; + + margin: -10px auto 0; + + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + -o-transform: rotate(45deg); + transform: rotate(45deg); + } +.leaflet-popup-content-wrapper, +.leaflet-popup-tip { + background: white; + + box-shadow: 0 3px 14px rgba(0,0,0,0.4); + } +.leaflet-container a.leaflet-popup-close-button { + position: absolute; + top: 0; + right: 0; + padding: 4px 4px 0 0; + text-align: center; + width: 18px; + height: 14px; + font: 16px/14px Tahoma, Verdana, sans-serif; + color: #c3c3c3; + text-decoration: none; + font-weight: bold; + background: transparent; + } +.leaflet-container a.leaflet-popup-close-button:hover { + color: #999; + } +.leaflet-popup-scrolled { + overflow: auto; + border-bottom: 1px solid #ddd; + border-top: 1px solid #ddd; + } + +.leaflet-oldie .leaflet-popup-content-wrapper { + zoom: 1; + } +.leaflet-oldie .leaflet-popup-tip { + width: 24px; + margin: 0 auto; + + -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; + filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); + } +.leaflet-oldie .leaflet-popup-tip-container { + margin-top: -1px; + } + +.leaflet-oldie .leaflet-control-zoom, +.leaflet-oldie .leaflet-control-layers, +.leaflet-oldie .leaflet-popup-content-wrapper, +.leaflet-oldie .leaflet-popup-tip { + border: 1px solid #999; + } + + +/* div icon */ + +.leaflet-div-icon { + background: #fff; + border: 1px solid #666; + } diff --git a/www/plugins/gis/lib/leaflet/dist/leaflet.js b/www/plugins/gis/lib/leaflet/dist/leaflet.js new file mode 100644 index 0000000..03434b7 --- /dev/null +++ b/www/plugins/gis/lib/leaflet/dist/leaflet.js @@ -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;it;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;ni||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=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||in;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),(it.max.x||nt.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;is?(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';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("')}}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.xe.max.x&&(i|=2),t.ye.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+ei;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)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;i1))&&(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:'Leaflet'},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='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 index 0000000..b28b6d3 --- /dev/null +++ b/www/plugins/gis/lib/leaflet/plugins/Bing.js @@ -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 index 0000000..f1cd7cc --- /dev/null +++ b/www/plugins/gis/lib/leaflet/plugins/Control.FullScreen.js @@ -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 index 0000000..2baef61 --- /dev/null +++ b/www/plugins/gis/lib/leaflet/plugins/Control.MiniMap.js @@ -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 index 0000000..2b8f6c9 --- /dev/null +++ b/www/plugins/gis/lib/leaflet/plugins/GPX.Speed.js @@ -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 index 0000000..e2e7d6a --- /dev/null +++ b/www/plugins/gis/lib/leaflet/plugins/GPX.js @@ -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 += '

' + name + '

' + descr; + if (len) txt += '

' + this._humanLen(len) + '

'; + + 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 index 0000000..7b39281 --- /dev/null +++ b/www/plugins/gis/lib/leaflet/plugins/Google.js @@ -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 index 0000000..2a7839d --- /dev/null +++ b/www/plugins/gis/lib/leaflet/plugins/KML.js @@ -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('

' + name + '

' + 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 index 0000000..32a8741 --- /dev/null +++ b/www/plugins/gis/lib/leaflet/plugins/Marker.Rotate.js @@ -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 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 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 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 index 0000000..3357365 --- /dev/null +++ b/www/plugins/gis/lib/leaflet/plugins/leaflet-plugins.css @@ -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 index 0000000..62f650a --- /dev/null +++ b/www/plugins/gis/lib/leaflet/plugins/leaflet-providers.js @@ -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: + '© OpenStreetMap' + }, + 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 Humanitarian OpenStreetMap Team' + } + } + } + }, + OpenSeaMap: { + url: 'http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png', + options: { + attribution: 'Map data: © OpenSeaMap contributors' + } + }, + Thunderforest: { + url: 'http://{s}.tile.thunderforest.com/{variant}/{z}/{x}/{y}.png', + options: { + attribution: + '© OpenCycleMap, {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 GIScience Research Group @ University of Heidelberg — 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 OpenStreetMap Sweden — 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 MapQuest — ' + + '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 MapQuest — ' + + '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 MapBox — ' + + 'Map data {attribution.OpenStreetMap}', + subdomains: 'abcd' + } + }, + Stamen: { + url: 'http://{s}.tile.stamen.com/{variant}/{z}/{x}/{y}.png', + options: { + attribution: + 'Map tiles by Stamen Design, ' + + 'CC BY 3.0 — ' + + '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 © Esri' + }, + variants: { + WorldStreetMap: { + options: { + attribution: + '{attribution.Esri} — ' + + '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} — Copyright: ©2012 DeLorme' + } + }, + WorldTopoMap: { + options: { + variant: 'World_Topo_Map', + attribution: + '{attribution.Esri} — ' + + '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} — ' + + '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} — ' + + 'Source: USGS, Esri, TANA, DeLorme, and NPS' + } + }, + WorldShadedRelief: { + options: { + variant: 'World_Shaded_Relief', + maxZoom: 13, + attribution: '{attribution.Esri} — Source: Esri' + } + }, + WorldPhysical: { + options: { + variant: 'World_Physical_Map', + maxZoom: 8, + attribution: '{attribution.Esri} — Source: US National Park Service' + } + }, + OceanBasemap: { + options: { + variant: 'Ocean_Basemap', + maxZoom: 13, + attribution: '{attribution.Esri} — Sources: GEBCO, NOAA, CHS, OSU, UNH, CSUMB, National Geographic, DeLorme, NAVTEQ, and Esri' + } + }, + NatGeoWorldMap: { + options: { + variant: 'NatGeo_World_Map', + maxZoom: 16, + attribution: '{attribution.Esri} — 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} — Esri, DeLorme, NAVTEQ' + } + } + } + }, + OpenWeatherMap: { + url: 'http://{s}.tile.openweathermap.org/map/{variant}/{z}/{x}/{y}.png', + options: { + attribution: 'Map data © OpenWeatherMap', + 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 © 1987-2014 HERE', + subdomains: '1234', + mapID: 'newest', + 'app_id': '', + 'app_code': '', + 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: + '©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 Freemap.sk' + } + }, + MtbMap: { + url: 'http://tile.mtbmap.cz/mtbmap_tiles/{z}/{x}/{y}.png', + options: { + attribution: + '{attribution.OpenStreetMap} & USGS' + } + }, + CartoDB: { + url: 'http://{s}.basemaps.cartocdn.com/{variant}/{z}/{x}/{y}.png', + options: { + attribution: '{attribution.OpenStreetMap} © CartoDB', + 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 index 0000000..1ca4767 --- /dev/null +++ b/www/plugins/gis/lib/leaflet/plugins/leaflet.markercluster-src.js @@ -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: '
' + childCount + '
', 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) { + /// Argument from zoomanim if being called in a zoom animation or null otherwise + 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 () { + /// _unspiderfy but passes no arguments + 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 index 0000000..789c8a3 --- /dev/null +++ b/www/plugins/gis/lib/leaflet/plugins/leaflet.markercluster.css @@ -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 index 0000000..3ee2176 --- /dev/null +++ b/www/plugins/gis/modeles/carte_gis.html @@ -0,0 +1,148 @@ +[(#REM) + +Modele carte_gis +---------------- + +Parametres possibles : + +- id_map|id_carte_gis = 1 id de la carte +- lat|latit|latitude = 48.3 latitude du centre de la carte +- lon|lonxit|longitude = -4.7 longitude du centre de la carte +- zoom = 5 zoom de la carte +- maxZoom = 13 zoom maximum autorisé +- minZoom = 13 zoom minimum autorisé + +- default_layer = acetate nom de la couche affichée par défaut +- affiche_layers = openmapsurfer/acetate noms des couches proposées séparés par des / + +- 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 + +- width|largeur = 100% largeur de la carte, 100% par defaut +- height|hauteur = 400px hauteur de la carte, 400px par defaut +- style = non ne pas styler la carte + +- fullscreen = oui afficher un bouton pour passer la carte en plein écran +- zoom_molette|zoom_wheel = non désactiver le zoom avec la molette de la souris, actif par defaut +- control_type|controle_type = non ne pas afficher le contrôle de changement de couche +- control_type_collapsed = non afficher le contrôle de changement de couche replié (oui par défaut) +- no_control|aucun_controle = oui ne pas afficher les contrôles de la carte +- scale = oui afficher l'échelle de la carte +- overview = oui afficher une mini carte de situation + +- autocenterandzoom|centrer_auto = oui centrer et zoomer la carte automatiquement pour afficher tous les marqueurs +- localize_visitor|localiser_visiteur = oui centrer la carte sur la position du visiteur (API geolocation HTML5) +- localize_visitor_zoom = 12 niveau de zoom sur la position du visiteur (par défaut la valeur de zoom de la carte) +- id_a_ouvrir id_gis de l'infobulle à afficher au chargement(marqueur uniquement) + +- objets = gis type d'objets à afficher (fichier json/gis_xx qui génère la source de donnees) +- limit|limite = 500 nombre max de marqueurs à afficher, 500 par defaut +- kml = 12 kml à superposer à la carte (id_document ou url ou liste d'url) +- gpx = 12 gpx à superposer à la carte (id_document ou url ou liste d'url) +- geojson = 12 geojson à superposer à la carte (id_document ou url ou liste d'url) +- centrer_fichier = non permet de ne pas centrer la carte automatiquement sur les fichiers kml/gpx surperposés +- point = non si elle vaut "non" cette option n'affichera pas de points du tout (utile pour n'afficher qu'un kml par exemple) + +- media = non permet de passer le critère 'media' (pour les documents) +- mots = #LISTE{1,4,7} plugin critere {mots} http://contrib.spip.net/Critere-mots +- path_styles=#ARRAY{color,#fff} options de style des éléments de la couche GeoJSON (voir http://leafletjs.com/reference.html#path-options) + +Uniquement si objets = point_libre : +- icone = chemin/vers/image image utilisée pour le marker +- titre titre du point +- description description du point + +Clustering (regroupement de points proches) : +- cluster = oui Active le clustering +- clusterMaxZoom = 11 Regroupe les points jusque à ce zoom, mais pas au delà +- clusterShowCoverageOnHover = oui Affiche au survol du cluster le contour de la zone couverte par les points regroupés +- maxClusterRadius = 80 Le rayon maximal (en pixels) qu'un cluster couvrira (80 pixels par defaut) +- clusterSpiderfyOnMaxZoom = oui Active l'effet d'éclatement pour afficher les points qui se chevauchent +] + +[(#SET{width,#ENV{width,#ENV{largeur,100%}}})] +[(#SET{height,#ENV{height,#ENV{hauteur,400px}}})] +[(#SET{id,#ENV{id_carte_gis,#ENV{id_map,#ENV{id,1}}}})] +[(#REM) -- compat gis v1 -- ] +[(#SET{lat,#ENV{lat,#ENV{latit,#ENV{latitude,#CONFIG{gis/lat,0}}}}})] +[(#SET{lon,#ENV{lon,#ENV{lonxit,#ENV{longitude,#CONFIG{gis/lon,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{sw_lat,#ENV{sw_lat,#GET{lat}|moins{10}}} + #SET{sw_lon,#ENV{sw_lon,#GET{lon}|moins{10}}} + #SET{ne_lat,#ENV{ne_lat,#GET{lat}|plus{10}}} + #SET{ne_lon,#ENV{ne_lon,#GET{lon}|plus{10}}} +] + +
+ + diff --git a/www/plugins/gis/modeles/carte_gis.yaml b/www/plugins/gis/modeles/carte_gis.yaml new file mode 100644 index 0000000..7ccf033 --- /dev/null +++ b/www/plugins/gis/modeles/carte_gis.yaml @@ -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 index 0000000..abc2d72 --- /dev/null +++ b/www/plugins/gis/modeles/carte_gis_objet.html @@ -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} + +#SET{carte,oui} +#SET{point,oui} + +#SET{kml,#LISTE} #SET{gpx,#LISTE} #SET{json,#LISTE} + +#SET{#EXTENSION, #GET{#EXTENSION}|push{#ID_DOCUMENT}} +#SET{carte,oui} + + +[(#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 index 0000000..2e9ca57 --- /dev/null +++ b/www/plugins/gis/modeles/carte_gis_preview.html @@ -0,0 +1,112 @@ + +
+ + + diff --git a/www/plugins/gis/paquet.xml b/www/plugins/gis/paquet.xml new file mode 100644 index 0000000..e3da2d5 --- /dev/null +++ b/www/plugins/gis/paquet.xml @@ -0,0 +1,94 @@ + + + GIS + + + b_b + kent1 + Les Développements Durables + Leaflet + Leaflet plugins + Leaflet providers + Leaflet fullscreen + Leaflet minimap + 2011-2014 + GPL v3 + Icône de mattrich sous licence CC BY-NC-SA + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/www/plugins/gis/prive/contenu/gis_objet.html b/www/plugins/gis/prive/contenu/gis_objet.html new file mode 100644 index 0000000..74dbd33 --- /dev/null +++ b/www/plugins/gis/prive/contenu/gis_objet.html @@ -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'})] +
+ [(#INCLURE{fond=prive/inclure/gis_objet_formulaires,env,ajax})] +
+#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 index 0000000..dc6ab9b --- /dev/null +++ b/www/plugins/gis/prive/inclure/gis_objet_formulaires.html @@ -0,0 +1,41 @@ +
+ +#SET{gis_defaut,nouveau} +#SET{gis_defaut,glop} + + +#SET{bloc_gis, #ENV{bloc_gis,#GET{gis_defaut}|=={nouveau}|?{editer,lier}}} + + + +[(#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})] +] + + [(#GET{bloc_gis}|=={rechercher}|oui) +
+ #FORMULAIRE_RECHERCHER_GIS{#ENV{objet},#ENV{id_objet},#SELF|parametre_url{bloc_gis,lier}} +
] +
diff --git a/www/plugins/gis/prive/objets/contenu/gis.html b/www/plugins/gis/prive/objets/contenu/gis.html new file mode 100644 index 0000000..50766c6 --- /dev/null +++ b/www/plugins/gis/prive/objets/contenu/gis.html @@ -0,0 +1,23 @@ + +
+ #DESCRIPTIF +
+
+ #ADRESSE +
+
+ #REGION +
+
+ #DEPARTEMENT +
+
+ #CODE_POSTAL +
+
+ #VILLE +
+
+ #PAYS [((#CODE_PAYS))] +
+ diff --git a/www/plugins/gis/prive/objets/infos/gis.html b/www/plugins/gis/prive/objets/infos/gis.html new file mode 100644 index 0000000..b7856c9 --- /dev/null +++ b/www/plugins/gis/prive/objets/infos/gis.html @@ -0,0 +1,16 @@ + +
+
<:gis:info_numero_gis:>

#ID_GIS

+ + [(#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} + )] + ] +
+ diff --git a/www/plugins/gis/prive/objets/liste/gis.html b/www/plugins/gis/prive/objets/liste/gis.html new file mode 100644 index 0000000..a4f6e6c --- /dev/null +++ b/www/plugins/gis/prive/objets/liste/gis.html @@ -0,0 +1,40 @@ +[(#SET{defaut_tri,#ARRAY{ + multi titre,1, + multi pays,1, + multi ville,1, + id_gis,1, + points,-1 +}}) +] + +#ANCRE_PAGINATION +
+ +[] + + + + + + + + + + + + + + + + + + +
(#ENV*{titre,#GRAND_TOTAL|singulier_ou_pluriel{gis:info_1_gis,gis:info_nb_gis}})
[(#TRI{multi titre,<:info_titre:>,ajax})][(#TRI{multi pays,<:gis:label_pays:>,ajax})][(#TRI{multi ville,<:gis:label_ville:>,ajax})][(#TRI{id_gis,<:info_numero_abbreviation:>,ajax})]
[(#LOGO_GIS|image_reduire{20,20})][(#TITRE|sinon{<:ecrire:info_sans_titre:>})]#PAYS#VILLE#ID_GIS
+[

(#PAGINATION{prive})

] +
+
+[ +
(#ENV*{sinon,''})
+] \ 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 index 0000000..1770b5e --- /dev/null +++ b/www/plugins/gis/prive/objets/liste/gis_associer.html @@ -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");}}} + + +[(#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 +[

<:info_resultat_recherche:> «(#ENV{recherche})»

] +
+ +[] + + + + + + + + + + + + + + + + +
(#ENV*{titre,#GRAND_TOTAL|singulier_ou_pluriel{gis:info_1_gis,gis:info_nb_gis}}) + + + + + + +
[(#TRI{multi titre,<:info_titre:>,ajax})] 
[(#CHEMIN_IMAGE{gis-16.png}|balise_img)][(#LOGO_GIS|image_reduire{20,20})][(#TITRE|sinon{<:ecrire:info_sans_titre:>})]
+[

(#PAGINATION{prive})

] +
+
+[(#ENV{recherche}|oui) +
+[(#VAL{gis:info_recherche_gis_zero}|_T{#ARRAY{cherche_gis,#ENV{recherche}}})] + + + + + + +
+] 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 index 0000000..891b756 --- /dev/null +++ b/www/plugins/gis/prive/objets/liste/gis_associer_fonctions.php @@ -0,0 +1,3 @@ + \ 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 index 0000000..d4950cf --- /dev/null +++ b/www/plugins/gis/prive/objets/liste/gis_lies.html @@ -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}}} + + +#ANCRE_PAGINATION +
+ +[] + + + + + + + + + + + + + + + + +
(#ENV*{titre,#GRAND_TOTAL|singulier_ou_pluriel{gis:info_1_gis,gis:info_nb_gis}})
[(#TRI{multi titre,<:info_titre:>,ajax})] 
[(#CHEMIN_IMAGE{gis-16.png}|balise_img)][(#LOGO_GIS|image_reduire{20,20})][(#TITRE|sinon{<:ecrire:info_sans_titre:>})] + [(#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})] +
+[

(#PAGINATION{prive})

] +[(#GRAND_TOTAL|>{3}|oui)
] +[(#INCLURE{fond=modeles/carte_gis_preview,id_objet,objet})] +
+
+
+[(#ENV*{titre,<:gis:info_aucun_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 index 0000000..891b756 --- /dev/null +++ b/www/plugins/gis/prive/objets/liste/gis_lies_fonctions.php @@ -0,0 +1,3 @@ + \ 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 index 0000000..d0a22cb --- /dev/null +++ b/www/plugins/gis/prive/objets/liste/objets_gis.html @@ -0,0 +1,41 @@ +[(#SET{defaut_tri,#ARRAY{ + objet,#ENV{objet_sens,-1}, + id_objet,1 +}}) +] +#ANCRE_PAGINATION +
+ +[] + + + + + + + + + + + + + + + + + + + + +
(#ENV*{titre,#GRAND_TOTAL|singulier_ou_pluriel{gis:info_1_objet_gis,gis:info_nb_objets_gis}})
[(#TRI{objet,<:gis:info_objet:>,ajax})][(#TRI{id_objet,<:gis:info_id_objet:>,ajax})]<:gis:titre_objet:>
[(#OBJET|objet_icone{16})][(#OBJET|objet_info{texte_objet}|_T)][(#ID_OBJET)]#INFO_TITRE{#OBJET,#ID_OBJET} + [(#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})] +
+[

(#PAGINATION{prive})

] +
+
[ +
(#ENV*{sinon,''})
+] 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 index 0000000..688e540 --- /dev/null +++ b/www/plugins/gis/prive/objets/liste/objets_gis_simple.html @@ -0,0 +1,16 @@ + + +#ANCRE_PAGINATION +
+#SET{total,#GRAND_TOTAL} +

[(#GET{total}|singulier_ou_pluriel{gis:info_1_objet_gis,gis:info_nb_objets_gis})]

+ +[

(#PAGINATION{prive})

] +
+
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 index 0000000..7c3b5c2 --- /dev/null +++ b/www/plugins/gis/prive/squelettes/contenu/configurer_gis.html @@ -0,0 +1,6 @@ +[(#AUTORISER{configurer,gis}|sinon_interdire_acces)] +

<:gis:cfg_titre_gis:>

+ +
+#FORMULAIRE_CONFIGURER_GIS +
diff --git a/www/plugins/gis/prive/squelettes/contenu/gis.html b/www/plugins/gis/prive/squelettes/contenu/gis.html new file mode 100644 index 0000000..101a4a1 --- /dev/null +++ b/www/plugins/gis/prive/squelettes/contenu/gis.html @@ -0,0 +1,27 @@ + +[(#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})] + ] +

(#TITRE|sinon{<:info_sans_titre:>})[(#CHEMIN_IMAGE{gis-24.png}|balise_img{gis,cadre-icone})]

+],simple fiche_objet})] + +[(#INCLURE{fond=modeles/carte_gis_preview,id_gis})] + +
+ +
+ +,env,ajax} /> + +#BOITE_FERMER + +#PIPELINE{afficher_complement_objet,#ARRAY{args,#ARRAY{type,gis,id,#ID_GIS},data,'
'}} + +[(#EVAL{_AJAX}|oui) + +] + + +[(#ENV**{exec}|=={gis_edit}|?{#INCLURE{fond=prive/squelettes/contenu/gis_edit,redirect='',env,retourajax=oui},#REM|sinon_interdire_acces})] + 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 index 0000000..ca63efe --- /dev/null +++ b/www/plugins/gis/prive/squelettes/contenu/gis_edit.html @@ -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}} +] +
+
+ [(#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:>})] +

(#ENV{titre,#INFO_TITRE{gis,#ID_GIS}|sinon{<:gis:titre_nouveau_point:>}})

+ ] +
+ +#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();'} +
+] + [(#FORMULAIRE_EDITER_GIS{#ENV{id_gis,oui},#GET{objet},#GET{id_objet},#GET{redirect},'',#ENV{associer_objet}})] +[(#ENV{retourajax,''}|oui) +
+ +] +
\ 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 index 0000000..6a88b72 --- /dev/null +++ b/www/plugins/gis/prive/squelettes/contenu/gis_tous.html @@ -0,0 +1,19 @@ +
+
    + [
  • (#URL_ECRIRE{gis_tous}|parametre_url{afficher,carte}|lien_ou_expose{Carte,#ENV{afficher,carte}|=={carte},ajax})
  • ] + [
  • (#URL_ECRIRE{gis_tous}|parametre_url{afficher,liste}|lien_ou_expose{Liste,#ENV{afficher,carte}|=={liste},ajax})
  • ] +
+ [(#FORMULAIRE_RECHERCHE_ECRIRE{#SELF,ajax})] +
+ +[(#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 index 0000000..74dad10 --- /dev/null +++ b/www/plugins/gis/prive/squelettes/extra/gis.html @@ -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 index 0000000..ddd24d5 --- /dev/null +++ b/www/plugins/gis/prive/squelettes/hierarchie/gis.html @@ -0,0 +1,8 @@ + + <:gis:gis_pluriel:> + > + + #TITRE + + <:gis:titre_nouveau_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 index 0000000..f17f81d --- /dev/null +++ b/www/plugins/gis/prive/squelettes/hierarchie/gis_edit.html @@ -0,0 +1 @@ + \ 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 index 0000000..91a9d56 --- /dev/null +++ b/www/plugins/gis/prive/squelettes/navigation/gis_edit.html @@ -0,0 +1,9 @@ +[(#ID_GIS|oui) +#BOITE_OUVRIR{'','info'} +#PIPELINE{boite_infos,#ARRAY{data,'',args,#ARRAY{'type','gis','id',#ID_GIS}}} +#BOITE_FERMER + +
+#FORMULAIRE_EDITER_LOGO{'gis',#ID_GIS,'',#ENV**} +
+] 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 index 0000000..b210f49 --- /dev/null +++ b/www/plugins/gis/prive/squelettes/top/gis_tous.html @@ -0,0 +1,2 @@ +

<:gis:editer_gis_titre:>

+#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 index 0000000..eb7ea22 --- /dev/null +++ b/www/plugins/gis/prive/style_prive_plugin_gis.html @@ -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 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 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 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 index 0000000..a58fbdf --- /dev/null +++ b/www/plugins/gis/saisies/carte.html @@ -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}}} +] + +
  • +#ENV*{inserer_debut} +
    + +#ENV*{inserer_fin} +
  • +[(#GET{geocoder}|oui) +
  • + + + <:info_rechercher:> +
  • ] diff --git a/www/plugins/gis/svn.revision b/www/plugins/gis/svn.revision new file mode 100644 index 0000000..ec1599a --- /dev/null +++ b/www/plugins/gis/svn.revision @@ -0,0 +1,10 @@ + + +Origine: file:///home/svn/repository/spip-zone/_plugins_/gis/trunk +Revision: 86891 +Dernier commit: 2014-12-29 10:00:02 +0100 + +file:///home/svn/repository/spip-zone/_plugins_/gis/trunk +86891 +2014-12-29 10:00:02 +0100 + \ 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 index 0000000..b40ee7e --- /dev/null +++ b/www/plugins/gis/tests/gis_connect_sql.php @@ -0,0 +1,46 @@ +$essai) + $err = array_merge(tester_fun($f, $essai),$err); + + // si le tableau $err est pas vide ca va pas + if ($err) { + echo ('
    ' . join('', $err) . '
    '); + } 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 index 0000000..d3f03db --- /dev/null +++ b/www/plugins/icalendar/demo/iter_icalendar.html @@ -0,0 +1,31 @@ +#CACHE{0} + + +#SET{today,#DATE} +#SET{nextyear,#VAL{Y-m-d}|date{#VAL{next year}|strtotime}} + +

    Vacances scolaires

    + + +=#GET{today}} +{dtstart/str<=#GET{nextyear}} +> +[

    Vacances (#VALEUR{summary/value}|match{Zone.*$}|unique)

    ] + +
    +[(#SET{date,#VALEUR{dtstart/str}})] +[(#GET{date}|jour) ][(#GET{date}|nom_mois)][ (#GET{date}|annee|unique{#_zone:VALEUR})] +[(#SET{date,#VALEUR{dtend/str}})] +[ — (#GET{date}|jour) ][(#GET{date}|nom_mois)][ (#GET{date}|annee|unique{#_zone:VALEUR})] +
    +
    [(#VALEUR{summary/value}|replace{- Zone.*$})]
    + + + + + +

    A noter : le format ical implique que DTEND est un jour après la fin de l'évènement ; le calendrier indique donc le jour de rentrée et non le dernier jour des vacances.

    diff --git a/www/plugins/icalendar/http/ical.php b/www/plugins/icalendar/http/ical.php new file mode 100644 index 0000000..f86aaa4 --- /dev/null +++ b/www/plugins/icalendar/http/ical.php @@ -0,0 +1,66 @@ + $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 index 0000000..158a66b --- /dev/null +++ b/www/plugins/icalendar/http/ical/inc-event.html @@ -0,0 +1,27 @@ + +[(#REM) Mise en mémoire des infos géo si présentes ] + +#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} + +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:[(#TITRE|textebrut|filtrer_ical)]\, [(#TITRE|textebrut|filtrer_ical)] +URL:[(#URL_EVENEMENT{#ID_EVENEMENT}|url_absolue|filtrer_ical)] +STATUS:CONFIRMED +END:VEVENT + diff --git a/www/plugins/icalendar/icalendar.png b/www/plugins/icalendar/icalendar.png new file mode 100644 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 index 0000000..bd35dd3 --- /dev/null +++ b/www/plugins/icalendar/inc/ics_to_array.php @@ -0,0 +1,47 @@ +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 index 0000000..70b9f2a --- /dev/null +++ b/www/plugins/icalendar/lang/paquet-icalendar_fr.php @@ -0,0 +1,19 @@ + '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 index 0000000..dbb7a54 --- /dev/null +++ b/www/plugins/icalendar/lib/iCalcreator.class.php @@ -0,0 +1,10458 @@ + + * @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 + * @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 = + */ + 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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @since 0.3.0 - 2006-08-10 + * @return void + */ + function _makeVersion() { + $this->version = '2.0'; + } +/** + * set calendar version + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @since 2.16.12 - 2013-02-10 + * @param mixed $startY optional, start Year, default current Year ALT. array selecOptions ( *[ => ] ) + * @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)."
    \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)."
    \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)."
    \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
    \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
    \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)
    \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 )."
    \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 )."
    \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 )."
    "; // 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
    \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."
    ";$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 + * @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 + * @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 + * @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 + * @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 + * @since 2.10.16 - 2011-10-28 + * @return string + */ + function createCalendar() { + $calendarInit = $calendarxCaldecl = $calendarStart = $calendar = ''; + switch( $this->format ) { + case 'xcal': + $calendarInit = ''.$this->nl. + 'nl. + '"http://www.ietf.org/internet-drafts/draft-ietf-calsch-many-xcal-01.txt"'; + $calendarStart = '>'.$this->nl.'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.' $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 .= ''.$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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 = '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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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: + 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: + $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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @since 2.16.1 - 2012-11-26 + * Generates components for all transitions in a date range, based on contribution by Yitzchok Lavi + * 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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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)."
    \n";print_r($recur);echo "
    \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)."
    \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
    \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)."
    \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)."
    \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]."
    \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]."
    \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 "
    \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
    \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
    \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
    \n"; // test ### + if(( $daynoexists && $daynosw && $daynamesw ) || + ( !$daynoexists && !$daynosw && $daynamesw )) { + $updateOK = TRUE; + break; + } + } + } + } +// echo "efter BYDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "
    \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)."
    \n";//test + $bysetposymd1[] = $wdatets; + } + else { +// echo "bysetposymd2[]=".date('Y-m-d H:i:s',$wdatets)."
    \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)."
    \n";//test + } +// echo "recur undate ".date('Y-m-d H:i:s',$wdatets)." okdatstart ".date('Y-m-d H:i:s',$startdatets)."
    \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 "
    \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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @since 2.14.1 - 2012-10-07 + * Modified to also return original string value by Yitzchok Lavi + * @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 " _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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 = ''; + $xmlstr .= ''; + $xmlstr .= ''; + $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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * adjusted for iCalcreator Kjell-Inge Gustafsson, kigkonsult + * @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 ' '."\n".' '.$timestamp.''.$disp.' '."\n".' '; // 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 index 0000000..16ecf89 --- /dev/null +++ b/www/plugins/icalendar/modeles/prochainement.html @@ -0,0 +1,43 @@ + +#ANCRE_PAGINATION + + + [(#VALEUR{dtend/str}|strtotime|>={#DATE|strtotime}|oui) + [(#VALEUR{dtstart/value/hour}|>{00}|?{ + [(#VALEUR{dtstart/value/month}|=={#VALEUR{dtend/value/month}}|?{ + [(#VALEUR{dtstart/value/day}|=={#VALEUR{dtend/value/day}}|?{ +
    + [(#SET{date_debut,#VALEUR{dtstart/str}})] + [(#SET{date_fin,#VALEUR{dtend/str}})] + Le [(#GET{date_debut}|jour) ][(#GET{date_debut}|nom_mois) ][(#GET{date_debut}|annee) ] de [(#GET{date_debut}|affdate{'G'})]:[(#GET{date_debut}|minutes )] à [(#GET{date_fin}|heures)]:[(#GET{date_fin}|minutes )] +
    +
    [(#VALEUR{summary/value})

    #VALEUR{description/0/value}]

    + , +
    + [(#SET{date_debut,#VALEUR{dtstart/str}})] + [(#SET{date_fin,#VALEUR{dtend/str}})] + Du [(#GET{date_debut}|jour) ][(#GET{date_debut}|nom_mois) ][(#GET{date_debut}|annee) ] + au [(#GET{date_fin}|jour) ][(#GET{date_fin}|nom_mois) ][(#GET{date_fin}|annee)] de [(#GET{date_debut}|affdate{'G'})]:[(#GET{date_debut}|minutes )] à [(#GET{date_fin}|affdate{'G'})]:[(#GET{date_fin}|minutes)] +
    +
    [(#VALEUR{summary/value})

    #VALEUR{description/0/value}]

    + , +
    + [(#SET{date_debut,#VALEUR{dtstart/str}})]) + Du [(#GET{date_debut}|jour) ][(#GET{date_debut}|nom_mois)] + [(#SET{date_fin,#VALEUR{dtend/str}})] + [ au (#GET{date_fin}|jour) ][(#GET{date_fin}|nom_mois) ][(#GET{date_fin}|annee)] [(#GET{date_debut}|heures)]:[(#GET{date_debut}|minutes )] +
    +
    [(#VALEUR{summary/value})

    #VALEUR{description/0/value}]

    })] })] + , +
    [(#SET{date_debut,#VALEUR{dtstart/str}})] + Le [(#GET{date_debut}|jour) ][(#GET{date_debut}|nom_mois) ][(#GET{date_debut}|annee)] +
    +
    [(#VALEUR{summary/value})

    #VALEUR{description/0/value}]

    })] + ] + + +#PAGINATION{precedent_suivant} +
    diff --git a/www/plugins/icalendar/paquet.xml b/www/plugins/icalendar/paquet.xml new file mode 100644 index 0000000..f6e318f --- /dev/null +++ b/www/plugins/icalendar/paquet.xml @@ -0,0 +1,20 @@ + + + iCalendar + + + Fil + + 2010-2011 + + GPL 3 + + diff --git a/www/plugins/icalendar/plugin.xml b/www/plugins/icalendar/plugin.xml new file mode 100644 index 0000000..201b581 --- /dev/null +++ b/www/plugins/icalendar/plugin.xml @@ -0,0 +1,16 @@ + + iCalendar + Fil + GNU/GPL - (c) 2010-2011 + 0.4.0 + dev + Faire des boucles iCalendar (format ics) + icalendar + + icalendar.png + http://contrib.spip.net/Plugin-iCalendar + date + + + + \ No newline at end of file diff --git a/www/plugins/icalendar/svn.revision b/www/plugins/icalendar/svn.revision new file mode 100644 index 0000000..32e1b76 --- /dev/null +++ b/www/plugins/icalendar/svn.revision @@ -0,0 +1,10 @@ + + +Origine: file:///home/svn/repository/spip-zone/_plugins_/icalendar +Revision: 87001 +Dernier commit: 2015-01-06 21:00:03 +0100 + +file:///home/svn/repository/spip-zone/_plugins_/icalendar +87001 +2015-01-06 21:00:03 +0100 + \ 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 index 0000000..e66934f --- /dev/null +++ b/www/plugins/import_ics/action/supprimer_almanach.php @@ -0,0 +1,23 @@ + 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 index 0000000..89710c0 --- /dev/null +++ b/www/plugins/import_ics/action/supprimer_evenements_almanach.php @@ -0,0 +1,30 @@ + diff --git a/www/plugins/import_ics/action/synchro_almanach.php b/www/plugins/import_ics/action/synchro_almanach.php new file mode 100644 index 0000000..c0cd720 --- /dev/null +++ b/www/plugins/import_ics/action/synchro_almanach.php @@ -0,0 +1,32 @@ + \ 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 index 0000000..ef10101 --- /dev/null +++ b/www/plugins/import_ics/base/import_ics.php @@ -0,0 +1,119 @@ + '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 index 0000000..6f75491 --- /dev/null +++ b/www/plugins/import_ics/fabrique_diff.diff @@ -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 index 0000000..1c2897d --- /dev/null +++ b/www/plugins/import_ics/fabrique_import_ics.php @@ -0,0 +1,224 @@ + + 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' => ' + + + + +', + '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 index 0000000..e78a501 --- /dev/null +++ b/www/plugins/import_ics/formulaires/editer_almanach.html @@ -0,0 +1,91 @@ + + +
    +
    + [(#REM) titre pour un formulaire en plusieurs étapes +

    Création d'un almanach et importation d'événements : étape #ENV{_etape}/#ENV{_etapes}

    ] +

    Création d'un almanach et importation d'événements

    + [

    (#ENV**{message_ok})

    ] + [

    (#ENV*{message_erreur})

    ] + +[(#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] + + +#SET{tableau_type_evenement, #GET{tableau_type_evenement}|array_merge{#ARRAY{#TITRE,#ID_MOT}} + + +[(#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} + +#SET{tableau_ressources, #GET{tableau_ressources}|array_merge{#ARRAY{#ORR_RESSOURCE_NOM,#ID_ORR_RESSOURCE}} + +[(#SET{tableau_ressources, #GET{tableau_ressources}|array_flip})] + + [

    (#ENV**{message_ok})

    ] + [

    (#ENV**{message_erreur})

    ] + + [(#ENV{editable}) +
    + #ACTION_FORMULAIRE{#ENV{action}} + + +
      + + [(#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) +
    • +
      + + +
      +
    • + + [(#SAISIE{selection, id_ressource, obligatoire=non, + label=<:almanach:resa_auto:>, + explication=<:almanach:choix_salle:>, + datas=#GET{tableau_ressources} + })] + ] +
    + [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ] + + [(#REM)

    ça ce sera quand le formulaire en deux parties sera correct pour l'instant on fait un formulaire en une partie] +

    +
    + ] +
    +
    diff --git a/www/plugins/import_ics/formulaires/editer_almanach.php b/www/plugins/import_ics/formulaires/editer_almanach.php new file mode 100644 index 0000000..f83e301 --- /dev/null +++ b/www/plugins/import_ics/formulaires/editer_almanach.php @@ -0,0 +1,170 @@ +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" => , "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'=>''.$descriptif_array["value"].'','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']."
    "; + } + } 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 index 0000000..8131d60 --- /dev/null +++ b/www/plugins/import_ics/formulaires/editer_almanach_2.html @@ -0,0 +1,20 @@ +
    +
    +

    Création d'un almanach et importation d'événements : étape #ENV{_etape}/#ENV{_etapes}

    + [

    (#ENV*{message_ok})

    ] + [

    (#ENV*{message_erreur})

    ] +
    + [(#REM) les hidden qui declencheront le service du formulaire parametre : url d'action ] + #ACTION_FORMULAIRE{#ENV{action},#FORM} + +

    Les événements contenus à l'adresse #ENV{url} sont :

    + + + [(#ENV{url}|mon_filtre)] + + +

    Si ces événements sont ceux que vous vous attendiez à trouver, vous pouvez valider la saisie et les associer à l'almanach [(#ENV{titre}) ] et à l'article #INFO_TITRE{article,#ENV{id_article}}, sinon retournez à l'étape précédente.

    +

    +
    +
    +
    \ 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 index 0000000..a83a2c8 --- /dev/null +++ b/www/plugins/import_ics/genie/import_ics_synchro.php @@ -0,0 +1,135 @@ + "distant", + "url" => $r['url']); + $v = new vcalendar($config); + $v->parse(); + + echo "almanach".$r["id_almanach"]."
    "; + 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" => , "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'=>''.$descriptif_array["value"].'','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 index 0000000..bb71150 --- /dev/null +++ b/www/plugins/import_ics/genie/synchro.php @@ -0,0 +1,26 @@ + \ 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 index 0000000..614b787 --- /dev/null +++ b/www/plugins/import_ics/import_ics_administrations.php @@ -0,0 +1,63 @@ + \ 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 index 0000000..b4cd673 --- /dev/null +++ b/www/plugins/import_ics/import_ics_autorisations.php @@ -0,0 +1,126 @@ + \ 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 index 0000000..3b273e2 --- /dev/null +++ b/www/plugins/import_ics/import_ics_fonctions.php @@ -0,0 +1,52 @@ + "latp", + "url" => $url); +//var_dump($url); + $v = new vcalendar($config); + + $v->parse(); + + while ($comp = $v->getComponent()) { + echo "
    "; + + /*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 "attendee : ", str_replace('MAILTO:', '', $attendee)."
    "; + /*summary*/ + $summary_array = $comp->getProperty("summary", 1, true); + echo "summary : ", str_replace('SUMMARY:', '', $summary_array["value"]), "\n"; + /*categorie*/ + $categories = $comp->getProperty("categories"); + echo "categories : ", $categories."
    "; + + echo "
    "; + } + +//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 index 0000000..736ebea --- /dev/null +++ b/www/plugins/import_ics/import_ics_pipelines.php @@ -0,0 +1,33 @@ +'*'),'*'); + 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 index 0000000..f7004a9 --- /dev/null +++ b/www/plugins/import_ics/lang/almanach_fr.php @@ -0,0 +1,68 @@ + '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 index 0000000..4474a10 --- /dev/null +++ b/www/plugins/import_ics/lang/import_ics_fr.php @@ -0,0 +1,12 @@ + '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 index 0000000..3a7880b --- /dev/null +++ b/www/plugins/import_ics/lang/paquet-import_ics_fr.php @@ -0,0 +1,14 @@ + '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 index 0000000..fb278f4 --- /dev/null +++ b/www/plugins/import_ics/paquet.xml @@ -0,0 +1,39 @@ + + + + Import_ics + + Amaury + + GNU/GPL + + + + + + + + + + + + + + + + + + + + \ 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 index 0000000..d33765b --- /dev/null +++ b/www/plugins/import_ics/prive/objets/contenu/almanach.html @@ -0,0 +1,88 @@ + + +[
    + + (#TITRE) +
    ] + +[
    + + (#URL) +
    ] + +
    + + + #INFO_TITRE{article,#ID_ARTICLE} (article n°#ID_ARTICLE) + +
    + +
    + + + #TITRE (mot n°#ID_MOT) + + +

    +
    + + + +#ANCRE_PAGINATION +
    + [(#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 ] + + + + + + + + + + + + + + + + + + + + +
    <:almanach:info_evenement_almanach:>
    <:date:><:seminaire:attendee:><:info_titre:>
    [(#DATE_DEBUT|agenda_affdate_debut_fin{#DATE_FIN,#HORAIRE,'hcal'})]#ATTENDEE + #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] + [(#TITRE|replace{#GET{attendee}\h-\h})]
    + [<:seminaire:lieu:> : (#LIEU)] +
    +[

    (#PAGINATION{prive})

    ] +
    +
    + +

    <:almanach:aucun_evenement:>

    +[(#BOUTON_ACTION{<:almanach:retour_liste:>,#URL_ECRIRE{almanachs}})] +

    <:almanach:regenerer_almanach:>

    + + + + [(#MAJ|oui) +
    <:almanach:info_derniere_synchronisation:>[ (#MAJ|affdate_heure)]
    + ] + +
    + [(#BOUTON_ACTION{<:almanach:lien_synchro_almanach:>,#URL_ACTION_AUTEUR{synchro_almanach,#ID_ALMANACH,#SELF},ajax,<:almanach:confirmation_mise_a_jour_evenements{titre_almanach=#TITRE}:>,})] +
    + + +
    + + +
    <:almanach:purger_almanach:>
    + [(#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)'})] + ] + +
    + + \ 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 index 0000000..5caae9f --- /dev/null +++ b/www/plugins/import_ics/prive/objets/liste/almanachs.html @@ -0,0 +1,55 @@ +[(#SET{defaut_tri,#ARRAY{ + titre,1, + date,-1, + id_almanach,1, + points,-1 +}})] +#ANCRE_PAGINATION +
    + + [] + + + + + + + + + + + + + + + + + + + + + + + + +
    (#ENV*{titre,#GRAND_TOTAL|singulier_ou_pluriel{almanach:info_1_almanach,almanach:info_nb_almanachs}})
    [(#TRI{statut,#,ajax})][(#TRI{titre,<:almanach:label_titre:>,ajax})][(#TRI{date,<:date:>,ajax})][(#TRI{id_almanach,<:info_numero_abbreviation:>,ajax})]
    [(#CHEMIN_IMAGE{almanach-16.png}|balise_img)][(#STATUT|puce_statut{almanach,#ID_ALMANACH})][(#LOGO_ALMANACH|image_reduire{20,26})][(#RANG). ]#TITRE + + + #SET{utilise,#TOTAL_BOUCLE} + [(#TOTAL_BOUCLE|singulier_ou_pluriel{almanach:un_evenement,almanach:plusieurs_evenements})] + + + [(#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)'})] + ] + ] + [(#DATE|affdate_jourcourt)][(#AUTORISER{modifier,almanach,#ID_ALMANACH}|?{ + #ID_ALMANACH, + #ID_ALMANACH + })]
    +[

    (#PAGINATION{prive})

    ] +
    +
    [ +
    (#ENV*{sinon,''})
    +] \ 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 index 0000000..47cb340 --- /dev/null +++ b/www/plugins/import_ics/svn.revision @@ -0,0 +1,10 @@ + + +Origine: file:///home/svn/repository/spip-zone/_plugins_/import_ics +Revision: 83395 +Dernier commit: 2014-06-20 01:59:14 +0200 + +file:///home/svn/repository/spip-zone/_plugins_/import_ics +83395 +2014-06-20 01:59:14 +0200 + \ 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 index 0000000..4b2d4a7 --- /dev/null +++ b/www/plugins/pages/base/pages_tables.php @@ -0,0 +1,22 @@ + diff --git a/www/plugins/pages/content/articles-resume.html b/www/plugins/pages/content/articles-resume.html new file mode 100644 index 0000000..b3d1f9f --- /dev/null +++ b/www/plugins/pages/content/articles-resume.html @@ -0,0 +1,12 @@ + +
    + #ANCRE_PAGINATION +

    [(#ENV{titre,<:derniers_articles:>})]

    +
      + = 0} {!par date} {pagination #ENV{nb,5}}> + #INCLURE{fond=inclure/article-resume,id_article} + +
    + [

    (#PAGINATION)

    ] +
    +
    diff --git a/www/plugins/pages/demo/inc-articles.html b/www/plugins/pages/demo/inc-articles.html new file mode 100644 index 0000000..f7cbd77 --- /dev/null +++ b/www/plugins/pages/demo/inc-articles.html @@ -0,0 +1,10 @@ + +
      + +
    • => #ID_ARTICLE - #TITRE \(#STATUT\)
    • + +
    +
    +

    Aucun article

    + +
    diff --git a/www/plugins/pages/demo/pages.html b/www/plugins/pages/demo/pages.html new file mode 100644 index 0000000..eb37d3f --- /dev/null +++ b/www/plugins/pages/demo/pages.html @@ -0,0 +1,129 @@ +

    BOUCLES PAGES UNIQUES

    +

    BOUCLE_p1(ARTICLES) {page=mentions_legales}{id_rubrique=-1}

    + +
      + +
    • => #ID_ARTICLE - #TITRE \(#STATUT\)
    • + +
    +
    +

    Aucun article

    + +
    + +

    BOUCLE_p2(ARTICLES) {page?}

    + +
      + +
    • => #ID_ARTICLE - #TITRE \(#STATUT\)
    • + +
    +
    +

    Aucun article

    + +
    + +

    BOUCLE_p3(ARTICLES) {page}

    + +
      + +
    • => #ID_ARTICLE - #TITRE \(#STATUT\)
    • + +
    +
    +

    Aucun article

    + +
    + +

    BOUCLE_p4(ARTICLES){id_rubrique<0}

    + +
      + +
    • => #ID_ARTICLE - #TITRE \(#STATUT\)
    • + +
    +
    +

    Aucun article

    + +
    + +

    BOUCLE_p5(ARTICLES){id_rubrique=-1}

    + +
      + +
    • => #ID_ARTICLE - #TITRE \(#STATUT\)
    • + +
    +
    +

    Aucun article

    + +
    + +

    BOUCLE_p6(ARTICLES){id_rubrique=-1}{statut?}

    + +
      + +
    • => #ID_ARTICLE - #TITRE \(#STATUT\)
    • + +
    +
    +

    Aucun article

    + +
    + + +

    BOUCLES ARTICLES SANS INCLURE

    +

    BOUCLE_a1(ARTICLES)

    + +
      + +
    • => #ID_ARTICLE - #TITRE \(#STATUT\)
    • + +
    +
    +

    Aucun article

    + +
    + +

    BOUCLE_a2(ARTICLES){id_rubrique=2}

    + +
      + +
    • => #ID_ARTICLE - #TITRE
    • + +
    +
    +

    Aucun article

    + +
    + +

    BOUCLE_a2(ARTICLES){page=''}

    + +
      + +
    • => #ID_ARTICLE - #TITRE
    • + +
    +
    +

    Aucun article

    + +
    + + +

    BOUCLES ARTICLES AVEC INCLURE inc-articles

    +

    BOUCLE -> article : inclure rubrique=1

    + +
    + +

    BOUCLE -> article : inclure rubrique=2

    + +
    + +

    BOUCLE -> article : aucun parametre

    + +
    diff --git a/www/plugins/pages/formulaires/editer_identifiant_page.html b/www/plugins/pages/formulaires/editer_identifiant_page.html new file mode 100644 index 0000000..94b725b --- /dev/null +++ b/www/plugins/pages/formulaires/editer_identifiant_page.html @@ -0,0 +1,40 @@ +
    + [

    (#ENV*{message_ok})

    ] + [

    (#ENV*{message_erreur})

    ] + [(#ENV{editable}) +
    + [(#REM) declarer les hidden qui declencheront le service du formulaire + parametre : url d'action ] + #ACTION_FORMULAIRE{#ENV{action}} + ] +
      + #SET{name,champ_page} #SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}} #SET{obli,''} +
    • + + [(#GET{erreurs})] + + [(#ENV{#GET{name}}|sinon{<:pagesbis:info_aucun_champ_page:>})] + + [(#ENV{editable}) + + [<:bouton_changer:>] + + + + + ] +
    • +
    + [(#ENV{editable}) + [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ] + +

    +   + + + +

    +
    + ] +
    diff --git a/www/plugins/pages/formulaires/editer_identifiant_page.php b/www/plugins/pages/formulaires/editer_identifiant_page.php new file mode 100644 index 0000000..6df164b --- /dev/null +++ b/www/plugins/pages/formulaires/editer_identifiant_page.php @@ -0,0 +1,79 @@ + 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 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 index 0000000..2772e78 --- /dev/null +++ b/www/plugins/pages/lang/pages.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/www/plugins/pages/lang/pages_ar.php b/www/plugins/pages/lang/pages_ar.php new file mode 100644 index 0000000..c8b1af8 --- /dev/null +++ b/www/plugins/pages/lang/pages_ar.php @@ -0,0 +1,29 @@ + 'لا توجد صفحات في هذه اللحظة.', # 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 index 0000000..3c9900a --- /dev/null +++ b/www/plugins/pages/lang/pages_en.php @@ -0,0 +1,37 @@ + '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 index 0000000..8b38c1b --- /dev/null +++ b/www/plugins/pages/lang/pages_es.php @@ -0,0 +1,37 @@ + '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 index 0000000..ec5a92f --- /dev/null +++ b/www/plugins/pages/lang/pages_fa.php @@ -0,0 +1,29 @@ + ' براي الأن هيچ صفحه‌ي نيست', # 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 index 0000000..d4a286d --- /dev/null +++ b/www/plugins/pages/lang/pages_fr.php @@ -0,0 +1,35 @@ + '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 index 0000000..b0d85e0 --- /dev/null +++ b/www/plugins/pages/lang/pages_nl.php @@ -0,0 +1,37 @@ + '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 index 0000000..90c1053 --- /dev/null +++ b/www/plugins/pages/lang/pages_ru.php @@ -0,0 +1,29 @@ + 'Пока нет страниц.', # 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 index 0000000..ac4220c --- /dev/null +++ b/www/plugins/pages/lang/pages_sk.php @@ -0,0 +1,37 @@ + '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 index 0000000..1dbb464 --- /dev/null +++ b/www/plugins/pages/lang/paquet-pages.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/www/plugins/pages/lang/paquet-pages_ar.php b/www/plugins/pages/lang/paquet-pages_ar.php new file mode 100644 index 0000000..93f5e6f --- /dev/null +++ b/www/plugins/pages/lang/paquet-pages_ar.php @@ -0,0 +1,17 @@ + 'هذا البرنامج المساعد يسمح لك إنشاء صفحات من البنود التي لا ترتبط بأي تسلسل معين. بيد أنها قد تترافق مع اسم القالب +يأذن للإنشاء صفحة من المعلومات القانونية ، من نحن ، اتصال ، وما إلى ذلك. +', + '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 index 0000000..763b2bf --- /dev/null +++ b/www/plugins/pages/lang/paquet-pages_en.php @@ -0,0 +1,18 @@ + '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 index 0000000..40b6eb7 --- /dev/null +++ b/www/plugins/pages/lang/paquet-pages_es.php @@ -0,0 +1,16 @@ + '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 index 0000000..bb4d42b --- /dev/null +++ b/www/plugins/pages/lang/paquet-pages_fa.php @@ -0,0 +1,17 @@ + 'اين پلاگين اجازه‌ي ايجاد صفحه‌هاي مقاله‌هايي را مي‌دهد كه به هيچ سلسله مراتبي متكي نيستند. +در عضو مي‌توانند به نام يك اسلكت مرتبط شوند. +اين پلاگين اجازه‌ي ايجاد صفحه‌هايي مانند اطلاعات حقوقي، در باره‌ي ما، تماس با ما و غيره را خواهد داد.', + '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 index 0000000..672ddd0 --- /dev/null +++ b/www/plugins/pages/lang/paquet-pages_fr.php @@ -0,0 +1,15 @@ + '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 index 0000000..645f1d4 --- /dev/null +++ b/www/plugins/pages/lang/paquet-pages_nl.php @@ -0,0 +1,17 @@ + '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 index 0000000..70ac5ea --- /dev/null +++ b/www/plugins/pages/lang/paquet-pages_ru.php @@ -0,0 +1,16 @@ + 'Данный плагин позволяет создавать отдельные страницы вне любых разделов сайта. Это отличное решения для размещения контактной информации, условий и правил использования, а так же для любой информации, которая логически не встраивается в структуру сайта. +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 index 0000000..b2917f6 --- /dev/null +++ b/www/plugins/pages/lang/paquet-pages_sk.php @@ -0,0 +1,17 @@ + '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 index 0000000..2511025 --- /dev/null +++ b/www/plugins/pages/pages_administrations.php @@ -0,0 +1,55 @@ + diff --git a/www/plugins/pages/pages_autorisations.php b/www/plugins/pages/pages_autorisations.php new file mode 100644 index 0000000..2a8c26f --- /dev/null +++ b/www/plugins/pages/pages_autorisations.php @@ -0,0 +1,161 @@ + diff --git a/www/plugins/pages/pages_fonctions.php b/www/plugins/pages/pages_fonctions.php new file mode 100644 index 0000000..9f1d50c --- /dev/null +++ b/www/plugins/pages/pages_fonctions.php @@ -0,0 +1,37 @@ + ' 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 index 0000000..f8ba588 --- /dev/null +++ b/www/plugins/pages/pages_pipelines.php @@ -0,0 +1,383 @@ + 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 = "/(]*name=('|\")id_parent[^>]*>)/is"; + if (!preg_match($cherche,$flux['data'])) { + $cherche = "/(]*name=('|\")id_rubrique[^>]*>)/is"; + $remplace = "$1\n"; + $flux['data'] = preg_replace($cherche, $remplace, $flux['data']); + } + + // On cherche et remplace l'entete de la page : "modifier la page" + $cherche = "/(]*class=('|\")entete-formulaire.*?<\/span>).*?(

    .*?<\/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 = "/(]*class=(?:'|\")icone[^'\"]*retour[^'\"]*(?:'|\")>" + . "]*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'],"")) + $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 = "/]*class=('|\")editer editer_parent.*?<\/li>/is"; + $remplace = '
  • '; + $remplace .= ''; + $remplace .= ''; + $remplace .= ''; + $remplace .= ''; + if ($erreurs['champ_page']) + $remplace .= ''.$erreurs['champ_page'].''; + $value = $args['contexte']['champ_page'] ? $args['contexte']['champ_page'] : $args['contexte']['page']; + $remplace .= ''; + $remplace .= '
  • '; + if (preg_match($cherche,$flux['data'])) { + $flux['data'] = preg_replace($cherche, $remplace, $flux['data'],1); + $flux['data'] = preg_replace($cherche, '', $flux['data']); + } else { + $cherche = "/(]*class=('|\")editer editer_soustitre.*?<\/li>)/is"; + if (preg_match($cherche,$flux['data'])) { + $flux['data'] = preg_replace($cherche,'$1'.$remplace, $flux['data']); + } else { + $cherche = "/(]*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 = "" . _T('info_racine_site') . ""; + $remplace = "" . _T('pages:pages_uniques') . ""; + $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 index 0000000..2a6b98e --- /dev/null +++ b/www/plugins/pages/paquet.xml @@ -0,0 +1,36 @@ + + + Pages + + + RastaPopoulos + Les Développements Durables + + GPL v3 + + + + + + + + + + + + + + + + + + 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 index 0000000..7b912a4 --- /dev/null +++ b/www/plugins/pages/prive/objets/editer/identifiant_page.html @@ -0,0 +1,3 @@ +
    + #FORMULAIRE_EDITER_IDENTIFIANT_PAGE{#ENV{id_article}} +
    diff --git a/www/plugins/pages/prive/squelettes/contenu/pages.html b/www/plugins/pages/prive/squelettes/contenu/pages.html new file mode 100644 index 0000000..ba9a365 --- /dev/null +++ b/www/plugins/pages/prive/squelettes/contenu/pages.html @@ -0,0 +1,24 @@ +[(#AUTORISER{voir,_pages}|sinon_interdire_acces)] +

    <:pages:toutes_les_pages:>

    + +#SET{statuts,#SESSION{statut}|statuts_articles_visibles} +[(#ENV{id_auteur,''}|=={#SESSION{id_auteur}}|oui) + #SET{statuts,#GET{statuts}|array_merge{#LISTE{prepa}}} +] +, + 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 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 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 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 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 index 0000000..8b7b020 --- /dev/null +++ b/www/plugins/pages/saisies-vues/pages_uniques.html @@ -0,0 +1,5 @@ + +

    #TITRE (#PAGE)

    + +

    <:saisies:vue_sans_reponse:>

    + \ 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 index 0000000..2b4d2e3 --- /dev/null +++ b/www/plugins/pages/saisies/pages_uniques.html @@ -0,0 +1,19 @@ +[(#REM) + Saisie permettant de sélectionner une page unique par son nom de page + + Parametres : + - 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 + + Exemple d'appel : + [(#SAISIE{pages_uniques,nom, + label=<:plugin:label:>, + })] +] + \ No newline at end of file diff --git a/www/plugins/pages/svn.revision b/www/plugins/pages/svn.revision new file mode 100644 index 0000000..cc45d84 --- /dev/null +++ b/www/plugins/pages/svn.revision @@ -0,0 +1,10 @@ + + +Origine: file:///home/svn/repository/spip-zone/_plugins_/pages/trunk +Revision: 86912 +Dernier commit: 2014-12-29 15:00:04 +0100 + +file:///home/svn/repository/spip-zone/_plugins_/pages/trunk +86912 +2014-12-29 15:00:04 +0100 + \ 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 index 0000000..7207238 --- /dev/null +++ b/www/plugins/ressource/inc/ressource.php @@ -0,0 +1,707 @@ +'.',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,"")!==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.''; +} + +function inc_ressource_dist($r) { + // $r contient tout le texte définissant la ressource : + // + + // 1. phraser le raccourci + $attrs = phraser_tag(' 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 : + ##### 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\w+) # attribute name + \s*=\s* + ( + (?P[\"\'])(?P.*?)(?P=quote) # a quoted value + | # or + (?P[^\s"\']+?)(?:\s+) # an unquoted value + ) + |(?P\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
    , 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 + if (isset($res['id_document'])) { +# return recuperer_fond('modeles/doc', $res); + } + + if ($res['type_document'] == 'PDF') { + return + recuperer_fond('modeles/application', $res); + } + + return +# "
    ".var_export($res,true)."
    " . + 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 = ''; + + } + // 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 ''; + } + if (in_array($s, array('d'))) { + $img = $r[1].'.jpg'; + return ''; + } + } + 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 ''; + } + if (in_array($s, array('d'))) { + $img = $r[1].'.jpg'; + return ''; + } + } + 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 ''; + } + if (in_array($s, array('d'))) { + $img = $r[1].'.jpg'; + return ''; + } + } + + // 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(' ', '~', $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 index 0000000..c3918b3 --- /dev/null +++ b/www/plugins/ressource/modeles/album_flickr.html @@ -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} + + +#SET{album_#CLE,#VALEUR**} + + + + +
    + +[(#REM) title=" " pour ne pas avoir de titre ] +[
    (#ENV{title}|sinon{#GET{album_photoset}|table_valeur{title}}|trim)
    ] + +
      + +
    • + + + +
    • + + +
    +
    +
    + + Album introuvable + \ 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 index 0000000..1ce59f0 --- /dev/null +++ b/www/plugins/ressource/modeles/application.html @@ -0,0 +1,18 @@ +[(#EXTENSION|=={pdf}|oui) + + + +] diff --git a/www/plugins/ressource/modeles/ressource.html b/www/plugins/ressource/modeles/ressource.html new file mode 100644 index 0000000..3302aa0 --- /dev/null +++ b/www/plugins/ressource/modeles/ressource.html @@ -0,0 +1,34 @@ +[(#REM) + + Modele pour en
    + + 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,[ +
    (#TITRE|sinon{[(#DESCRIPTIF*|oui)]})
    [ +
    (#DESCRIPTIF|PtoBR)[(#NOTES|PtoBR)]
    + ] +] +})] + +[(#SET{caption,[(#CAPTION + |sinon{#LEGEND} + |sinon{#LEGENDE} + |propre|PtoBR + |sinon{#GET{captionspip}} +)]})] + +
    +[][][(#GET{url}|?{})][(#IMGCLASS|?{})][ +
    (#GET{caption})
    ] +
    diff --git a/www/plugins/ressource/modeles/ressource_embed.html b/www/plugins/ressource/modeles/ressource_embed.html new file mode 100644 index 0000000..c61a87c --- /dev/null +++ b/www/plugins/ressource/modeles/ressource_embed.html @@ -0,0 +1,32 @@ +[(#REM) + + Modele pour en
    + + 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,[ +
    (#TITRE|sinon{[(#DESCRIPTIF*|oui)]})
    [ +
    (#DESCRIPTIF|PtoBR)[(#NOTES|PtoBR)]
    + ] +] +})] + +[(#SET{caption,[(#CAPTION + |sinon{#LEGEND} + |sinon{#LEGENDE} + |propre|PtoBR + |sinon{#GET{captionspip}} +)]})] + +
    +[][(#ENV*{embed})][(#IMGCLASS|?{})][ +
    (#GET{caption})
    ] +
    diff --git a/www/plugins/ressource/paquet.xml b/www/plugins/ressource/paquet.xml new file mode 100644 index 0000000..a8d2acf --- /dev/null +++ b/www/plugins/ressource/paquet.xml @@ -0,0 +1,22 @@ + + Ressource + + Fil + + GNU/GPL + + + + + + + + \ No newline at end of file diff --git a/www/plugins/ressource/plugin.xml b/www/plugins/ressource/plugin.xml new file mode 100644 index 0000000..b039b64 --- /dev/null +++ b/www/plugins/ressource/plugin.xml @@ -0,0 +1,32 @@ + + Raccourci <ressource> + + collectif + + + © 2012 - GNU/GPL + + + 0.3.0 + + + dev + + + Un raccourci permettant d'intégrer une ressource externe en indiquant entre chevrons son URL ou son nom de fichier. + + http://contrib.spip.net/Ressource + + ressource + + + + edition + ressource_meta + + + + + \ No newline at end of file diff --git a/www/plugins/ressource/svn.revision b/www/plugins/ressource/svn.revision new file mode 100644 index 0000000..6315099 --- /dev/null +++ b/www/plugins/ressource/svn.revision @@ -0,0 +1,10 @@ + + +Origine: file:///home/svn/repository/spip-zone/_plugins_/ressource +Revision: 84614 +Dernier commit: 2014-09-14 23:33:31 +0200 + +file:///home/svn/repository/spip-zone/_plugins_/ressource +84614 +2014-09-14 23:33:31 +0200 + \ 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 index 0000000..c74ba2b --- /dev/null +++ b/www/plugins/rss_article_3_0/base/rssarticle.php @@ -0,0 +1,57 @@ + "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’une 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 index 0000000..12d5536 --- /dev/null +++ b/www/plugins/rss_article_3_0/exec/rss_article.php @@ -0,0 +1,47 @@ +

    \n"; // outch ! aie aie aie ! au secours ! + echo gros_titre(_T('rssarticle:activer_recopie_intro'),'', false); + + // colonne gauche + echo debut_gauche('', true); + echo debut_droite('', true); + + // centre de la page + genie_rssarticle_copie_dist("manuel"); + echo '
    '.date('Y/m/d H:i:s').'
    '._T('rssarticle:maj_manuelle').'
    '; + echo ''; + + + // pied + echo fin_gauche() . fin_page(); +} + +?> 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 index 0000000..7bd3197 --- /dev/null +++ b/www/plugins/rss_article_3_0/formulaires/configurer_rssarticle.html @@ -0,0 +1,74 @@ +
    +

    <:rssarticle:activer_recopie_intro:>

    + [

    (#ENV**{message_ok})

    ] + [

    (#ENV**{message_erreur})

    ] + [(#ENV{editable}) +
    + [(#REM) declarer les hidden qui declencheront le service du formulaire + parametre : url d'action ] + #ACTION_FORMULAIRE{#ENV{action}} + +
      +
    • +

      <:rssarticle:statut_article_importe:>

      +
        + +
      + +
    • +
    • +

      <:rssarticle:mode:>

      +
        +
        + + +
        +
        + + +
        +
      +
    • +
    • +

      <:rssarticle:cron_interval:>

      + + + +
    • +
    • +

      <:rssarticle:suivi_syndic:>

      +
        +
      • + + +
      • +
      • + + +
      • +
      +
    • +
    • +

      <:rssarticle:copie_logo:>

      + + + +
    • +
    • +

      <:rssarticle:html2spip:>

      + + + +
    • +
    + + [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ] + +

      +

    +
    + ] +
    \ 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 index 0000000..d523609 --- /dev/null +++ b/www/plugins/rss_article_3_0/formulaires/editer_rssarticle.html @@ -0,0 +1,22 @@ + +
    + + [

    (#ENV*{message_ok})

    ] + [

    (#ENV*{message_erreur})

    ] + +
    + #ACTION_FORMULAIRE{#ENV{action}} +
      + #SET{erreurs,#ENV**{erreurs}|table_valeur{#GET{name}}}#SET{obli,'obligatoire'} +
    • +
      + [(#CHEMIN_IMAGE{rssarticle-32.png}|balise_img{RSS})] + + +
      + [(#ENV*{erreurs}|table_valeur{rssarticle})] +
    • +
    + [(#ENV{editable}|oui)

    ] +
    +
    \ 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 index 0000000..5206ab7 --- /dev/null +++ b/www/plugins/rss_article_3_0/formulaires/editer_rssarticle.php @@ -0,0 +1,64 @@ + '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 index 0000000..5f5a1e8 --- /dev/null +++ b/www/plugins/rss_article_3_0/genie/rssarticle_copie.php @@ -0,0 +1,268 @@ +$titre, 'id_rubrique'=>$id_rubrique, + 'texte'=>$texte, 'statut'=>$import_statut, 'id_secteur'=>$id_secteur, + 'date'=> $lsDate, 'accepter_forum'=>$accepter_forum, 'lang'=>$lang, 'url_site'=>$url)); + + // lier article et site + sql_insertq( 'spip_articles_syndic', array('id_article'=>$id_article, 'id_syndic'=>$id_syndic)); + + // gestion auteur + $auteurs= explode(", ",$a['lesauteurs']); + foreach ($auteurs as $k => $auteur) { + if ($current_id_auteur = rssarticle_get_id_auteur($auteur)) + sql_insertq( 'spip_auteurs_liens', array('id_auteur'=>$current_id_auteur, 'id_objet'=>$id_article, 'objet'=>'article')); + } + + // tags a convertir en documents distants + $doc_distants = extraire_enclosures($tags); + foreach ($doc_distants as $k=>$doc_distant) { + $infos = recuperer_infos_distantes($doc_distant); + if ($infos['extension']) { + $ext = $infos['extension']; + $taille = $infos['tailles']; + $row = sql_fetsel("inclus", "spip_types_documents", "extension=" . sql_quote($ext) . " AND upload='oui'"); // extension autorisee ? + if ($row) { + $id_document = sql_insertq( 'spip_documents', array( + 'extension'=>$ext, + 'date'=> $lsDate, + 'fichier'=> $doc_distant, + 'taille'=> $taille, + 'mode' => 'document', + 'distant' => 'oui')); + + sql_insertq( 'spip_documents_liens', array( + 'id_document' =>$id_document, + 'id_objet'=> $id_article, + 'objet'=> 'article', + 'vu'=> 'non')); + } + } + + } + + // logo + if ($copie_logo) { + if ($logo_site = inc_chercher_logo_dist($id_syndic,"id_syndic")) { + $logo_article = "arton$id_article.".$logo_site[3]; + @copy($logo_site[0],_DIR_LOGOS."$logo_article"); + } + } + + $log_c++; + $log .= "\n - $titre"; + + // on "depublie" l'article syndique qui vient d'etre copie + sql_update("spip_syndic_articles", array('statut' => '"refuse"'), "id_syndic_article=$id_syndic_article"); + + // Mise à jour des dates de rubriques après création d'un article dedans + if ($id_article) { + if (function_exists('calculer_rubriques')) + calculer_rubriques(); + if (function_exists('calculer_langues_rubriques')) + calculer_langues_rubriques(); + if (function_exists('propager_les_secteurs')) + propager_les_secteurs(); + } + + } // test doublons + } + } // FIN PILE + + + // log et alerte email + $log .= "\n\n---------\nPlugin Copie RSS en Articles: $log_c articles copies\n"; + spip_log($log); + $log .= $GLOBALS['meta']['adresse_site']."/ecrire/?exec=accueil"; + + if ($email_alerte && $email_suivi !="" && $log_c > 0) + envoyer_mail($email_suivi,"Copie RSS en Articles", $log); + + // maintenance generale + // mode auto: on efface les syndic_articles de plus de 2 mois pour soulager le systeme (cf genie/syndic) + // attention: on efface sur l'ensemble des sites syndiques ss tenir compte de l'option + if ($mode_auto) sql_delete('spip_syndic_articles', "maj < DATE_SUB(NOW(), INTERVAL 2 MONTH) AND date < DATE_SUB(NOW(), INTERVAL 2 MONTH)"); + + return 1; +} + + +// +// recupere id d'un auteur selon son nom sinon le creer +function rssarticle_get_id_auteur($nom) { + if (trim($nom)=="") + return false; + + if ($row = sql_fetsel(array("id_auteur"),"spip_auteurs","nom=".sql_quote($nom))) + return $row['id_auteur']; + + // auteur inconnu, on le cree ... + return sql_insertq('spip_auteurs',array('nom'=>$nom,'statut'=>'1comite')); +} + +// +// extraire les documents taggues enclosure +// voir http://doc.spip.org/@afficher_enclosures +function extraire_enclosures($tags) { + $s = array(); + foreach (extraire_balises($tags, 'a') as $tag) { + if (extraire_attribut($tag, 'rel') == 'enclosure' + AND $t = extraire_attribut($tag, 'href')) { + $s[] = $t; + } + } + return $s; +} + +/** + * + * Nettoyer l'utf-8 et ses accents + * +**/ +function clean_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; +} + + +//passe le html en SPIP +//repris de memo.php, merci + +function html2spip($lapage){ + $lapage=clean_utf8($lapage); + + // remettre les double quotes casé par texte_backend + $lapage = str_replace('"','"',$lapage); + + // PRETRAITEMENTS + $lapage = str_replace("\n\r", "\r", $lapage); // echapper au greedyness de preg_replace + $lapage = str_replace("\n", "\r", $lapage); + + // itals + $lapage = preg_replace(",<(i|em)( [^>\r]*)?".">(.+),Uims", "{\\3}", $lapage); + + // gras (pas de {{ pour eviter tout conflit avec {) + $lapage = preg_replace(",<(b|h[4-6])( [^>]*)?".">(.+),Uims", "@@b@@\\3@@/b@@", $lapage); + $lapage = preg_replace(",]*)?".">(.+),Uims", "@@b@@\\2@@/b@@", $lapage); + + // entites + include_spip('inc/charsets'); + $lapage = html2unicode($lapage, true); //secure? + + // liens avec possibilités de non fermeture du tag + $lapage = preg_replace(",]*href=[^<>]*(http[^<>]*)[^<>]>(.*?)<,uims", "[\\2->\\1] <", $lapage); + + // images (cf ressource) + $lapage = preg_replace(",]*src=[^<>]*(http[^<>'\"]*)[^<>]*>,uims","[img]\\1[//img]", $lapage); + + + // intertitres + $lapage = preg_replace(",<(h[1-3])( [^>]*)?".">(.+),Uims", "\r{{{ \\3 }}}\r", $lapage); + // tableaux + $lapage = preg_replace(",]*)?".">,Uims", "
    \r", $lapage); + $lapage = preg_replace(",]*)?".">,Uims", " | ", $lapage); + + // POST TRAITEMENT + $lapage = str_replace("\r", "\n", $lapage); + + // SUPPRIME LES TAGS + if (eregi("(.*)", $lapage, $regs)) + $titre = textebrut($regs[1]); + $lapage = textebrut($lapage); + + // Suite tableaux + $lapage = preg_replace(",\n[| ]+\n,", "", $lapage); + $lapage = preg_replace(",\n[|].+?[|].+?[|].+,", "\\0|\r", $lapage); + + // retablir les gras + $lapage = preg_replace(",@@b@@(.*)@@/b@@,Uims","{{\\1}}",$lapage); + + //retablir les images pour les lire avec le plugin ressource + $lapage = preg_replace('#\[img\](.*)\[\//img\]#Umis', "<$1>", $lapage); + + //nettoyer les "] qui dépassent parfois + $lapage = preg_replace(",\"\],uims", "]", $lapage); + + return $lapage; +} + +?> \ 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 index 0000000..b6c94da --- /dev/null +++ b/www/plugins/rss_article_3_0/lang/paquet-rssarticle_fr.php @@ -0,0 +1,21 @@ + 'Ce plugin recopie les flux RSS (articles syndiqués) en articles + +-* reprise du contenu du flux; +-* créé l\'auteur s\'il est mentionné dans le flux; +-* ajoute les documents distants présents dans le flux; +-* dans le champs URL de l\'article, on indique l\'adresse de l\'article d\'origine. + +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 index 0000000..726711c --- /dev/null +++ b/www/plugins/rss_article_3_0/lang/rssarticle_ar.php @@ -0,0 +1,37 @@ + 'هذا المقال منقول عن موقع', + 'activer_recopie_intro' => 'تدفق RSS الى المقالات', + 'activer_recopie' => 'نسخ المقالات الناتحة عن تدفق RSS الى مقالات SPIP', + + // C + 'citer_source' => 'كر المصدر', + 'citer_source_oui' => 'ذكر عنوان المقال المصدر في المقال المستورد', + 'copie_logo' => 'نسخ شعار الموقع وجعله سعار المقال', + + // S + 'statut_article_importe' => 'وضعية المقالات المستوردة', + 'suivi_syndic' => 'متابعة الترخيص', + + // E + 'email_alerte' => 'إنذار بالبريد الإلكتروني عند كل ترخيص في مقال؟', + 'email_alerte_email' => 'في حال الموافقة، على أي عنوان يتم إرسال الإنذار؟', + + // I + 'install_rssarticle' => 'إنشاء جدول spip_articles_syndic', + + // M + 'mode' => 'وضعية التشغيل', + 'mode_auto' => 'الوضعية الآلية: نسخ كل المواقع المبوبة كمقالات', + 'mode_manuel' => 'الوضعية اليدوية: تحدد يدوياً المواقع المبوبة التي تريد نسخها كمقالات' + +); + + +?> 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 index 0000000..c8686b1 --- /dev/null +++ b/www/plugins/rss_article_3_0/lang/rssarticle_fr.php @@ -0,0 +1,49 @@ + 'Cet article est repris du site', + 'activer_recopie_intro' => 'Flux RSS en Articles', + 'activer_recopie' => 'Copier les articles issus de ce flux RSS en articles SPIP', + + // C + 'citer_source' => 'Citer la source', + 'citer_source_oui' => 'Citer l\'URL de l\'article d\'origine dans l\'article importé', + 'configuration_rssarticle' => 'Flux RSS en articles', + 'copie_logo' => 'Recopier le logo du site comme logo d\'article', + 'cron_interval' => 'Fréquence de la copie des flux RSS en articles', + 'cron_interval_timer' => 'Intervalle en seconde ', + + //R + 'html2spip' => 'Passer le HTML en syntaxe SPIP. Utilisez le plugin "ressource" pour afficher ensuite les images.', + + // S + 'statut_article_importe' => 'Statut des articles importés', + 'suivi_syndic' => 'Suivi de la syndication', + 'site_maj' => 'Option enregistrée', + + // E + 'email_alerte' => 'Prévenir par email à chaque nouvelle syndication en articles ?', + 'email_alerte_email' => 'Si oui, sur quel email ? ', + + // I + 'install_rssarticle' => 'Création de la table spip_articles_syndic', + + // M + 'maj_manuelle' => 'La copie manuelle des derniers flux RSS en articles a été effectuée', + 'maj_recharge' => 'Relancer la copie manuelle', + 'mode' => 'Mode de fonctionnement', + 'mode_auto' => 'Mode automatique: tous les sites référencés sont recopiés en articles', + 'mode_manuel' => 'Mode manuel: vous selectionnez manuellement les sites référencés qui doivent être recopiés en articles', + + // T + 'titre_page_configurer_rssarticle' => 'Copie RSS en articles' + +); + + +?> diff --git a/www/plugins/rss_article_3_0/paquet.xml b/www/plugins/rss_article_3_0/paquet.xml new file mode 100644 index 0000000..e238c7c --- /dev/null +++ b/www/plugins/rss_article_3_0/paquet.xml @@ -0,0 +1,21 @@ + + Flux RSS en articles + erational + LudoRA + GNU/GPL v3 + + + + + + + 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 index 0000000..6de5685 --- /dev/null +++ b/www/plugins/rss_article_3_0/prive/contenu/rssarticle.html @@ -0,0 +1,3 @@ +
    + #FORMULAIRE_EDITER_RSSARTICLE{#ID_SYNDIC} +
    \ 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 index 0000000..970c186 --- /dev/null +++ b/www/plugins/rss_article_3_0/prive/squelettes/contenu/configurer_rssarticle.html @@ -0,0 +1,7 @@ +[(#AUTORISER{configurer,_rssarticle}|sinon_interdire_acces)] + +

    <:rssarticle:titre_page_configurer_rssarticle:>

    + +
    + #FORMULAIRE_CONFIGURER_RSSARTICLE +
    \ 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 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 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 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 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 index 0000000..5548c82 --- /dev/null +++ b/www/plugins/rss_article_3_0/rssarticle_administrations.php @@ -0,0 +1,60 @@ +"._T('rssarticle:install_rssarticle')."

    "; + 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 "

    "._T('rssarticle:mise_a_jour_v03')."

    "; + 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 index 0000000..491da0c --- /dev/null +++ b/www/plugins/rss_article_3_0/rssarticle_pipelines.php @@ -0,0 +1,51 @@ +"; + $out .= "\n". recuperer_fond('prive/contenu/rssarticle',$contexte,array('ajax'=>false)); + $out .= "\n"; + //$out .= "\n". fin_cadre_relief(true); + if ($p=strpos($flux['data'],'')) + $flux['data'] = substr_replace($flux['data'],$out,$p,0); + + + } + } + return $flux; +} + + + +?> diff --git a/www/plugins/rss_article_3_0/svn.revision b/www/plugins/rss_article_3_0/svn.revision new file mode 100644 index 0000000..72eac51 --- /dev/null +++ b/www/plugins/rss_article_3_0/svn.revision @@ -0,0 +1,10 @@ + + +Origine: file:///home/svn/repository/spip-zone/_plugins_/rss_article/trunk +Revision: 86047 +Dernier commit: 2014-11-13 16:19:17 +0100 + +file:///home/svn/repository/spip-zone/_plugins_/rss_article/trunk +86047 +2014-11-13 16:19:17 +0100 + \ 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 index 0000000..a28e532 --- /dev/null +++ b/www/plugins/saisies/action/deplacer_saisie.php @@ -0,0 +1,38 @@ + diff --git a/www/plugins/saisies/aide/saisies.html b/www/plugins/saisies/aide/saisies.html new file mode 100644 index 0000000..9b07f38 --- /dev/null +++ b/www/plugins/saisies/aide/saisies.html @@ -0,0 +1,5 @@ +

    Références complètes des saisies

    + +[(#ENV{format}|=={brut}|oui)] diff --git a/www/plugins/saisies/balise/configurer_saisie.php b/www/plugins/saisies/balise/configurer_saisie.php new file mode 100644 index 0000000..33902c1 --- /dev/null +++ b/www/plugins/saisies/balise/configurer_saisie.php @@ -0,0 +1,28 @@ + diff --git a/www/plugins/saisies/balise/generer_saisies.php b/www/plugins/saisies/balise/generer_saisies.php new file mode 100644 index 0000000..83e4401 --- /dev/null +++ b/www/plugins/saisies/balise/generer_saisies.php @@ -0,0 +1,49 @@ + diff --git a/www/plugins/saisies/balise/saisie.php b/www/plugins/saisies/balise/saisie.php new file mode 100644 index 0000000..c891e55 --- /dev/null +++ b/www/plugins/saisies/balise/saisie.php @@ -0,0 +1,180 @@ +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 index 0000000..3498901 --- /dev/null +++ b/www/plugins/saisies/balise/voir_saisie.php @@ -0,0 +1,39 @@ + 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 index 0000000..609742d --- /dev/null +++ b/www/plugins/saisies/balise/voir_saisies.php @@ -0,0 +1,30 @@ + diff --git a/www/plugins/saisies/contenu/page-saisies_cvt.html b/www/plugins/saisies/contenu/page-saisies_cvt.html new file mode 100644 index 0000000..0d7a487 --- /dev/null +++ b/www/plugins/saisies/contenu/page-saisies_cvt.html @@ -0,0 +1,4 @@ +

    CVT automatique avec Saisies

    +

    Démonstration d'un formulaire CVT généré uniquement à partir d'une déclaration de Saisies

    + +#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 index 0000000..94415fa --- /dev/null +++ b/www/plugins/saisies/css/formulaires_constructeur.css @@ -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 index 0000000..f45fbd1 --- /dev/null +++ b/www/plugins/saisies/extra-vues/pays.html @@ -0,0 +1,8 @@ + +
    + #ENV{label_extra} + + #NOM + +
    +
    \ 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 index 0000000..df8b08f --- /dev/null +++ b/www/plugins/saisies/formulaires/construire_formulaire.html @@ -0,0 +1,219 @@ +[(#ENV{erreurs/positionner}|oui) + +] +
    + [

    (#ENV*{message_ok})

    ] +

    #ENV*{_message_attention}

    + [

    (#ENV*{message_erreur})

    ] + + +
    + [(#REM) declarer les hidden qui declencheront le service du formulaire + parametre : url d'action ] + #ACTION_FORMULAIRE{#ENV{action}} + + +
      + +
    • + +
    • + + + [(#VAL{saisie}|array_key_exists{#VALEUR}|oui) + [(#VALEUR**|formidable_generer_saisie_configurable{#ENV**|unserialize})] + ] + +
    • <:saisies:construire_aucun_champs:>
    • + + + +
    • + + + + +
    • +
      +
    + + + [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ] + + + +
    + + + + +
    diff --git a/www/plugins/saisies/formulaires/construire_formulaire.php b/www/plugins/saisies/formulaires/construire_formulaire.php new file mode 100644 index 0000000..675c159 --- /dev/null +++ b/www/plugins/saisies/formulaires/construire_formulaire.php @@ -0,0 +1,535 @@ +$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\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, + '
      ' + .recuperer_fond( + 'inclure/generer_saisies', + $env2 + ) + .'
    • + + + +
    • ' + .'
    ', + '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 index 0000000..faaedc4 --- /dev/null +++ b/www/plugins/saisies/formulaires/inc-construire_formulaire-actions.html @@ -0,0 +1,27 @@ +
    + #SET{nom,#ENV{identifiant,#ENV{nom}}} + [(#ENV{formulaire_config}|is_array|non) + [(#ENV{deplacable}|oui) + + <:saisies:construire_action_deplacer:> + + ] + + + + ] + [(#ENV{formulaire_config}|is_array|oui) + + + ] +
    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 index 0000000..fbb0c00 --- /dev/null +++ b/www/plugins/saisies/formulaires/inc-generer_saisies_configurables.html @@ -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}} +] + +[(#VAL{saisie}|array_key_exists{#VALEUR}|oui) + [(#VALEUR**|formidable_generer_saisie_configurable{#ENV{_env}|sinon{#ENV**|unserialize}})] +] + diff --git a/www/plugins/saisies/formulaires/inc-saisies-cvt.html b/www/plugins/saisies/formulaires/inc-saisies-cvt.html new file mode 100644 index 0000000..b87cb54 --- /dev/null +++ b/www/plugins/saisies/formulaires/inc-saisies-cvt.html @@ -0,0 +1,24 @@ +
    + [

    (#ENV**{message_ok})

    ] + [

    (#ENV**{message_erreur})

    ] + + [(#ENV{editable}|oui) +
    + [(#REM) declarer les hidden qui declencheront le service du formulaire + parametre : url d'action ] + #ACTION_FORMULAIRE{#ENV{action}} + +
      + #GENERER_SAISIES{#ENV{_saisies}} +
    + + [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ] + + +

    + + +

    +
    + ] +
    diff --git a/www/plugins/saisies/formulaires/saisies_cvt.html b/www/plugins/saisies/formulaires/saisies_cvt.html new file mode 100644 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 index 0000000..582fa56 --- /dev/null +++ b/www/plugins/saisies/formulaires/saisies_cvt.php @@ -0,0 +1,41 @@ + '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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 index 0000000..4a343f5 --- /dev/null +++ b/www/plugins/saisies/inc/saisies.php @@ -0,0 +1,494 @@ + array('form' => $form, 'args' => $args), + 'data' => $saisies + ) + ) + // Si c'est toujours un tableau après le pipeline + and is_array($saisies) + ){ + return $saisies; + } + else{ + return false; + } +} + +/* + * Cherche une saisie par son id, son nom ou son chemin et renvoie soit la saisie, soit son chemin + * + * @param array $saisies Un tableau décrivant les saisies + * @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 + * @param bool $retourner_chemin Indique si on retourne non pas la saisie mais son chemin + * @return array Retourne soit la saisie, soit son chemin, soit null + */ +function saisies_chercher($saisies, $id_ou_nom_ou_chemin, $retourner_chemin=false){ + + if (is_array($saisies) and $id_ou_nom_ou_chemin){ + if (is_string($id_ou_nom_ou_chemin)){ + $nom = $id_ou_nom_ou_chemin; + // identifiant ? premier caractere @ + $id = ($nom[0] == '@'); + + foreach($saisies as $cle => $saisie){ + $chemin = array($cle); + // notre saisie est la bonne ? + if ($nom == ($id ? $saisie['identifiant'] : $saisie['options']['nom'])) { + return $retourner_chemin ? $chemin : $saisie; + // sinon a telle des enfants ? et si c'est le cas, cherchons dedans + } elseif (isset($saisie['saisies']) and is_array($saisie['saisies']) and $saisie['saisies'] + and ($retour = saisies_chercher($saisie['saisies'], $nom, $retourner_chemin))) { + return $retourner_chemin ? array_merge($chemin, array('saisies'), $retour) : $retour; + } + + } + } + elseif (is_array($id_ou_nom_ou_chemin)){ + $chemin = $id_ou_nom_ou_chemin; + $saisie = $saisies; + // On vérifie l'existence quand même + foreach ($chemin as $cle){ + if (isset($saisie[$cle])) $saisie = $saisie[$cle]; + else return null; + } + // Si c'est une vraie saisie + if ($saisie['saisie'] and $saisie['options']['nom']) + return $retourner_chemin ? $chemin : $saisie; + } + } + + return null; +} + +/** + * Génère un nom unique pour un champ d'un formulaire donné + * + * @param array $formulaire + * Le formulaire à analyser + * @param string $type_saisie + * Le type de champ dont on veut un identifiant + * @return string + * Un nom unique par rapport aux autres champs du formulaire + */ +function saisies_generer_nom($formulaire, $type_saisie){ + $champs = saisies_lister_champs($formulaire); + + // Tant que type_numero existe, on incrémente le compteur + $compteur = 1; + while (array_search($type_saisie.'_'.$compteur, $champs) !== false) + $compteur++; + + // On a alors un compteur unique pour ce formulaire + return $type_saisie.'_'.$compteur; +} + +/* + * Crée un identifiant Unique + * pour toutes les saisies donnees qui n'en ont pas + * + * @param Array $saisies Tableau de saisies + * @param Bool $regenerer_id Régénère un nouvel identifiant pour toutes les saisies ? + * @return Array Tableau de saisies complété des identifiants + */ +function saisies_identifier($saisies, $regenerer = false) { + if (!is_array($saisies)) { + return array(); + } + foreach ($saisies as $k => $saisie) { + $saisies[$k] = saisie_identifier($saisie, $regenerer); + } + return $saisies; +} + +/** + * Crée un identifiant Unique + * pour la saisie donnee si elle n'en a pas + * (et pour ses sous saisies éventuels) + * + * @param Array $saisie Tableau d'une saisie + * @param Bool $regenerer_id Régénère un nouvel identifiant pour la saisie ? + * @return Array Tableau de la saisie complété de l'identifiant +**/ +function saisie_identifier($saisie, $regenerer = false) { + if (!isset($saisie['identifiant']) OR !$saisie['identifiant']) { + $saisie['identifiant'] = uniqid('@'); + } elseif ($regenerer) { + $saisie['identifiant'] = uniqid('@'); + } + if (isset($saisie['saisies']) AND is_array($saisie['saisies'])) { + $saisie['saisies'] = saisies_identifier($saisie['saisies'], $regenerer); + } + return $saisie; +} + +/* + * Vérifier tout un formulaire tel que décrit avec les Saisies + * + * @param array $formulaire Le contenu d'un formulaire décrit dans un tableau de Saisies + * @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). + * @return array Retourne un tableau d'erreurs + */ +function saisies_verifier($formulaire, $saisies_masquees_nulles=true){ + include_spip('inc/verifier'); + $erreurs = array(); + $verif_fonction = charger_fonction('verifier','inc',true); + + if ($saisies_masquees_nulles) + $formulaire = saisies_verifier_afficher_si($formulaire); + + $saisies = saisies_lister_par_nom($formulaire); + foreach ($saisies as $saisie){ + $obligatoire = isset($saisie['options']['obligatoire']) ? $saisie['options']['obligatoire'] : ''; + $champ = $saisie['options']['nom']; + $file = ($saisie['saisie'] == 'input' and isset($saisie['options']['type']) and $saisie['options']['type'] == 'file'); + $verifier = isset($saisie['verifier']) ? $saisie['verifier'] : false; + + // Si le nom du champ est un tableau indexé, il faut parser ! + if (preg_match('/([\w]+)((\[[\w]+\])+)/', $champ, $separe)){ + $valeur = _request($separe[1]); + preg_match_all('/\[([\w]+)\]/', $separe[2], $index); + // On va chercher au fond du tableau + foreach($index[1] as $cle){ + $valeur = isset($valeur[$cle]) ? $valeur[$cle] : null; + } + } + // Sinon la valeur est juste celle du nom + else + $valeur = _request($champ); + + // Pour la saisie "destinataires" il faut filtrer si jamais on a mis un premier choix vide + if ($saisie['saisie'] == 'destinataires') { + $valeur = array_filter($valeur); + } + + // On regarde d'abord si le champ est obligatoire + if ($obligatoire + and $obligatoire != 'non' + and ( + ($file and !$_FILES[$champ]['name']) + or (!$file and ( + is_null($valeur) + or (is_string($valeur) and trim($valeur) == '') + or (is_array($valeur) and count($valeur) == 0) + )) + ) + ) { + $erreurs[$champ] = + (isset($saisie['options']['erreur_obligatoire']) and $saisie['options']['erreur_obligatoire']) + ? $saisie['options']['erreur_obligatoire'] + : _T('info_obligatoire'); + } + + // On continue seulement si ya pas d'erreur d'obligation et qu'il y a une demande de verif + if ((!isset($erreurs[$champ]) or !$erreurs[$champ]) and is_array($verifier) and $verif_fonction){ + $normaliser = null; + // Si le champ n'est pas valide par rapport au test demandé, on ajoute l'erreur + $options = isset($verifier['options']) ? $verifier['options'] : array(); + if ($erreur_eventuelle = $verif_fonction($valeur, $verifier['type'], $options, $normaliser)) { + $erreurs[$champ] = $erreur_eventuelle; + // S'il n'y a pas d'erreur et que la variable de normalisation a été remplie, on l'injecte dans le POST + } elseif (!is_null($normaliser)) { + set_request($champ, $normaliser); + } + } + } + + return $erreurs; +} + +/** + * Applatie une description tabulaire + * @param string $tab, le tableau à aplatir + * @return $nouveau_tab + */ +function saisies_aplatir_tableau($tab){ + $nouveau_tab = array(); + foreach($tab as $entree=>$contenu){ + if (is_array($contenu)) { + foreach ($contenu as $cle => $valeur) { + $nouveau_tab[$cle] = $valeur; + } + } else { + $nouveau_tab[$entree] = $contenu; + } + } + return $nouveau_tab; +} + +/** + * Applatie une description chaînée, en supprimant les sous-groupes. + * @param string $chaine, la chaîne à aplatir + * @return $chaine + */ +function saisies_aplatir_chaine($chaine){ + return trim(preg_replace("#(?:^|\n)(\*(?:.*)|/\*)\n#i","\n",$chaine)); +} + +/** + * Transforme une chaine en tableau avec comme principe : + * + * - une ligne devient une case + * - si la ligne est de la forme truc|bidule alors truc est la clé et bidule la valeur + * - si la ligne commence par * alors on commence un sous-tableau + * - si la ligne est égale à /*, alors on fini le sous-tableau + * + * @param string $chaine Une chaine à transformer + * @return array Retourne un tableau PHP + */ +function saisies_chaine2tableau($chaine, $separateur="\n"){ + if ($chaine and is_string($chaine)){ + $tableau = array(); + $soustab = False; + // On découpe d'abord en lignes + $lignes = explode($separateur, $chaine); + foreach ($lignes as $i=>$ligne){ + $ligne = trim(trim($ligne), '|'); + // Si ce n'est pas une ligne sans rien + if ($ligne !== ''){ + // si ca commence par * c'est qu'on va faire un sous tableau + if (strpos($ligne,"*")===0) { + $soustab=True; + $soustab_cle = _T_ou_typo(substr($ligne,1), 'multi'); + if (!isset($tableau[$soustab_cle])){ + $tableau[$soustab_cle] = array(); + } + } + elseif ($ligne=="/*") {//si on finit sous tableau + $soustab=False; + } + else{//sinon c'est une entrée normale + // Si on trouve un découpage dans la ligne on fait cle|valeur + if (strpos($ligne, '|') !== false) { + list($cle,$valeur) = explode('|', $ligne, 2); + // permettre les traductions de valeurs au passage + if ($soustab == True){ + $tableau[$soustab_cle][$cle] = _T_ou_typo($valeur, 'multi'); + } else { + $tableau[$cle] = _T_ou_typo($valeur, 'multi'); + } + } + // Sinon on génère la clé + else{ + if ($soustab == True) { + $tableau[$soustab_cle][$i] = _T_ou_typo($ligne,'multi'); + } else { + $tableau[$i] = _T_ou_typo($ligne,'multi'); + } + } + } + } + } + return $tableau; + } + // Si c'est déjà un tableau on lui applique _T_ou_typo (qui fonctionne de manière récursive avant de le renvoyer + elseif (is_array($chaine)){ + return _T_ou_typo($chaine, 'multi'); + } else { + return array(); + } +} + +/** + * Transforme un tableau en chaine de caractères avec comme principe : + * + * - une case de vient une ligne de la chaine + * - chaque ligne est générée avec la forme cle|valeur + * - si une entrée du tableau est elle même un tableau, on met une ligne de la forme *clef + * - pour marquer que l'on quitte un sous-tableau, on met une ligne commencant par /*, sauf si on bascule dans un autre sous-tableau. + */ +function saisies_tableau2chaine($tableau){ + if ($tableau and is_array($tableau)){ + $chaine = ''; + $avant_est_tableau = False; + foreach($tableau as $cle=>$valeur){ + if (is_array($valeur)){ + $avant_est_tableau = True; + $ligne=trim("*$cle"); + $chaine .= "$ligne\n"; + $chaine .= saisies_tableau2chaine($valeur)."\n"; + } + else{ + if ($avant_est_tableau == True){ + $avant_est_tableau = False; + $chaine.="/*\n"; + } + $ligne = trim("$cle|$valeur"); + $chaine .= "$ligne\n"; + } + } + $chaine = trim($chaine); + + return $chaine; + } + // Si c'est déjà une chaine on la renvoie telle quelle + elseif (is_string($tableau)){ + return $tableau; + } + else{ + return ''; + } +} + + + + +/** + * Passe une valeur en tableau d'élements si ce n'en est pas une + * + * entrée : + * cle|valeur + * cle|valeur + * + * Sinon : + * valeur,valeur + * + * @param mixed $valeur + * @return array Tableau de valeurs +**/ +function saisies_valeur2tableau($valeur, $sinon_separateur="") { + if (is_array($valeur)) { + return $valeur; + } + + if (!strlen($valeur)) { + return array(); + } + + $t = saisies_chaine2tableau($valeur); + if (count($t) > 1) { + return $t; + } + + // qu'une seule valeur, c'est qu'elle a peut etre un separateur a virgule + // et a donc une cle est 0 dans ce cas la d'ailleurs + if (isset($t[0])) { + $t = saisies_chaine2tableau($t[0], ','); + } + + return $t; +} + + + + +/* + * Génère une page d'aide listant toutes les saisies et leurs options + */ +function saisies_generer_aide(){ + // On a déjà la liste par saisie + $saisies = saisies_lister_disponibles(); + + // On construit une liste par options + $options = array(); + foreach ($saisies as $type_saisie=>$saisie){ + $options_saisie = saisies_lister_par_nom($saisie['options'], false); + foreach ($options_saisie as $nom=>$option){ + // Si l'option n'existe pas encore + if (!isset($options[$nom])){ + $options[$nom] = _T_ou_typo($option['options']); + } + // On ajoute toujours par qui c'est utilisé + $options[$nom]['utilisee_par'][] = $type_saisie; + } + ksort($options_saisie); + $saisies[$type_saisie]['options'] = $options_saisie; + } + ksort($options); + + return recuperer_fond( + 'inclure/saisies_aide', + array( + 'saisies' => $saisies, + 'options' => $options + ) + ); +} + +/* + * Le tableau de saisies a-t-il une option afficher_si ? + * + * @param array $saisies Un tableau de saisies + * @return boolean + */ + +function saisies_afficher_si($saisies) { + $saisies = saisies_lister_par_nom($saisies,true); + // Dès qu'il y a au moins une option afficher_si, on l'active + foreach ($saisies as $saisie) { + if (isset($saisie['options']['afficher_si'])) + return true; + } + return false; +} + + +/* + * Le tableau de saisies a-t-il une option afficher_si_remplissage ? + * + * @param array $saisies Un tableau de saisies + * @return boolean + */ +function saisies_afficher_si_remplissage($saisies) { + $saisies = saisies_lister_par_nom($saisies,true); + // Dès qu'il y a au moins une option afficher_si_remplissage, on l'active + foreach ($saisies as $saisie) { + if (isset($saisie['options']['afficher_si_remplissage'])) + return true; + } + return false; +} +?> diff --git a/www/plugins/saisies/inc/saisies_afficher.php b/www/plugins/saisies/inc/saisies_afficher.php new file mode 100644 index 0000000..b4a713c --- /dev/null +++ b/www/plugins/saisies/inc/saisies_afficher.php @@ -0,0 +1,429 @@ + 0) { + return true; + } + if ($editable == 0) { + return false; + } + } + + // cas -1 + // name de la saisie + if (isset($champ['options']['nom'])) { + // si on a le name dans l'environnement, on le teste + $nom = $champ['options']['nom']; + if (isset($env[$nom])) { + return $env[$nom] ? true : false ; + } + } + // sinon, si on a des sous saisies + if (isset($champ['saisies']) and is_array($champ['saisies'])) { + foreach($champ['saisies'] as $saisie) { + if (saisie_editable($saisie, $env, false)) { + return true; + } + } + } + + // aucun des paramètres demandés n'avait de contenu + return false; +} + +/** + * Génère une saisie à partir d'un tableau la décrivant et de l'environnement + * + * @param array $champ + * Description de la saisie. + * Le tableau doit être de la forme suivante : + * array( + * 'saisie' => 'input', + * 'options' => array( + * 'nom' => 'le_name', + * 'label' => 'Un titre plus joli', + * 'obligatoire' => 'oui', + * 'explication' => 'Remplissez ce champ en utilisant votre clavier.' + * ) + * ) + * @param array $env + * Environnement du formulaire + * Permet de savoir les valeurs actuelles des contenus des saisies, + * les erreurs eventuelles présentes... + * @return string + * Code HTML des saisies de formulaire + */ +function saisies_generer_html($champ, $env=array()){ + // Si le parametre n'est pas bon, on genere du vide + if (!is_array($champ)) + return ''; + + // Si la saisie n'est pas editable, on sort aussi. + if (!saisie_editable($champ, $env)) { + return ''; + } + + $contexte = array(); + + // On sélectionne le type de saisie + $contexte['type_saisie'] = $champ['saisie']; + // Identifiant unique de saisie, si present + if (isset($champ['identifiant'])) { + $contexte['id_saisie'] = $champ['identifiant']; + } + + // Peut-être des transformations à faire sur les options textuelles + $options = $champ['options']; + foreach ($options as $option => $valeur){ + if ($option == 'datas') { + // exploser une chaine datas en tableau (applique _T_ou_typo sur chaque valeur) + $options[$option] = saisies_chaine2tableau($valeur); + } else { + $options[$option] = _T_ou_typo($valeur, 'multi'); + } + } + + // On ajoute les options propres à la saisie + $contexte = array_merge($contexte, $options); + + // Si env est définie dans les options ou qu'il y a des enfants, on ajoute tout l'environnement + if (isset($contexte['env']) or (isset($champ['saisies']) AND is_array($champ['saisies']))) { + unset($contexte['env']); + + // on sauve l'ancien environnement + // car les sous-saisies ne doivent pas être affectees + // par les modification sur l'environnement servant à generer la saisie mère + $contexte['_env'] = $env; + + // À partir du moment où on passe tout l'environnement, il faut enlever certains éléments qui ne doivent absolument provenir que des options + unset($env['inserer_debut']); + unset($env['inserer_fin']); + $saisies_disponibles = saisies_lister_disponibles(); + if (isset($saisies_disponibles[$contexte['type_saisie']]) and is_array($saisies_disponibles[$contexte['type_saisie']]['options'])) { + $options_a_supprimer = saisies_lister_champs($saisies_disponibles[$contexte['type_saisie']]['options']); + foreach ($options_a_supprimer as $option_a_supprimer){ + unset($env[$option_a_supprimer]); + } + } + + $contexte = array_merge($env, $contexte); + } + // Sinon on ne sélectionne que quelques éléments importants + else{ + // On récupère la liste des erreurs + $contexte['erreurs'] = $env['erreurs']; + // On ajoute toujours le bon self + $contexte['self'] = self(); + } + + // Dans tous les cas on récupère de l'environnement la valeur actuelle du champ + // Si le nom du champ est un tableau indexé, il faut parser ! + if (preg_match('/([\w]+)((\[[\w]+\])+)/', $contexte['nom'], $separe)){ + $contexte['valeur'] = $env[$separe[1]]; + preg_match_all('/\[([\w]+)\]/', $separe[2], $index); + // On va chercher au fond du tableau + foreach($index[1] as $cle){ + $contexte['valeur'] = isset($contexte['valeur'][$cle]) ? $contexte['valeur'][$cle] : null; + } + } + // Sinon la valeur est juste celle du nom + else { + $contexte['valeur'] = (isset($env[$contexte['nom']]) ? $env[$contexte['nom']] : null); + } + + // Si ya des enfants on les remonte dans le contexte + if (isset($champ['saisies']) and is_array($champ['saisies'])) + $contexte['saisies'] = $champ['saisies']; + + // On génère la saisie + return recuperer_fond( + 'saisies/_base', + $contexte + ); +} + +/** + * Génère une vue d'une saisie à partir d'un tableau la décrivant + * + * @see saisies_generer_html() + * @param array $saisie + * Tableau de description d'une saisie + * @param array $env + * L'environnement, contenant normalement la réponse à la saisie + * @param array $env_obligatoire + * ??? + * @return string + * Code HTML de la vue de la saisie + */ +function saisies_generer_vue($saisie, $env=array(), $env_obligatoire=array()){ + // Si le paramètre n'est pas bon, on génère du vide + if (!is_array($saisie)) + return ''; + + $contexte = array(); + + // On sélectionne le type de saisie + $contexte['type_saisie'] = $saisie['saisie']; + + // Peut-être des transformations à faire sur les options textuelles + $options = $saisie['options']; + foreach ($options as $option => $valeur){ + if ($option == 'datas') { + // exploser une chaine datas en tableau (applique _T_ou_typo sur chaque valeur) + $options[$option] = saisies_chaine2tableau($valeur); + } else { + $options[$option] = _T_ou_typo($valeur, 'multi'); + } + } + + // On ajoute les options propres à la saisie + $contexte = array_merge($contexte, $options); + + // Si env est définie dans les options ou qu'il y a des enfants, on ajoute tout l'environnement + if (isset($contexte['env']) or (isset($saisie['saisies']) AND is_array($saisie['saisies']))){ + unset($contexte['env']); + + // on sauve l'ancien environnement + // car les sous-saisies ne doivent pas être affectees + // par les modification sur l'environnement servant à generer la saisie mère + $contexte['_env'] = $env; + + // À partir du moment où on passe tout l'environnement, il faut enlever + // certains éléments qui ne doivent absolument provenir que des options + $saisies_disponibles = saisies_lister_disponibles(); + if (is_array($saisies_disponibles[$contexte['type_saisie']]['options'])){ + $options_a_supprimer = saisies_lister_champs($saisies_disponibles[$contexte['type_saisie']]['options']); + foreach ($options_a_supprimer as $option_a_supprimer){ + unset($env[$option_a_supprimer]); + } + } + + $contexte = array_merge($env, $contexte); + } + + // Dans tous les cas on récupère de l'environnement la valeur actuelle du champ + + // On regarde en priorité s'il y a un tableau listant toutes les valeurs + if ($env['valeurs'] and is_array($env['valeurs']) and isset($env['valeurs'][$contexte['nom']])){ + $contexte['valeur'] = $env['valeurs'][$contexte['nom']]; + } + // Si le nom du champ est un tableau indexé, il faut parser ! + elseif (preg_match('/([\w]+)((\[[\w]+\])+)/', $contexte['nom'], $separe)){ + $contexte['valeur'] = $env[$separe[1]]; + preg_match_all('/\[([\w]+)\]/', $separe[2], $index); + // On va chercher au fond du tableau + foreach($index[1] as $cle){ + $contexte['valeur'] = $contexte['valeur'][$cle]; + } + } + // Sinon la valeur est juste celle du nom + else { + // certains n'ont pas de nom (fieldset) + $contexte['valeur'] = isset($env[$contexte['nom']]) ? $env[$contexte['nom']] : ''; + } + + // Si ya des enfants on les remonte dans le contexte + if (isset($saisie['saisies']) AND is_array($saisie['saisies'])) + $contexte['saisies'] = $saisie['saisies']; + + if (is_array($env_obligatoire)) { + $contexte = array_merge($contexte, $env_obligatoire); + } + + // On génère la saisie + return recuperer_fond( + 'saisies-vues/_base', + $contexte + ); +} + +/** + * Génère, à partir d'un tableau de saisie le code javascript ajouté à la fin de #GENERER_SAISIES + * pour produire un affichage conditionnel des saisies ayant une option afficher_si ou afficher_si_remplissage. + * + * @param array $saisies + * Tableau de descriptions des saisies + * @param string $id_form + * Identifiant unique pour le formulaire + * @return text + * Code javascript + */ +function saisies_generer_js_afficher_si($saisies,$id_form){ + $i = 0; + $saisies = saisies_lister_par_nom($saisies,true); + $code = ''; + $code .= '(function($){'; + $code .= '$(document).ready(function(){chargement=true;'; + $code .= 'verifier_saisies_'.$id_form." = function(form){\n"; + foreach ($saisies as $saisie) { + // on utilise comme selecteur l'identifiant de saisie en priorite s'il est connu + // parce que li_class = 'tableau[nom][option]' ne fonctionne evidement pas + // lorsque le name est un tableau + if (isset($saisie['options']['afficher_si']) or isset($saisie['options']['afficher_si_remplissage'])) { + $i++; + // retrouver la classe css probable + switch ($saisie['saisie']) { + case 'fieldset': + $class_li = 'fieldset_'.$saisie['options']['nom']; + break; + case 'explication': + $class_li = 'explication_'.$saisie['options']['nom']; + break; + default: + $class_li = 'editer_'.$saisie['options']['nom']; + } + $afficher_si = isset($saisie['options']['afficher_si']) ? $saisie['options']['afficher_si'] : ''; + $afficher_si_remplissage = isset($saisie['options']['afficher_si_remplissage']) ? $saisie['options']['afficher_si_remplissage'] : ''; + $condition = join("\n", array_filter(array($afficher_si, $afficher_si_remplissage))); + // retrouver l'identifiant + $identifiant = ''; + if (isset($saisie['identifiant']) and $saisie['identifiant']) { + $identifiant = $saisie['identifiant']; + } + // On gère le cas @plugin:non_plugin@ + preg_match_all('#@plugin:(.+)@#U', $condition, $matches); + foreach ($matches[1] as $plug) { + if (defined('_DIR_PLUGIN_'.strtoupper($plug))) + $condition = preg_replace('#@plugin:'.$plug.'@#U', 'true', $condition); + else + $condition = preg_replace('#@plugin:'.$plug.'@#U', 'false', $condition); + } + // On gère le cas @config:plugin:meta@ suivi d'un test + preg_match_all('#@config:(.+):(.+)@#U', $condition, $matches); + foreach ($matches[1] as $plugin) { + $config = lire_config($plugin); + $condition = preg_replace('#@config:'.$plugin.':'.$matches[2][0].'@#U', '"'.$config[$matches[2][0]].'"', $condition); + } + // On transforme en une condition valide + preg_match_all('#@(.+)@#U', $condition, $matches); + foreach ($matches[1] as $nom) { + switch($saisies[$nom]['saisie']) { + case 'radio': + case 'oui_non': + $condition = preg_replace('#@'.preg_quote($nom).'@#U', '$(form).find("[name=\''.$nom.'\']:checked").val()', $condition); + break; + case 'case': + $condition = preg_replace('#@'.preg_quote($nom).'@#U', '($(form).find(".checkbox[name=\''.$nom.'\']").is(":checked") ? $(form).find(".checkbox[name=\''.$nom.'\']").val() : "")', $condition); + break; + case 'checkbox': + preg_match_all('#@(.+)@\s*==\s*"(.*)"$#U', $condition, $matches2); + foreach ($matches2[2] as $value) { + $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); + } + break; + default: + $condition = preg_replace('#@'.preg_quote($nom).'@#U', '$(form).find("[name=\''.$nom.'\']").val()', $condition); + } + } + if ($identifiant) { + $sel = "li[data-id='$identifiant']"; + } else { + $sel = "li.$class_li"; + } + $code .= "\tif (".$condition.') {$(form).find("'.$sel.'").show(400);} '."\n\t"; + $code .= 'else {if (chargement==true) {$(form).find("'.$sel.'").hide(400).css("display","none");} else {$(form).find("'.$sel.'").hide(400);};} '."\n"; + } + } + $code .= "};"; + $code .= '$("li#afficher_si_'.$id_form.'").parents("form").each(function(){verifier_saisies_'.$id_form.'(this);});'; + $code .= '$("li#afficher_si_'.$id_form.'").parents("form").change(function(){verifier_saisies_'.$id_form.'(this);});'; + $code .= 'chargement=false;})'; + $code .= '})(jQuery);'; + return $i>0 ? $code : ''; +} + +/** + * Lorsque l'on affiche les saisies (#VOIR_SAISIES), les saisies ayant une option afficher_si + * et dont les conditions ne sont pas remplies doivent être retirées du tableau de saisies. + * + * Cette fonction sert aussi lors de la vérification des saisies avec saisies_verifier(). + * À ce moment là, les saisies non affichées sont retirées de _request + * (on passe leur valeur à NULL). + * + * @param array $saisies + * Tableau de descriptions de saisies + * @param array|null $env + * Tableau d'environnement transmis dans inclure/voi_saisies.html, + * NULL si on doit rechercher dans _request (pour saisies_verifier()). + * @return array + * Tableau de descriptions de saisies + */ +function saisies_verifier_afficher_si($saisies, $env=NULL) { + // eviter une erreur par maladresse d'appel :) + if (!is_array($saisies)) { + return array(); + } + foreach ($saisies as $cle => $saisie) { + if (isset($saisie['options']['afficher_si'])) { + $condition = $saisie['options']['afficher_si']; + // On gère le cas @plugin:non_plugin@ + preg_match_all('#@plugin:(.+)@#U', $condition, $matches); + foreach ($matches[1] as $plug) { + if (defined('_DIR_PLUGIN_'.strtoupper($plug))) + $condition = preg_replace('#@plugin:'.$plug.'@#U', 'true', $condition); + else + $condition = preg_replace('#@plugin:'.$plug.'@#U', 'false', $condition); + } + // On gère le cas @config:plugin:meta@ suivi d'un test + preg_match_all('#@config:(.+):(.+)@#U', $condition, $matches); + foreach ($matches[1] as $plugin) { + $config = lire_config($plugin); + $condition = preg_replace('#@config:'.$plugin.':'.$matches[2][0].'@#U', '"'.$config[$matches[2][0]].'"', $condition); + } + // On transforme en une condition valide + if (is_null($env)) + $condition = preg_replace('#@(.+)@#U', '_request(\'$1\')', $condition); + else + $condition = preg_replace('#@(.+)@#U', '$env["valeurs"][\'$1\']', $condition); + eval('$ok = '.$condition.';'); + if (!$ok) { + unset($saisies[$cle]); + if (is_null($env)) set_request($saisie['options']['nom'],NULL); + } + } + if (isset($saisies[$cle]['saisies'])) // S'il s'agit d'un fieldset ou equivalent, verifier les sous-saisies + $saisies[$cle]['saisies'] = saisies_verifier_afficher_si($saisies[$cle]['saisies'], $env); + } + return $saisies; +} + +?> diff --git a/www/plugins/saisies/inc/saisies_lister.php b/www/plugins/saisies/inc/saisies_lister.php new file mode 100644 index 0000000..482e079 --- /dev/null +++ b/www/plugins/saisies/inc/saisies_lister.php @@ -0,0 +1,324 @@ + $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 index 0000000..9ccffda --- /dev/null +++ b/www/plugins/saisies/inc/saisies_manipuler.php @@ -0,0 +1,303 @@ + 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 index 0000000..70991bb --- /dev/null +++ b/www/plugins/saisies/inclure/configurer_saisie.html @@ -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 index 0000000..5285b93 --- /dev/null +++ b/www/plugins/saisies/inclure/configurer_saisie_fonctions.php @@ -0,0 +1,38 @@ + '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 index 0000000..9acd758 --- /dev/null +++ b/www/plugins/saisies/inclure/generer_saisies.html @@ -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}} + +] + + +[(#VAL{saisie}|array_key_exists{#VALEUR}|oui) +[(#VALEUR**|saisies_generer_html{#ENV{_env}|sinon{#ENV**|unserialize}})] +] + + +[(#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 index 0000000..6866cbe --- /dev/null +++ b/www/plugins/saisies/inclure/js_afficher_si.html @@ -0,0 +1,7 @@ +[(#REM) Ajout d'un marqueur unique pour identifier le formulaire] +[(#SET{id_unique,#EVAL{'rand();'}})] + \ 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 index 0000000..c3b6061 --- /dev/null +++ b/www/plugins/saisies/inclure/saisies_aide.html @@ -0,0 +1,65 @@ + +Sauter à : Toutes les saisies, Toutes les options + +

    Utilisation des options

    + +
    + + + + + + + + + + + + + + + + + + + +
    Options \ Saisies#CLE
    #CLE[(#CLE|in_array{#_options_tableau:UTILISEE_PAR}|?{'X','-'})]
    +
    +
    + +

    Toutes les saisies

    + +

    #TITRE (#CLE)

    +

    + Description : #DESCRIPTION +

    +

    + Options : + #CLE +

    + + +

    Toutes les options

    + +[(#SET{label, #LABEL|sinon{#LABEL_CASE}})] +

    [(#GET{label}|?{#GET{label} [ ((#CLE))], #CLE})]

    +[

    + Description : (#EXPLICATION|sinon{#LABEL|?{#LABEL_CASE}}) +

    ] + +

    + Utilisée par : + #VALEUR +

    +
    + +

    + Choix possibles : +

      + +
    • "#CLE" : #VALEUR
    • + +
    +

    +
    + diff --git a/www/plugins/saisies/inclure/voir_saisies.html b/www/plugins/saisies/inclure/voir_saisies.html new file mode 100644 index 0000000..7a77eee --- /dev/null +++ b/www/plugins/saisies/inclure/voir_saisies.html @@ -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}})] + +[(#VAL{saisie}|array_key_exists{#VALEUR}|oui) + [(#VALEUR|saisies_generer_vue{#ENV{_env}|sinon{#ENV**|unserialize}})] +] + diff --git a/www/plugins/saisies/javascript/saisies.js b/www/plugins/saisies/javascript/saisies.js new file mode 100644 index 0000000..9cdd288 --- /dev/null +++ b/www/plugins/saisies/javascript/saisies.js @@ -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 index 0000000..0f911f7 --- /dev/null +++ b/www/plugins/saisies/lang/paquet-saisies.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/www/plugins/saisies/lang/paquet-saisies_ar.php b/www/plugins/saisies/lang/paquet-saisies_ar.php new file mode 100644 index 0000000..bab403a --- /dev/null +++ b/www/plugins/saisies/lang/paquet-saisies_ar.php @@ -0,0 +1,18 @@ + 'يسهّل هذا الملحق إنشاء حقول للاستمارات من خلال توفيره علامة 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 index 0000000..4fb90f5 --- /dev/null +++ b/www/plugins/saisies/lang/paquet-saisies_de.php @@ -0,0 +1,17 @@ + '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 index 0000000..ac252f3 --- /dev/null +++ b/www/plugins/saisies/lang/paquet-saisies_en.php @@ -0,0 +1,19 @@ + '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 index 0000000..eb06ed8 --- /dev/null +++ b/www/plugins/saisies/lang/paquet-saisies_es.php @@ -0,0 +1,17 @@ + '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 index 0000000..3485022 --- /dev/null +++ b/www/plugins/saisies/lang/paquet-saisies_fr.php @@ -0,0 +1,17 @@ + '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 index 0000000..f0dc892 --- /dev/null +++ b/www/plugins/saisies/lang/paquet-saisies_fr_tu.php @@ -0,0 +1,19 @@ + '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 index 0000000..13b832b --- /dev/null +++ b/www/plugins/saisies/lang/paquet-saisies_nl.php @@ -0,0 +1,17 @@ + '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 index 0000000..1fc60c0 --- /dev/null +++ b/www/plugins/saisies/lang/paquet-saisies_ru.php @@ -0,0 +1,17 @@ + 'Этот плагин облегчает работу по созданию форм. Вам предоставляется возможность создавать поля (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 index 0000000..406bad6 --- /dev/null +++ b/www/plugins/saisies/lang/paquet-saisies_sk.php @@ -0,0 +1,17 @@ + '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 index 0000000..4059c75 --- /dev/null +++ b/www/plugins/saisies/lang/saisies.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/www/plugins/saisies/lang/saisies_ca.php b/www/plugins/saisies/lang/saisies_ca.php new file mode 100644 index 0000000..829f1d5 --- /dev/null +++ b/www/plugins/saisies/lang/saisies_ca.php @@ -0,0 +1,119 @@ + '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ó: [Obligatoire].', + '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 diversos destinataris.', # MODIF + 'option_type_choix_tous' => 'Posar tots aquests autors com a destinataris. L’usuari no tindrà cap tria.', + 'option_type_choix_un' => 'Permetre a l’usuari escollir un únic 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' => 'Sense resposta', # 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 index 0000000..10a80af --- /dev/null +++ b/www/plugins/saisies/lang/saisies_de.php @@ -0,0 +1,176 @@ + '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:[Pflichtfeld].', + '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 mehrerer Empfänger erlauben', + 'option_type_choix_tous' => 'Alle diese Autoren als Empfänger hinzufügen. Der Besucher kann keine Auswahl treffen.', + 'option_type_choix_un' => 'Der Besucher kann nur einen einzigen 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' => 'Ohne Antwort', + + // 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 index 0000000..6e8cd5a --- /dev/null +++ b/www/plugins/saisies/lang/saisies_en.php @@ -0,0 +1,193 @@ + '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 @.
    Example @selection_1@=="Toto" conditions the display of the field only when field selection_1 has a value of Toto.', + '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.
    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: [Obligatoire].', + '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 several message recipients.', + 'option_type_choix_tous' => 'Make all these authors as recipients. The user will not have choice.', + 'option_type_choix_un' => 'Allow the user to choose only one message recipient (as a dropdown list).', + 'option_type_choix_un_radio' => 'Allow the user to select one single 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' => '(no data entered)', + + // 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 index 0000000..c37fa17 --- /dev/null +++ b/www/plugins/saisies/lang/saisies_es.php @@ -0,0 +1,193 @@ + '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 @.
    Ejemplo @selection_1@=="Toto" condiciona la visualización del campo a que el campo selection_1 tenga por valor Toto.', + '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".
    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: [Obligatorio.', + '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 varias personas destinatarias.', + 'option_type_choix_tous' => 'Poner a todos estos autores como destinatarios. El usuario no tendrá ninguna opción.', + 'option_type_choix_un' => 'Permitir al usuario elegir sólo una persona destinataria (en forma de lista desplegable).', + 'option_type_choix_un_radio' => 'Permitir al usuario elegir sólo una 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' => 'Sin respuesta', + + // 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 index 0000000..5f3a866 --- /dev/null +++ b/www/plugins/saisies/lang/saisies_fa.php @@ -0,0 +1,135 @@ + 'مرور مقاله', + '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' => 'يك يا چند نويسنده كه كاربر مي‌تواند از ميانشان انتخاب كند. اگر كسي انتخاب نشود،‌ نويسنده‌اي كه سايت را نصب كرده انتخاب خواهد شد.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' => 'مي‌توانيد كاربرد پيش‌ گزيده‌ي الزامي را اصلاح كنيد: [Obligatoire].', + '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' => 'اجازه به كاربر در انتخاب چند دريافت كننده‌ي ايميل.', + 'option_type_choix_tous' => 'تمام مؤلفان دريافت‌كننده شوند. كاربر گزينه‌اي نخواهد داشت.', + 'option_type_choix_un' => 'اجازه به كاربر براي گزينش فقط يك دريافت‌كننده ', # 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' => 'بي‌ پاسخ', + + // 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 index 0000000..ce3dbd4 --- /dev/null +++ b/www/plugins/saisies/lang/saisies_fr.php @@ -0,0 +1,191 @@ + '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 @.
    Exemple @selection_1@=="Toto" conditionne l’affichage du champ à ce que le champ selection_1 ait pour valeur Toto.', + '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.
    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 : [Obligatoire].', + '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 plusieurs destinataires.', + 'option_type_choix_tous' => 'Mettre tous ces auteurs en destinataires. L’utilisateur n’aura aucun choix.', + 'option_type_choix_un' => 'Permettre à l’utilisateur de choisir un seul destinataire (sous forme de liste déroulante).', + 'option_type_choix_un_radio' => 'Permettre à l’utilisateur de choisir un seul 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' => 'Sans réponse', + + // 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 index 0000000..1dbdbc1 --- /dev/null +++ b/www/plugins/saisies/lang/saisies_fr_tu.php @@ -0,0 +1,190 @@ + '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 @.
    Exemple @selection_1@=="Toto" conditionne l’affichage du champ à ce que le champ selection_1 ait pour valeur Toto.', + '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.
    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 : [Obligatoire].', + '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 plusieurs destinataires.', + 'option_type_choix_tous' => 'Mettre tous ces auteurs en destinataires. L’utilisateur n’aura aucun choix.', + 'option_type_choix_un' => 'Permettre à l’utilisateur de choisir un seul destinataire.Permettre à l’utilisateur de choisir un seul destinataire (sous forme de liste déroulante).', + 'option_type_choix_un_radio' => 'Permettre à l’utilisateur de choisir un seul 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' => 'Sans réponse', + + // 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 index 0000000..de77118 --- /dev/null +++ b/www/plugins/saisies/lang/saisies_it.php @@ -0,0 +1,173 @@ + '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 : [Obbligatorio].', + '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 più destinatari.', + 'option_type_choix_tous' => 'Imposta tutti questi autori come destinatari. L’utente non avrà alcuna scelta.', + 'option_type_choix_un' => 'Consenti all’utente di scegliere un solo 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' => 'Senza risposta', + + // 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 index 0000000..76d61d5 --- /dev/null +++ b/www/plugins/saisies/lang/saisies_nl.php @@ -0,0 +1,193 @@ + '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 @ worden geplaatst.
    Bijvoorbeeld @selection_1@=="Toto" geeft de voorwaarde aan dat het veld moet worden getoond wanneer veld selection_1 de waarde Toto 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.
    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: [Obligatoire].', + '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 meerdere ontvangers kiezen.', + 'option_type_choix_tous' => 'Maak alle auteurs ontvanger. De gebruiker kan niet kiezen.', + 'option_type_choix_un' => 'Laat de gebruiker een enkele ontvanger kiezen.', + 'option_type_choix_un_radio' => 'Sta de gebruiker toe een enkele 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' => 'Zonder antwoord', + + // 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 index 0000000..e1634f0 --- /dev/null +++ b/www/plugins/saisies/lang/saisies_ru.php @@ -0,0 +1,176 @@ + 'Посмотреть статью', # 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' => 'Позволяет выбрать несколько получателей.', # MODIF + 'option_type_choix_tous' => 'Отметить всех авторов как получателей. Пользователю выбор не предоставляется.', # 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' => 'ничего не задано', + + // 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 index 0000000..b8504d2 --- /dev/null +++ b/www/plugins/saisies/lang/saisies_sk.php @@ -0,0 +1,193 @@ + '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 @.
    Príklad @selection_1@=="Toto" prikazuje zobraziť pole, ktoré selection_1 má hodnotu Toto.', + '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.
    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: [Povinné].', + '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 mnohých príjemcov.', + 'option_type_choix_tous' => 'Urobiť príjemcov zo všetkých týchto autorov. Používateľ nebude mať na výber.', + 'option_type_choix_un' => 'Povoliť používateľovi vybrať si jedného príjemcu (vo forme rozbaľovacieho menu).', + 'option_type_choix_un_radio' => 'Umožniť používateľovi vybrať si jediného 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' => 'Bez reakcie', + + // Z + 'z' => 'zzz' +); + +?> diff --git a/www/plugins/saisies/paquet.xml b/www/plugins/saisies/paquet.xml new file mode 100644 index 0000000..0e2de34 --- /dev/null +++ b/www/plugins/saisies/paquet.xml @@ -0,0 +1,29 @@ + + Saisies + Matthieu Marcillaud + RastaPopoulos + Joseph + Les Développements Durables + GNU/GPL + + + + + + + + + + + + + + diff --git a/www/plugins/saisies/prive/exec/construire_formulaire.html b/www/plugins/saisies/prive/exec/construire_formulaire.html new file mode 100644 index 0000000..5cd0537 --- /dev/null +++ b/www/plugins/saisies/prive/exec/construire_formulaire.html @@ -0,0 +1,3 @@ +

    <:saisies:info_configurer_saisies:>

    + +
    #FORMULAIRE_CONSTRUIRE_FORMULAIRE{test,#ARRAY,#ARRAY{modifier_nom,oui,nom_unique,oui}}
    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 index 0000000..c62602e --- /dev/null +++ b/www/plugins/saisies/prive/listes/articles_originaux_recursifs.html @@ -0,0 +1,14 @@ +[(#SET{iteration,#ENV{iteration,1}})] +[(#SET{separateur,[(#ENV{separateur}|concat{ › })]})] + + + + [(#ENV{multiple}|oui) + ] + [(#ENV{multiple}|non) + ] + + + + + diff --git a/www/plugins/saisies/prive/listes/rubriques_recursives.html b/www/plugins/saisies/prive/listes/rubriques_recursives.html new file mode 100644 index 0000000..368e56b --- /dev/null +++ b/www/plugins/saisies/prive/listes/rubriques_recursives.html @@ -0,0 +1,8 @@ +[(#SET{separateur,[(#ENV{separateur}|concat{ › })]})] + + [(#ENV{multiple}|oui) + ] + [(#ENV{multiple}|non) + ] + + diff --git a/www/plugins/saisies/saisies-vues/_base.html b/www/plugins/saisies/saisies-vues/_base.html new file mode 100644 index 0000000..85c612c --- /dev/null +++ b/www/plugins/saisies/saisies-vues/_base.html @@ -0,0 +1,42 @@ +[(#SET{sans_reponse,#ENV{sans_reponse}|is_null|?{<:saisies:vue_sans_reponse:>,#ENV{sans_reponse}}})] + +#SET{valeur_uniquement,#ENV{valeur_uniquement}|et{#ENV{valeur_uniquement}|!={non}}|oui} +#SET{enfants,#ENV*{saisies}|et{#ENV*{saisies}|is_array}} + +[(#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. ] + #SET{reponse,''} + [(#CHEMIN{saisies-vues/#ENV{type_saisie}.html}|oui) + #SET{reponse,#INCLURE{fond=saisies-vues/#ENV{type_saisie},env,sans_reponse=#GET{sans_reponse}}|trim} + ] + [(#CHEMIN{saisies-vues/#ENV{type_saisie}.html}|non) + #SET{reponse,#ENV*{valeur}|saisie_traitement_vue{#ENV**}} + ] + +[(#REM) Maintenant on affiche en encapsulant ou pas ] + +[(#REM) Cas normal avec présentation ] +[(#GET{valeur_uniquement}|non) +
    + [(#REM) S'il y a des enfants on n'inclut que la vue ] + [(#GET{enfants}|oui) + #GET{reponse} + ] + [(#GET{enfants}|non|et{#ENV{type_saisie}|!={explication}}) + [(#ENV{label_case,#ENV{label,#ENV{nom}}})] +
    + [(#GET{reponse}|sinon{#GET{sans_reponse}})] +
    + ] +
    +] + +[(#REM) Cas où on demande uniquement la valeur ] +[(#GET{valeur_uniquement}|oui) + [(#REM) S'il y a des enfants on inclut que la vue ] + [(#GET{enfants}|oui) + #GET{reponse} + ] + [(#GET{enfants}|non) + [(#GET{reponse}|sinon{#GET{sans_reponse}})] + ] +] diff --git a/www/plugins/saisies/saisies-vues/auteurs.html b/www/plugins/saisies/saisies-vues/auteurs.html new file mode 100644 index 0000000..e46baf5 --- /dev/null +++ b/www/plugins/saisies/saisies-vues/auteurs.html @@ -0,0 +1,16 @@ + + [(#SET{valeur,[(#ENV*{valeur}|is_array|?{[(#ENV*{valeur})],[(#ENV*{valeur}|explode{','})]})]})] + +
      + +
    • #NOM (#ID_AUTEUR)
    • + +
    +
    + + +

    #NOM (#ID_AUTEUR)

    + + diff --git a/www/plugins/saisies/saisies-vues/case.html b/www/plugins/saisies/saisies-vues/case.html new file mode 100644 index 0000000..12a6e3e --- /dev/null +++ b/www/plugins/saisies/saisies-vues/case.html @@ -0,0 +1 @@ +[

    (#ENV*{valeur}|?{<:item_oui:>,<:item_non:>})

    ] diff --git a/www/plugins/saisies/saisies-vues/checkbox.html b/www/plugins/saisies/saisies-vues/checkbox.html new file mode 100644 index 0000000..4818e29 --- /dev/null +++ b/www/plugins/saisies/saisies-vues/checkbox.html @@ -0,0 +1,12 @@ +[(#REM) datas peut être une chaine qu'on sait décomposer ] +#SET{datas, #ENV{datas}|saisies_chaine2tableau} +#SET{valeur, #ENV{valeur}|saisies_valeur2tableau} + +
      + + [
    • (#GET{datas/#VALEUR})
    • ] + + + [
    • #ENV{choix_alternatif_label} : (#GET{valeur/choix_alternatif})
    • ] +
    +
    diff --git a/www/plugins/saisies/saisies-vues/date.html b/www/plugins/saisies/saisies-vues/date.html new file mode 100644 index 0000000..d0173b9 --- /dev/null +++ b/www/plugins/saisies/saisies-vues/date.html @@ -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'}} + })] +] +[

    (#GET{valeur})

    ] diff --git a/www/plugins/saisies/saisies-vues/destinataires.html b/www/plugins/saisies/saisies-vues/destinataires.html new file mode 100644 index 0000000..12ac840 --- /dev/null +++ b/www/plugins/saisies/saisies-vues/destinataires.html @@ -0,0 +1,8 @@ +[(#SET{valeur,[(#ENV*{valeur}|is_array|?{[(#ENV*{valeur})],[(#ENV*{valeur}|explode{','})]})]})] + +
      + +
    • #NOM
    • + +
    +
    diff --git a/www/plugins/saisies/saisies-vues/explication.html b/www/plugins/saisies/saisies-vues/explication.html new file mode 100644 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 index 0000000..253044e --- /dev/null +++ b/www/plugins/saisies/saisies-vues/fieldset.html @@ -0,0 +1,5 @@ +[

    (#ENV{label})

    ] + +[(#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 index 0000000..a820b0b --- /dev/null +++ b/www/plugins/saisies/saisies-vues/groupe_mots.html @@ -0,0 +1,9 @@ +[(#SET{valeurs,[(#ENV{multiple}|oui|?{#ENV*{valeur},#ARRAY{0,#ENV{valeur}}})]})] + + +
      + +
    • #TITRE
    • + +
    +
    diff --git a/www/plugins/saisies/saisies-vues/mot.html b/www/plugins/saisies/saisies-vues/mot.html new file mode 100644 index 0000000..5e9f0a6 --- /dev/null +++ b/www/plugins/saisies/saisies-vues/mot.html @@ -0,0 +1,3 @@ +[(#REM) datas peut être une chaine qu'on sait décomposer ] +#SET{valeur, #ENV{valeur}|saisies_valeur2tableau} +

    #INFO_TITRE{mot,#VALEUR}

    diff --git a/www/plugins/saisies/saisies-vues/oui_non.html b/www/plugins/saisies/saisies-vues/oui_non.html new file mode 100644 index 0000000..dcd2157 --- /dev/null +++ b/www/plugins/saisies/saisies-vues/oui_non.html @@ -0,0 +1 @@ +[

    (#ENV*{valeur}|et{#ENV{valeur}|!={#ENV{valeur_non,non}}}|?{<:item_oui:>,<:item_non:>})

    ] diff --git a/www/plugins/saisies/saisies-vues/radio.html b/www/plugins/saisies/saisies-vues/radio.html new file mode 100644 index 0000000..debb78b --- /dev/null +++ b/www/plugins/saisies/saisies-vues/radio.html @@ -0,0 +1,4 @@ +[(#REM) datas peut être une chaine qu'on sait décomposer ] +#SET{datas, #ENV{datas}|saisies_chaine2tableau} + +[

    (#GET{datas/#ENV{valeur}})

    ] diff --git a/www/plugins/saisies/saisies-vues/secteur.html b/www/plugins/saisies/saisies-vues/secteur.html new file mode 100644 index 0000000..626572c --- /dev/null +++ b/www/plugins/saisies/saisies-vues/secteur.html @@ -0,0 +1,10 @@ +[(#REM) valeur peut être une chaine qu'on sait décomposer ] +#SET{valeur, #ENV{valeur}|saisies_chaine2tableau} + + +
      + +
    • #TITRE
    • + +
    +
    diff --git a/www/plugins/saisies/saisies-vues/selecteur.html b/www/plugins/saisies/saisies-vues/selecteur.html new file mode 100644 index 0000000..419acb6 --- /dev/null +++ b/www/plugins/saisies/saisies-vues/selecteur.html @@ -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})] + + + +

    + [(#OBJET|objet_info{texte_objet}|_T)] #ID_OBJET : #INFO_TITRE{#OBJET,#ID_OBJET} +

    + + +
      + {1})}> +
    • + [(#OBJET|objet_info{texte_objet}|_T)] #ID_OBJET : #INFO_TITRE{#OBJET,#ID_OBJET} +
    • + +
    +
    + diff --git a/www/plugins/saisies/saisies-vues/selecteur_article.html b/www/plugins/saisies/saisies-vues/selecteur_article.html new file mode 100644 index 0000000..d7341bb --- /dev/null +++ b/www/plugins/saisies/saisies-vues/selecteur_article.html @@ -0,0 +1,23 @@ +[(#REM) + + ### /!\ selecteur (spip Bonux) ### + Attention, ce qui est retourne est un tableau : + _request($name) = array('article|3', 'article|9', 'article|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 + +] + + +
      + +
    • #TITRE (<:article:> #ID_ARTICLE) - #STATUT
    • + +
    +
    + + +

    #TITRE (<:article:> #ID_ARTICLE) - #STATUT

    + + 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 index 0000000..ba76acb --- /dev/null +++ b/www/plugins/saisies/saisies-vues/selecteur_article_fonctions.php @@ -0,0 +1,5 @@ + diff --git a/www/plugins/saisies/saisies-vues/selecteur_document.html b/www/plugins/saisies/saisies-vues/selecteur_document.html new file mode 100644 index 0000000..1e4ba7d --- /dev/null +++ b/www/plugins/saisies/saisies-vues/selecteur_document.html @@ -0,0 +1,3 @@ + +

    #ID_DOCUMENT - #TITRE (#TYPE_DOCUMENT [(#TAILLE|taille_en_octets)])

    + diff --git a/www/plugins/saisies/saisies-vues/selecteur_rubrique.html b/www/plugins/saisies/saisies-vues/selecteur_rubrique.html new file mode 100644 index 0000000..2bf9b24 --- /dev/null +++ b/www/plugins/saisies/saisies-vues/selecteur_rubrique.html @@ -0,0 +1,24 @@ +[(#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 + +] + + + +
      + +
    • #TITRE (<:rubrique:> #ID_RUBRIQUE) - #STATUT
    • + +
    +
    + + +

    #TITRE (<:rubrique:> #ID_RUBRIQUE) - #STATUT

    + + 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 index 0000000..c4d9724 --- /dev/null +++ b/www/plugins/saisies/saisies-vues/selecteur_rubrique_article.html @@ -0,0 +1,38 @@ +[(#REM) + + ### /!\ selecteur (spip Bonux) ### + Attention, ce qui est retourne est un tableau : + _request($name) = array('article|3', 'article|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{reponse,""} + + + +
      + +
    • #TITRE (<:rubrique:> #ID_RUBRIQUE) - #STATUT
    • + +
    +
    + + +
      + +
    • #TITRE (<:article:> #ID_ARTICLE) - #STATUT
    • + +
    +
    + + + +

    #TITRE (<:rubrique:> #ID_RUBRIQUE) - #STATUT

    + + + +

    #TITRE (<:article:> #ID_ARTICLE) - #STATUT

    + + diff --git a/www/plugins/saisies/saisies-vues/selecteur_site.html b/www/plugins/saisies/saisies-vues/selecteur_site.html new file mode 100644 index 0000000..1c5eca6 --- /dev/null +++ b/www/plugins/saisies/saisies-vues/selecteur_site.html @@ -0,0 +1,13 @@ + + +
      + +
    • #NOM SITE (#ID_SYNDIC)
    • + +
    +
    + + +

    #NOM_SITE (#ID_SYNDIC)

    + + diff --git a/www/plugins/saisies/saisies-vues/selection.html b/www/plugins/saisies/saisies-vues/selection.html new file mode 100644 index 0000000..4d5f5a6 --- /dev/null +++ b/www/plugins/saisies/saisies-vues/selection.html @@ -0,0 +1,4 @@ +[(#REM) datas peut être une chaine qu'on sait décomposer ] +#SET{datas, #ENV{datas}|saisies_chaine2tableau|saisies_aplatir_tableau} + +[

    (#GET{datas/#ENV{valeur}})

    ] diff --git a/www/plugins/saisies/saisies-vues/selection_multiple.html b/www/plugins/saisies/saisies-vues/selection_multiple.html new file mode 100644 index 0000000..a13d173 --- /dev/null +++ b/www/plugins/saisies/saisies-vues/selection_multiple.html @@ -0,0 +1,10 @@ +[(#REM) datas peut être une chaine qu'on sait décomposer ] +#SET{datas, #ENV{datas}|saisies_chaine2tableau|saisies_aplatir_tableau} +#SET{valeur, #ENV*{valeur}|saisies_valeur2tableau} + +
      + +
    • #GET{datas/#VALEUR}
    • + +
    +
    diff --git a/www/plugins/saisies/saisies.css.html b/www/plugins/saisies/saisies.css.html new file mode 100644 index 0000000..1686610 --- /dev/null +++ b/www/plugins/saisies/saisies.css.html @@ -0,0 +1,48 @@ +#CACHE{3600*100,cache-client} +[(#REM) + + +

    Test pour générer le formulaire de configuration d'une saisie

    + +

    Par defaut, sans configuration du nom du champ

    +
    +
      + #CONFIGURER_SAISIE{#ENV{saisie, input}} +
    • + +
    • +
    +
    + +

    En forçant la configuration du nom

    +
    +
      + #CONFIGURER_SAISIE{#ENV{saisie, input}, avec_nom=oui} +
    • + +
    • +
    +
    + + diff --git a/www/plugins/saisies/test/generer_saisies.html b/www/plugins/saisies/test/generer_saisies.html new file mode 100644 index 0000000..4398eb6 --- /dev/null +++ b/www/plugins/saisies/test/generer_saisies.html @@ -0,0 +1,152 @@ + + + Test de génération de saisies + [] + + + +

    Test pour générer des saisies à partir d'une description

    + +

    Génération d'une seule saisie

    + #SET{champ, + #ARRAY{ + saisie, input, + options, #ARRAY{ + nom, test, + label, Une sorte de titre, + explication, Un sorte d'explication, + obligatoire, oui + } + } + } +
    +
      + [(#GET{champ}|saisies_generer_html{#ENV**|unserialize})] +
    • + +
    • +
    +
    + +

    Génération complète du contenu (l'intérieur) d'un formulaire

    + #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" + } + }, + } + } +
    +
      + #GENERER_SAISIES{#GET{saisies}} +
    • + +
    • +
    +
    + + diff --git a/www/plugins/saisies/test/saisie.html b/www/plugins/saisies/test/saisie.html new file mode 100644 index 0000000..899d81a --- /dev/null +++ b/www/plugins/saisies/test/saisie.html @@ -0,0 +1,11 @@ +Version PHP : + + +

    Input

    +[(#SAISIE{input,titre})] + +

    Textarea

    +[(#SAISIE{textarea,texte})] + +

    Input obligatoire et label

    +[(#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 index 0000000..d72f830 --- /dev/null +++ b/www/plugins/saisies/test/voir_saisie.html @@ -0,0 +1,10 @@ +

    Input et label

    +[(#VOIR_SAISIE{input,titre,label=Un vrai titre,valeur=TRUC})] + +

    Textarea

    +[(#VOIR_SAISIE{textarea,texte,valeur=Un super long texte
    sur plusieurs ligne})] + +

    Destinataires

    +[(#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 index 0000000..3d7c50f --- /dev/null +++ b/www/plugins/saisies/test/voir_saisies.html @@ -0,0 +1,147 @@ + + + Test de génération des vues de saisies + [] + + + +

    Générer des vues de saisie

    + + #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" + } + }, + } + } + +

    Formulaire auquel on doit répondre

    +
    +
      + #GENERER_SAISIES{#GET{saisies}} +
    +
    + + + #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, + } + } + +

    Réponse à ce formulaire

    + #VOIR_SAISIES{#GET{saisies}, #GET{valeurs}} + + diff --git a/www/plugins/seminaire/agenda.json.html b/www/plugins/seminaire/agenda.json.html new file mode 100644 index 0000000..67f7889 --- /dev/null +++ b/www/plugins/seminaire/agenda.json.html @@ -0,0 +1,15 @@ +#HTTP_HEADER{Content-type:text/javascript;} +[ + [(#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)] +] \ 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 index 0000000..563c82f --- /dev/null +++ b/www/plugins/seminaire/agenda/inc-agenda.html @@ -0,0 +1,5 @@ + +[(#CALENDRIER_MINI{#ENV{date},'date',#URL_PAGE{jour},#URL_PAGE{calendrier_mini_event.json}})] +
    + +#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 index 0000000..e69de29 diff --git a/www/plugins/seminaire/base/seminaire.php b/www/plugins/seminaire/base/seminaire.php new file mode 100644 index 0000000..5e5b673 --- /dev/null +++ b/www/plugins/seminaire/base/seminaire.php @@ -0,0 +1,38 @@ + '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 index 0000000..13db95d --- /dev/null +++ b/www/plugins/seminaire/calendrier.html @@ -0,0 +1,3 @@ +[(#PLUGIN{kitcnrs}|oui) +] +[(#PLUGIN{Z}|oui) ] \ 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 index 0000000..811f4bc --- /dev/null +++ b/www/plugins/seminaire/contenu/calendrier.html @@ -0,0 +1,72 @@ + +
    + + + + \ 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 index 0000000..eb2ac0a --- /dev/null +++ b/www/plugins/seminaire/contenu/page-agenda.html @@ -0,0 +1,49 @@ +[(#REM) + + Squelette + (c) 2009 xxx + Distribue sous licence GPL + +] +[(#REM) Fil d'Ariane pour les squelettes Z ] +[(#PLUGIN{Z}|oui)

    <:accueil_site:> > <:seminaire:titre_agenda:>

    ] + +
    [(#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] + + +

    Les événements de [(#DATE_DEBUT|affdate_mois_annee)]

    + + + + + + + +
    + [

    (#_mot_evenement:TITRE|entites_html|unique)

    ] + [
    (#_mot_evenement:DESCRIPTIF|entites_html|unique)
    ] +
      +
    • #_mot_articles:TITRE

    • +
        + +
      • #MODELE{evenement_vevent} +

        + Article +

        +
      • + +
      +
    +
    +
    + + + + + + <:seminaire:creer_evenement:> + +
    + + + diff --git a/www/plugins/seminaire/contenu/page-evenement.html b/www/plugins/seminaire/contenu/page-evenement.html new file mode 100644 index 0000000..f2b9f99 --- /dev/null +++ b/www/plugins/seminaire/contenu/page-evenement.html @@ -0,0 +1,21 @@ + + + +[(#REM) Fil d'Ariane et conteneur pour les squelettes Z ] +[(#PLUGIN{Z}|oui)

    <:accueil_site:>[ > (#TITRE|couper{80})]

    + +
    ] + +
    +

    #TITRE

    +
    + + #MODELE{evenement_vevent} + +

    Pour en savoir plus sur cet événement, consultez l'article + #INFO_TITRE{article, #ID_ARTICLE} +

    + +[

    <:info_notes:>

    (#NOTES)
    ] +[(#PLUGIN{Z}|oui)
    ] + \ 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 index 0000000..de633fc --- /dev/null +++ b/www/plugins/seminaire/contenu/page-jour.html @@ -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) +

    + <:accueil_site:> > <:seminaire:aujour:> +

    +] + + + + + [

    [(#ENV{date}|affdate): ](#TOTAL_BOUCLE|singulier_ou_pluriel{agenda:info_un_evenement,agenda:info_nombre_evenements}{doublons})

    ] + +

    [(#DESCRIPTIF|attribut_html)]

    + + +
    + \ No newline at end of file diff --git a/www/plugins/seminaire/evenement.html b/www/plugins/seminaire/evenement.html new file mode 100644 index 0000000..7e59168 --- /dev/null +++ b/www/plugins/seminaire/evenement.html @@ -0,0 +1,3 @@ +[(#PLUGIN{kitcnrs}|oui) +] +[(#PLUGIN{Z}|oui) ] \ 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 index 0000000..234e225 --- /dev/null +++ b/www/plugins/seminaire/export_xml.html @@ -0,0 +1,30 @@ +#HTTP_HEADER{Content-type:text/xml; charset=#CHARSET} +#CACHE{0} + + + + + + GREGORIAN + + + -//SPIP//NONSGML v1.0//FR + + + 2.0 + + + + [(#REM)mettre ici l'inclure pour l'appel du fichier contenant les components] + + [(#INCLURE{fond=lure/evenement-xml}{evtitre=#TITRE}{id_evenement}{date_debut}{horaire}{date_fin}{id_article}{descriptif}{lieu}{name}{abstract})] + +=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})] + + + [(#INCLURE{fond=inclure/evenement-xml}{evtitre=#TITRE}{id_evenement}{date_debut}{horaire}{date_fin}{id_article}{descriptif}{lieu}{name}{abstract})] + + + + \ 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 index 0000000..a217dc9 --- /dev/null +++ b/www/plugins/seminaire/extra/page-agenda.html @@ -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] + +#ANCRE_PAGINATION +
      + +
    • [(#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}}})]
    • + +
    +[

    (#PAGINATION)

    ] +
    \ 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 index 0000000..06e549b --- /dev/null +++ b/www/plugins/seminaire/fabrique_seminaire.php @@ -0,0 +1,141 @@ + + array ( + 'version' => 5, + ), + 'paquet' => + array ( + 'nom' => 'Séminaire LATP', + 'slogan' => 'Gérer les événements d\\\'un laboratoire de recherche', + '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.', + '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' => ' + + + + ', + 'administrations' => + array ( + 'maj' => 'cextras_api_upgrade(seminaire_declarer_champs_extras(), $maj[\'create\']); +', + 'desinstallation' => '', + 'fin' => '', + ), + 'base' => + array ( + 'tables' => + array ( + 'fin' => 'if (!defined("_ECRIRE_INC_VERSION")) return; + +function seminaire_declarer_champs_extras($champs = array()){ + $champs[\'spip_evenements\'][\'name\'] = array( + \'saisie\' => \'input\',// type de saisie + \'options\' => array( + \'nom\' => \'name\', + \'label\' => _T(\'seminaire:name\'), + \'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\'][\'abstract\'] = array( + \'saisie\' => \'textarea\', + \'options\' => array( + \'nom\' => \'abstract\', // nom sql + \'label\' => _T(\'seminaire:abstract\'), + \'sql\' => "text NOT NULL DEFAULT \'\'", // declaration sql + \'rechercher\'=>true, + \'defaut\' => \'\', + \'rows\' => 4, + \'traitements\' => \'_TRAITEMENT_RACCOURCIS\', + \'class\' =>\'\', + )); + $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\', + )); + + 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 index 0000000..c8d4ba3 --- /dev/null +++ b/www/plugins/seminaire/formulaires/configurer_seminaire.html @@ -0,0 +1,20 @@ +
    + +

    <:seminaire:cfg_titre_parametrages:>

    + + [

    (#ENV*{message_ok})

    ] + [

    (#ENV*{message_erreur})

    ] + +
    + #ACTION_FORMULAIRE{#ENV{action}} +
    +
      + #SAISIE{input, prodid, label=PRODID pour le calendrier (moins de 50 caractères), obligatoire=oui} + +
    + + +

     

    +
    +
    +
    \ 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 index 0000000..adb966f --- /dev/null +++ b/www/plugins/seminaire/formulaires/editer_evenement.html @@ -0,0 +1,135 @@ +
    + [

    (#ENV**{message_ok})

    ] + [

    (#ENV**{message_erreur})

    ] + + + + #SET{id_mot_recupere,#ID_MOT} + + +
    + [(#REM) declarer les hidden qui declencheront le service du formulaire + parametre : url d'action ] + #ACTION_FORMULAIRE{#ENV{action}} + + + +
      +
    • + [ + (#ENV**{erreurs}|table_valeur{titre}) + ] +
    • +
    • + [ + (#ENV**{erreurs}|table_valeur{id_parent}) + ] +
    • + +
    • + [ + (#ENV**{erreurs}|table_valeur{id_mot}) + ] +

      <:seminaire:type_evenement:>

      + +
    • + +
    • <:agenda:evenement_date:> +
        +
      • + [ + (#ENV**{erreurs}|table_valeur{horaire}) + ] +
      • +
      • + [ + (#ENV**{erreurs}|table_valeur{date_debut})][ + (#ENV**{erreurs}|table_valeur{date_fin}) + ] + + + + + + +
      • +
      +
    • +
    • + [ + (#ENV{repetitions}|non)<:agenda:ajouter_repetition:> +
      ][ + (#ENV**{erreurs}|table_valeur{repetitions}) + ]
      + [ + (#ENV{repetitions}|non)
      ] +
    • + [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ] + +
    • + [ + (#ENV**{erreurs}|table_valeur{descriptif}) + ] +
    • + [ + (#ENV{affiche_inscription,oui}|=={oui}|oui) +
    • +
      + [(#ENV**{erreurs}|table_valeur{inscription})] + + +
      + [ + (#ENV**{erreurs}|table_valeur{places}) + ] +
      +
    • ] +
    • + [ + (#ENV**{erreurs}|table_valeur{lieu}) + ] +
    • +
    • + [ + (#ENV**{erreurs}|table_valeur{adresse}) + ] +
    • +
    +

    +
    + #INCLURE{fond=formulaires/dateur/inc-dateur} + +
    + \ 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 index 0000000..81c7131 --- /dev/null +++ b/www/plugins/seminaire/formulaires/editer_evenement.php @@ -0,0 +1,150 @@ +'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 index 0000000..580388a --- /dev/null +++ b/www/plugins/seminaire/head/page-agenda.html @@ -0,0 +1,2 @@ +<:seminaire:titre_agenda:> - [(#NOM_SITE_SPIP|textebrut)] + diff --git a/www/plugins/seminaire/images/calendrier.png b/www/plugins/seminaire/images/calendrier.png new file mode 100644 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 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 index 0000000..c1e9bd8 --- /dev/null +++ b/www/plugins/seminaire/inc/inc-p-calendrier.html @@ -0,0 +1,10 @@ +[(#REM) Contenu de page calendrier +-- + Paramètres: + - type_page=calendrier + - env : toutes les variables d'environnement +-- + Utilisé par: squelette principal calendrier.html, via structure.html de la charte active (kitcnrs) +] + + diff --git a/www/plugins/seminaire/inc/inc-p-evenement.html b/www/plugins/seminaire/inc/inc-p-evenement.html new file mode 100755 index 0000000..81bb3b4 --- /dev/null +++ b/www/plugins/seminaire/inc/inc-p-evenement.html @@ -0,0 +1,10 @@ +[(#REM) Contenu de page evenement +-- + Paramètres: + - type_page=evenement + - env : toutes les variables d'environnement +-- + Utilisé par: squelette principal evenement.html, via structure.html de la charte active (kitcnrs) +] + + diff --git a/www/plugins/seminaire/inc/inc-p-jour.html b/www/plugins/seminaire/inc/inc-p-jour.html new file mode 100755 index 0000000..44e7ef4 --- /dev/null +++ b/www/plugins/seminaire/inc/inc-p-jour.html @@ -0,0 +1,10 @@ +[(#REM) Contenu de page jour +-- + Paramètres: + - type_page=jour + - env : toutes les variables d'environnement +-- + Utilisé par: squelette principal jour.html, via structure.html de la charte active (kitcnrs) +] + + diff --git a/www/plugins/seminaire/inclure/agenda_article.html b/www/plugins/seminaire/inclure/agenda_article.html new file mode 100644 index 0000000..2cce941 --- /dev/null +++ b/www/plugins/seminaire/inclure/agenda_article.html @@ -0,0 +1,134 @@ +[(#REM)pour vérifier qu'un événment est bein associé à l'article] + +
    + +[(#REM) Se placer au debut du mois en cours par defaut] +#SET{date_debut,#VAL{Y-m-}|date|concat{01}} + +[(#REM) Si un evenement passe, on commence par le jour de cet evenement] +#SET{date_debut,#DATE_DEBUT|affdate{Y-m-d}} + + #SET{date_debut,#ENV{date}|affdate{Y-m-d}} + #SET{date_debut,#ENV{date_debut,#GET{date_debut}}} + + +#SET{self,#SELF|parametre_url{date_debut|id_evenement|debut_agenda,''}}#SET{yena,''} +

    <:agenda:agenda:>

    + + + + + +

    [(#DESCRIPTIF|attribut_html)]

    +
      + +
    • + [(#MODELE{evenement_vevent}|trim|sinon{'?'})] +
    • +
      + + [

      (#PAGINATION{page})

      ] +
    +
    + + + +
    + + [(#REM)bouton de création d'événement] + <:seminaire:creer_evenement:> + + + + iCal[(#REM)bouton d'abonnement au calendrier] + +
    + + + + + + + + +
    + \ 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 index 0000000..13dc1e3 --- /dev/null +++ b/www/plugins/seminaire/inclure/evenement-ical.html @@ -0,0 +1,21 @@ +BEGIN:VEVENT +SUMMARY:[(#EVTITRE|textebrut|filtrer_ical)] +UID:[(#DATE_CREATION|date_ical)]-a#ID_ARTICLE-e#ID_EVENEMENT@[(#URL_SITE_SPIP||replace{http://})][ +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)] +[ORGANIZER;CN=(#NOM|supprimer_tags|textebrut|filtrer_ical)][:mailto:#EMAIL][ +ATTENDEE:(#ATTENDEE|supprimer_tags|textebrut|filtrer_ical)][ - (#ORIGIN|supprimer_tags|textebrut|filtrer_ical)][ +DESCRIPTION:(#DESCRIPTIF|supprimer_tags|textebrut|filtrer_ical)][ +CATEGORIES:(#TITRE|textebrut|filtrer_ical)][ +URL:(#URL_ARTICLE|parametre_url{id_evenement,#ID_EVENEMENT}|url_absolue|filtrer_ical)][ +SEQUENCE:(#ID_VERSION|moins{1}) +] +[GEO:(#LAT);#LON] +STATUS:CONFIRMED +END:VEVENT diff --git a/www/plugins/seminaire/inclure/evenement-xml.html b/www/plugins/seminaire/inclure/evenement-xml.html new file mode 100644 index 0000000..6302dfe --- /dev/null +++ b/www/plugins/seminaire/inclure/evenement-xml.html @@ -0,0 +1,26 @@ + + + + + [(#DATE_DEBUT|date_ical)] + + + [(#EVTITRE|textebrut)] + + + [(#LIEU|PtoBR|textebrut)] + + CONFIRMED + + + [(#ABSTRACT|textebrut)] + + + + + [(#TITRE|textebrut)] + + + + + diff --git a/www/plugins/seminaire/javascript/rechargement.html b/www/plugins/seminaire/javascript/rechargement.html new file mode 100644 index 0000000..5a1b467 --- /dev/null +++ b/www/plugins/seminaire/javascript/rechargement.html @@ -0,0 +1,5 @@ + \ 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 index 0000000..ac0fa89 --- /dev/null +++ b/www/plugins/seminaire/javascript/toggle.js @@ -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 index 0000000..cc95b9e --- /dev/null +++ b/www/plugins/seminaire/jour.html @@ -0,0 +1,3 @@ +[(#PLUGIN{kitcnrs}|oui) +] +[(#PLUGIN{Z}|oui) ] \ 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 index 0000000..915b115 --- /dev/null +++ b/www/plugins/seminaire/lang/paquet-seminaire_fr.php @@ -0,0 +1,14 @@ + '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 index 0000000..7e78573 --- /dev/null +++ b/www/plugins/seminaire/lang/seminaire_fr.php @@ -0,0 +1,70 @@ + 'Il faut IMPERATIVEMENT mettre un mot clé sur l’événement', + 'abstract' => 'Résumé', + 'apreciser' => 'A préciser', + 'aujour' => 'Les événements de la journée', + 'a_venir' => 'Les prochains séminaires', + 'attendee' => 'Nom de l’intervenant', + 'attention_type' => 'Le choix du type d’événement détermine le tri qui lui sera appliqué.', + 'abonnement' => 'S’abonner au calendrier', + + // C + 'cfg_exemple' => 'Exemple', + 'cfg_exemple_explication' => 'Explication de cet exemple', + 'cfg_titre_parametrages' => 'Paramétrages', + 'choix_mot' => 'Type d’événement', + 'creer_evenement' => 'Ajouter un événement', + + // E + 'erreur_install_groupe_categories' => 'erreur à la création du groupe', + 'evenement_titre' => 'Titre de l’exposé', + 'evenements_depuis_debut' => 'Archives', + 'evenements_du_jour' => 'Les événements du jour', + 'evenements_seul' => 'Détails d’un événement', + 'exemple' => 'Par exemple : CMI - Salle R164', + + // L + 'lien_tout_replier' => 'Sans résumé', + 'lien_tout_deplier' => 'Avec résumé', + 'lieu' => 'Lieu', + + // M + 'mots_categorie_kitcnrs' => 'Les mots clés à affecter aux articles pour que les événements soient encore mieux triés', + 'mots_cles_categories' => 'Les différentes catégories de séminaires, groupes de travail et événements exceptionnels', + 'mots_cles_techniques_kitcnrs'=> 'Les mots clés pour repérer plus finement les différents types d’événements', + + // N + 'nom_de_l_intervenant' => 'Intervenant à préciser', + 'notes' => 'Notes de dernières minutes', + + // O + 'origin' => 'Institut d’origine de l’intervenant', + 'oubli_titre' => 'Ne pas oublier de mettre le titre de l’événement', + + + // P + 'plus' => 'En savoir plus', + 'precisions_name' => 'le nom sera affiché dans le calendrier et dans le mini calendrier', + 'precisions_origin' => 'N’apparaît que dans les détails de l’événement', + 'precisions_abstract' => 'pour mettre du code LATEX, ajoutez les balises en début et fin de texte (ex : La fraction $\frac{1}{2}$)', + 'precisions_notes' => 'N’apparaît que dans les détails de l’événement', + + // S + 'seminaire_titre' => 'Séminaire', + + // T + 'titre_agenda' => 'Agenda Scientifique', + 'titre_evenement' => 'Titre à préciser', + 'titre_page_configurer_seminaire' => 'seminaire', + 'type' => 'Type d’évé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 index 0000000..966f2dd --- /dev/null +++ b/www/plugins/seminaire/modeles/connexion.html @@ -0,0 +1,8 @@ +
    + [(#SESSION{id_auteur}|?{' '}) +
    + <:icone_deconnecter:>] + [(#SESSION{id_auteur}|?{'',' '})
    + ] + [(#AUTORISER{ecrire})

    <:espace_prive:>

    ] +
    \ 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 index 0000000..142c186 --- /dev/null +++ b/www/plugins/seminaire/modeles/evenement_vevent.html @@ -0,0 +1,26 @@ + +
    +
    +

    + + [(#DATE_DEBUT|agenda_affdate_debut_fin{#DATE_FIN,#HORAIRE,'hcal'}) - ][(#REM)date de l'evenement] + #ATTENDEE[ - (#ORIGIN)] +

    + + #TITRE [(#REM)titre de l'evenement] + +
    +
    + + [(#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|?{' ',''})

    <:seminaire:inscriptions:> : #NB_INSCRITS[/(#PLACES) ]<:agenda:inscrits:>

    ] + [

    <:seminaire:abstract:> : (#DESCRIPTIF|propre|PtoBR)

    ] + [

    <:seminaire:lieu:> : (#LIEU|textebrut)[ - (#ADRESSE|PtoBR)]

    ] + [

    <:seminaire:notes:> : (#CHAMP_SQL{notes}|typo)

    ] +
    +
    + #FORMULAIRE_PARTICIPER_EVENEMENT{#ID_EVENEMENT} +
    +
    +
    + \ 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 index 0000000..3131131 --- /dev/null +++ b/www/plugins/seminaire/navigation/dist.html @@ -0,0 +1,8 @@ +[(#REM) Menu de navigation par rubriques ] + + +#FORMULAIRE_RECHERCHE + +#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 index 0000000..2188c4b --- /dev/null +++ b/www/plugins/seminaire/navigation/page-agenda.html @@ -0,0 +1,5 @@ +[(#REM) Menu de navigation par rubriques ] + + +#FORMULAIRE_RECHERCHE + diff --git a/www/plugins/seminaire/paquet.xml b/www/plugins/seminaire/paquet.xml new file mode 100644 index 0000000..85719cb --- /dev/null +++ b/www/plugins/seminaire/paquet.xml @@ -0,0 +1,23 @@ + + Séminaire + + Amaury Adon + + GNU/GPL + + + + + + + + \ 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 index 0000000..8045df1 --- /dev/null +++ b/www/plugins/seminaire/prive/squelettes/contenu/configurer_seminaire.html @@ -0,0 +1,7 @@ +[(#AUTORISER{configurer,_seminaire}|sinon_interdire_acces)] + +

    <:seminaire:titre_page_configurer_seminaire:>

    + +
    + #FORMULAIRE_CONFIGURER_SEMINAIRE +
    \ 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 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 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 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 index 0000000..f06c986 --- /dev/null +++ b/www/plugins/seminaire/seminaire_administrations.php @@ -0,0 +1,101 @@ +'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 index 0000000..a719927 --- /dev/null +++ b/www/plugins/seminaire/seminaire_ical.html @@ -0,0 +1,11 @@ +#CACHE{3600}BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//SPIP/Plugin #PLUGIN{AGENDA,nom}//NONSGML v1.0//FR +X-WR-TIMEZONE:Europe/Paris +CALSCALE:GREGORIAN +X-WR-CALNAME;VALUE=TEXT:[(#INFO_TITRE{article,#ENV{id_article}}|supprimer_numero|filtrer_ical)] -- [(#NOM_SITE_SPIP|filtrer_ical)] +X-WR-RELCALID:[(#URL_ARTICLE|url_absolue|filtrer_ical)] +[(#INCLURE{fond=inclure/evenement-ical}{evtitre=#TITRE}{id_evenement}{date_debut}{horaire}{date_fin}{id_article}{descriptif}{lieu}{date_creation}{maj}{attendee}{descriptif})] +=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})] +[(#INCLURE{fond=inclure/evenement-ical}{evtitre=#TITRE}{id_evenement}{date_debut}{horaire}{date_fin}{id_article}{descriptif}{lieu}{date_creation}{maj}{attendee}{descriptif})] +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 index 0000000..386f54e --- /dev/null +++ b/www/plugins/seminaire/seminaire_pipelines.php @@ -0,0 +1,17 @@ +\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 index 0000000..6e43cae --- /dev/null +++ b/www/plugins/seminaire/styles/calendrier-seminaire.css @@ -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 index 0000000..1d569ad --- /dev/null +++ b/www/plugins/seminaire/svn.revision @@ -0,0 +1,10 @@ + + +Origine: file:///home/svn/repository/spip-zone/_plugins_/seminaire/branches/v2.1 +Revision: 85477 +Dernier commit: 2014-10-23 11:15:17 +0200 + +file:///home/svn/repository/spip-zone/_plugins_/seminaire/branches/v2.1 +85477 +2014-10-23 11:15:17 +0200 + \ No newline at end of file