Subversion Repositories Applications.referentiel

Compare Revisions

Ignore whitespace Rev 286 → Rev 287

/tags/v1.1/trunk/AUTEURS.txt
New file
0,0 → 1,0
Jean-Pascal MILCENT <jpm@tela-botanica.org>.
Property changes:
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: v1.1/trunk/scripts/modules/versionnage/squelettes/bdnt_ligne.tpl.tsv
===================================================================
--- v1.1/trunk/scripts/modules/versionnage/squelettes/bdnt_ligne.tpl.tsv (revision 0)
+++ v1.1/trunk/scripts/modules/versionnage/squelettes/bdnt_ligne.tpl.tsv (revision 287)
@@ -0,0 +1,2 @@
+<?=implode("\t", $nom_infos)?>
+
Index: v1.1/trunk/scripts/modules/versionnage/squelettes/bdnt_partiel_ligne.tpl.tsv
===================================================================
--- v1.1/trunk/scripts/modules/versionnage/squelettes/bdnt_partiel_ligne.tpl.tsv (revision 0)
+++ v1.1/trunk/scripts/modules/versionnage/squelettes/bdnt_partiel_ligne.tpl.tsv (revision 287)
@@ -0,0 +1,6 @@
+<?php if (!isset($nom_infos['exclure_taxref']) || $nom_infos['exclure_taxref'] != '1') :
+ foreach ($champs_partiel as $champ) :
+ echo $nom_infos[$champ];
+ echo ($dernier_champ == $champ) ? "\n" : "\t";
+ endforeach;
+endif; ?>
\ No newline at end of file
Index: v1.1/trunk/scripts/modules/versionnage/squelettes/meta.tpl.tsv
===================================================================
--- v1.1/trunk/scripts/modules/versionnage/squelettes/meta.tpl.tsv (revision 0)
+++ v1.1/trunk/scripts/modules/versionnage/squelettes/meta.tpl.tsv (revision 287)
@@ -0,0 +1,45 @@
+titre <?=$titre?>
+
+code <?=$acronyme?>
+
+domaine_taxo <?=implode(', ', $dom_tax)?>
+
+domaine_geo <?=implode(', ', $dom_geo)?>
+
+domaine_nom <?=implode(', ', $dom_code)?>
+
+classification <?=$classification?>
+
+version <?=$version?>
+
+auteur <?=$auteur_principal?>
+
+coordinateur <?=$coordinateur?>
+
+contributeur <?=$contributeur?>
+
+date_production <?=$date_prod?>
+
+source <?=$source?>
+
+contact <?=$contact?>
+
+editeur <?=$editeur?>
+
+droit <?=$copyright?>
+
+licence <?=$licence?>
+
+referencement <?=$referencement?>
+
+stat_combinaison <?=$stats['combinaison']?>
+
+stat_taxon <?=$stats['taxon']?>
+
+stat_modification <?=$stats['modification']?>
+
+signature <?=$signature?>
+
+errata <?=$errata?>
+
+notes <?=$notes?>
\ No newline at end of file
Index: v1.1/trunk/scripts/modules/versionnage/squelettes/diff.tpl.tsv
===================================================================
--- v1.1/trunk/scripts/modules/versionnage/squelettes/diff.tpl.tsv (revision 0)
+++ v1.1/trunk/scripts/modules/versionnage/squelettes/diff.tpl.tsv (revision 287)
@@ -0,0 +1,6 @@
+<?=implode("\t", $champs)?>
+
+<?php foreach ($diff as $nom_infos) : ?>
+<?=implode("\t", $nom_infos)?>
+
+<?php endforeach; ?>
\ No newline at end of file
Index: v1.1/trunk/scripts/modules/versionnage/squelettes/diff_partiel.tpl.tsv
===================================================================
--- v1.1/trunk/scripts/modules/versionnage/squelettes/diff_partiel.tpl.tsv (revision 0)
+++ v1.1/trunk/scripts/modules/versionnage/squelettes/diff_partiel.tpl.tsv (revision 287)
@@ -0,0 +1,8 @@
+<?=implode("\t", $champs_partiel_diff)?>
+
+<?php foreach ($diff_partiel as $id => $nom_infos) :
+ foreach ($champs_partiel_diff as $champ) :
+ echo $nom_infos[$champ];
+ echo ($dernier_champ == $champ) ? "\n" : "\t";
+ endforeach;
+endforeach; ?>
\ No newline at end of file
Index: v1.1/trunk/scripts/modules/versionnage/squelettes/bdnt_entete.tpl.tsv
===================================================================
--- v1.1/trunk/scripts/modules/versionnage/squelettes/bdnt_entete.tpl.tsv (revision 0)
+++ v1.1/trunk/scripts/modules/versionnage/squelettes/bdnt_entete.tpl.tsv (revision 287)
@@ -0,0 +1,2 @@
+<?=implode("\t", $champs)?>
+
Index: v1.1/trunk/scripts/modules/versionnage/squelettes/bdnt_partiel_entete.tpl.tsv
===================================================================
--- v1.1/trunk/scripts/modules/versionnage/squelettes/bdnt_partiel_entete.tpl.tsv (revision 0)
+++ v1.1/trunk/scripts/modules/versionnage/squelettes/bdnt_partiel_entete.tpl.tsv (revision 287)
@@ -0,0 +1,2 @@
+<?=implode("\t", $champs_partiel)?>
+
Index: v1.1/trunk/scripts/modules/versionnage/Versionnage.php
===================================================================
--- v1.1/trunk/scripts/modules/versionnage/Versionnage.php (revision 0)
+++ v1.1/trunk/scripts/modules/versionnage/Versionnage.php (revision 287)
@@ -0,0 +1,765 @@
+<?php
+// Encodage : UTF-8
+// +-------------------------------------------------------------------------------------------------------------------+
+/**
+* Versionnage de référentiels de nomenclature et taxonomie
+*
+* Description : classe permettant de versionner les référentiels selon le manuel technique
+* Utilisation : php script.php versionnage -p bdnff -a tout
+*
+//Auteur original :
+* @author Jean-Pascal MILCENT <jpm@tela-botanica.org>
+* @copyright Tela-Botanica 1999-2010
+* @link http://www.tela-botanica.org/wikini/RTaxMethodo/wakka.php?wiki=MaNuel
+* @licence GPL v3 & CeCILL v2
+* @version $Id$
+*/
+// +-------------------------------------------------------------------------------------------------------------------+
+// TODO : lors de la génération de la version 2 de la BDTFX tester les diff! Il se peut que la mémoire soit dépassée.
+class Versionnage extends ScriptCommande {
+
+ const SCRIPT_NOM = 'versionnage';
+ const MANUEL_VERSION = '4.3';
+
+ private $projet = null;
+ private $traitement = null;
+ private $meta = null;
+ private $version_courante = null;
+ private $messages = null;
+ private $manuel = null;
+ private $manuel_nom = null;
+ private $manuel_chemin = null;
+ private $zip_chemin_dossier = null;
+ private $zip_chemin_fichier = null;
+ private $zip_chemin_dossier_partiel = null;
+ private $zip_chemin_fichier_partiel = null;
+
+ private $noms = null;
+ private $noms_precedents = null;
+ private $noms_supprimes = null;
+ private $noms_stat = null;
+ private $noms_stat_partiel = null;
+ private $champs_ordre = null;
+ private $champs_nom = null;
+ private $champs_nom_partiel = null;
+ private $champs_courants = null;
+ private $champs_diff = null;
+ private $diff_champs_nom = null;
+ private $diff_modif_types = null;
+ private $signature_md5 = null;
+ private $signature_md5_partiel = null;
+ private $derniere_meta = null;
+ private $rangs_bdnt_taxref = null;
+ private $statuts_bdnt_taxref = null;
+
+ private $resultatDao = null;
+ private $traitementDao = null;
+ private $metaDao = null;
+ private $tableStructureDao = null;
+ private $referentielDao = null;
+
+ public function executer() {
+ // Récupération du dernier traitement demandé
+ $this->traitementDao = new TraitementDao();
+ $this->traitement = $this->traitementDao->getDernierTraitement('tout', self::SCRIPT_NOM);
+ if (isset($this->traitement)) {
+ $this->projet = $this->traitement['referentiel_code']; // Récupération du nom de projet
+ Debug::printr($this->traitement);
+ // Écriture de la date de début du traitement
+ Debug::printr('Debute:'.$this->traitementDao->debuterTraitement($this->traitement['id_traitement']));
+
+ // Nettoyage des traitements obsolètes
+ $traitements_obsoletes = $this->traitementDao->getTraitementsObsoletes($this->projet, self::SCRIPT_NOM);
+ if (isset($traitements_obsoletes)) {
+ Debug::printr('Supp. obsoletes:'.$this->traitementDao->supprimer($traitements_obsoletes));
+ }
+
+ // Lancement du test demandé
+ $cmd = $this->getParam('a');
+ switch ($cmd) {
+ case 'tout' :
+ $this->initialiserScript();
+ Debug::printr('Départ lancement versionnage:');
+ $this->lancerVersionnage();
+ break;
+ default :
+ $this->traiterErreur('Erreur : la commande "%s" n\'existe pas!', array($cmd));
+ }
+ // Écriture de la date de fin du traitement
+ Debug::printr('Termine:'.$this->traitementDao->terminerTraitement($this->traitement['id_traitement']));
+ }
+ }
+
+ private function initialiserScript() {
+ $this->metaDao = new MetaDao();
+ $this->resultatDao = new ResultatDao();
+ $this->referentielDao = new ReferentielDao();
+
+ $this->manuel_nom = 'mtpr_v'.str_replace('.', '_', self::MANUEL_VERSION).'.pdf';
+ $this->manuel_chemin = Config::get('chemin_appli').DS.'..'.DS.'configurations'.DS;
+
+ $manuel_config_nom = 'referentiel_v'.self::MANUEL_VERSION.'.ini';
+ $this->manuel = parse_ini_file($this->manuel_chemin.$manuel_config_nom);
+ }
+
+ public function lancerVersionnage() {
+ $this->chargerTraitementParametre();
+ $this->initialiserNomVersionCOurante();
+ $this->initialiserCheminsZip();
+ $this->creerDossiersZip();
+ $this->archiver();
+ $this->chargerNomsATraiter();
+ $this->analyserNomsATraiter();
+ $this->creerFichiers();
+ $this->nettoyerFichiers();
+ $this->traiterMessages();
+ }
+
+ private function chargerTraitementParametre() {
+ $this->meta = unserialize($this->traitement['script_parametres']);
+ }
+
+ private function archiver() {
+ $ok = $this->referentielDao->archiver($this->projet, $this->meta['version']);
+ if ($ok) {
+ $m = "L'archivage de la version '{$this->meta['version']}' du référentiel '{$this->projet}' a réussi";
+ $this->ajouterMessage($m);
+ } else {
+ $m = "L'archivage de la version '{$this->meta['version']}' du référentiel '{$this->projet}' a échoué";
+ $this->ajouterMessage($m);
+ }
+ }
+
+ private function initialiserNomVersionCOurante() {
+ $this->version_courante = strtolower($this->projet).'_v'.str_replace('.', '_', $this->meta['version']);
+ Debug::printr("Nom archive courante :".$this->version_courante);
+ }
+
+ private function initialiserCheminsZip() {
+ $this->zip_chemin_dossier = Config::get('chemin_referentiel_zip').$this->version_courante.DIRECTORY_SEPARATOR;
+ $this->zip_chemin_fichier = Config::get('chemin_referentiel_zip').$this->version_courante.'.zip';
+ $this->zip_chemin_dossier_partiel = Config::get('chemin_referentiel_zip').$this->version_courante.$this->manuel['suffixe_partiel'].DIRECTORY_SEPARATOR;
+ $this->zip_chemin_fichier_partiel = Config::get('chemin_referentiel_zip').$this->version_courante.$this->manuel['suffixe_partiel'].'.zip';
+ }
+
+ private function creerDossiersZip() {
+ $recursivite = true;
+ if (mkdir($this->zip_chemin_dossier, 0777, $recursivite) === false) {
+ $this->ajouterMessage("La création du dossier '$this->zip_chemin_dossier' devant contenir les fichiers a échouée.");
+ }
+ if (mkdir($this->zip_chemin_dossier_partiel, 0777, $recursivite) === false) {
+ $this->ajouterMessage("La création du dossier '$this->zip_chemin_dossier_partiel' devant contenir les fichiers partiels a échouée.");
+ }
+ }
+
+ private function chargerNomsATraiter() {
+ $this->noms = $this->referentielDao->getTout($this->version_courante);
+ }
+
+ private function analyserNomsATraiter() {
+ $this->noms_stat['combinaison'] = $this->getNombreCombinaison();
+ $this->noms_stat['taxon'] = $this->getNombreTaxon();
+ Debug::printr("Stats :".print_r($this->noms_stat, true));
+ $this->noms_stat_partiel = $this->getStatsPartiel();
+ Debug::printr("Stats partiel:".print_r($this->noms_stat_partiel, true));
+ }
+
+ private function getNombreCombinaison() {
+ return count($this->noms);
+ }
+
+ private function getNombreTaxon() {
+ $nbre = 0;
+ foreach ($this->noms as $nom) {
+ if ($nom['num_nom_retenu'] == $nom['num_nom']) {
+ $nbre++;
+ }
+ }
+ return $nbre;
+ }
+
+ private function getStatsPartiel() {
+ $stat['combinaison'] = 0;
+ $stat['taxon'] = 0;
+ foreach ($this->noms as $nom) {
+ if ($nom['exclure_taxref'] != '1') {
+ $stat['combinaison']++;
+ if ($nom['num_nom_retenu'] == $nom['num_nom']) {
+ $stat['taxon']++;
+ }
+ }
+ }
+ return $stat;
+ }
+
+ private function creerFichiers() {
+ // Respecter l'ordre de traitement : BDNT puis DIFF puis META
+ $donnees =& $this->creerFichierBdnt();
+ $this->creerFichierBdntPartiel($donnees);
+ $donnees = null;
+ $donnees =& $this->creerFichierDiff();
+ $this->creerFichierDiffPartiel($donnees);
+ $donnees = null;
+ $donnees =& $this->creerFichierMeta();
+ $this->creerFichierMetaPartiel($donnees);
+ $donnees = null;
+ $this->nettoyerMemoire();
+ $this->copierManuel();
+ $this->creerFichiersZip();
+ }
+
+ private function creerFichierBdnt() {
+ reset($this->noms);
+ Debug::printr("Element courrant du tableau des noms : ".count($this->noms).'-'.print_r(current($this->noms),true));
+
+ $this->determinerOrdreDesChamps();
+ $this->definirNomDesChamps();
+ $this->definirNomDesChampsDiff();
+
+ $donnees = array();
+ $donnees['champs'] = $this->champs_nom;
+
+ $fichier_nom = $this->getBaseNomFichier().$this->manuel['suffixe_bdnt'].$this->manuel['ext_fichier'];
+ $fichier_chemin = $this->zip_chemin_dossier.$fichier_nom;
+ $bdnt_tsv_entete = $this->getVue('versionnage/squelettes/bdnt_entete', array('champs' => $donnees['champs']), '.tpl.tsv');
+ $this->ecrireFichier($fichier_chemin, $bdnt_tsv_entete);
+
+ foreach ($this->noms as $id => &$nom) {
+ $infos = array();
+ foreach ($this->champs_ordre as $champ => $ordre) {
+ if (array_key_exists($champ, $nom)) {
+ $infos[$champ] = trim($nom[$champ]);
+ } else {
+ $e = "Le champ '$champ' n'a pas été trouvé dans les données du nom : $id.";
+ $this->ajouterMessage($e);
+ }
+ }
+ $infos = $this->remplacerTabulation($infos);
+ $infos = $this->remplacerSautsDeLigne($infos);
+ $this->noms[$id] = $infos;
+
+ $bdnt_tsv_ligne = $this->getVue('versionnage/squelettes/bdnt_ligne', array('nom_infos' => $infos), '.tpl.tsv');
+ $this->ajouterAuFichier($fichier_chemin, $bdnt_tsv_ligne);
+ }
+ $this->ecrireComplementFichierBdnt($fichier_chemin);
+ return $donnees;
+ }
+
+ private function determinerOrdreDesChamps() {
+ $champs_ordre = explode(',', $this->manuel['champs']);
+ $champs_ordre = array_flip($champs_ordre);
+ $nom_courant = current($this->noms);
+ $champs_ordre = $this->attribuerOrdreChampsSupplémentaires($champs_ordre, $nom_courant);
+ asort($champs_ordre);
+ $this->champs_ordre = $champs_ordre;
+ Debug::printr("Ordre des champs : ".print_r($this->champs_ordre,true));
+ }
+
+ private function attribuerOrdreChampsSupplémentaires($champs_ordre, $nom) {
+ foreach ($nom as $champ => $info) {
+ if (!isset($champs_ordre[$champ])) {
+ $champs_ordre[$champ] = count($champs_ordre);
+ }
+ }
+ return $champs_ordre;
+ }
+
+ private function definirNomDesChamps() {
+ $this->champs_nom = array_flip($this->champs_ordre);
+ }
+
+ private function definirNomDesChampsDiff() {
+ $this->champs_diff = explode(',', $this->manuel['champs_diff']);
+ $this->diff_champs_nom = array_merge($this->champs_nom, $this->champs_diff);
+ }
+
+ private function ajouterMessage($message) {
+ $titre = self::SCRIPT_NOM.' #'.$this->traitement['id_traitement'];
+ $this->messages[] = array('message' => $message, 'resultat' => true);
+ }
+
+ private function remplacerTabulation($doc) {
+ if (is_string($doc)) {
+ $doc = str_replace("\t", ' ', $doc);
+ } else if (is_array($doc) && count($doc) > 0) {
+ foreach ($doc as $cle => $valeur) {
+ $doc[$cle] = $this->remplacerTabulation($valeur);
+ }
+ }
+ return $doc;
+ }
+
+ private function remplacerSautsDeLigne($doc) {
+ if (is_string($doc)) {
+ $a_remplacer = array("\r", "\n");
+ $doc = str_replace($a_remplacer, ' ', $doc);
+ } else if (is_array($doc) && count($doc) > 0) {
+ foreach ($doc as $cle => $valeur) {
+ $doc[$cle] = $this->remplacerSautsDeLigne($valeur);
+ }
+ }
+ return $doc;
+ }
+
+ private function ecrireComplementFichierBdnt($fichier_chemin) {
+ if (file_exists($fichier_chemin)) {
+ $this->ajouterMessage("Écriture du fichier bdnt réussie.");
+ $this->signature_md5 = md5_file($fichier_chemin);
+ $this->ajouterMessage("Signature MD5 du fichier bdnt :".$this->signature_md5);
+ $this->ajouterMessage("Nombre de combinaisons traitées : ".$this->noms_stat['combinaison']);
+ }
+ }
+
+ private function getBaseNomFichier() {
+ return strtolower($this->meta['acronyme'].'_v'.str_replace('.', '_', $this->meta['version']));
+ }
+
+ private function creerFichierBdntPartiel(&$donnees) {
+ $this->definirChampsPartiel();
+ $this->creerCorrespondanceRangBdntTaxref();
+ $this->creerCorrespondanceStatutBdntTaxref();
+ Debug::printr(current($donnees['noms']));
+ $donnees['champs_partiel'] = $this->champs_nom_partiel;
+ $donnees['dernier_champ'] = end($this->champs_nom_partiel);
+ $this->ecrireFichierBdntPartielle($donnees);
+ }
+
+ private function definirChampsPartiel() {
+ $this->champs_nom_partiel = explode(',', $this->manuel['champs_partiel']);
+ $champs_taxref = explode(',', $this->manuel['champs_taxref']);
+ $this->champs_nom_partiel = array_merge($this->champs_nom_partiel, $champs_taxref);
+ Debug::printr("Champs partiels : ".print_r($this->champs_nom_partiel,true));
+ }
+
+ private function creerCorrespondanceRangBdntTaxref() {
+ $rangs = explode(',', $this->manuel['rangs_bdnt_taxref']);
+ foreach ($rangs as $rang) {
+ list($id_bdnt, $code_taxref) = explode(':', trim($rang));
+ $this->rangs_bdnt_taxref[$id_bdnt] = $code_taxref;
+ }
+ }
+
+ private function creerCorrespondanceStatutBdntTaxref() {
+ $statuts = explode(',', $this->manuel['statuts_bdnt_taxref']);
+ foreach ($statuts as $statut) {
+ list($code_taxref, $champ_et_code_bdnt) = explode('=', trim($statut));
+ list($nom_champ_bdnt, $code_bdnt) = explode(':', trim($champ_et_code_bdnt));
+ $this->statuts_bdnt_taxref[$nom_champ_bdnt][$code_bdnt] = $code_taxref;
+ }
+ Debug::printr($this->statuts_bdnt_taxref);
+ }
+
+ private function ecrireFichierBdntPartielle(&$donnees) {
+ $fichier_nom = $this->getBaseNomFichier().$this->manuel['suffixe_bdnt'].$this->manuel['suffixe_partiel'].$this->manuel['ext_fichier'];
+ $fichier_chemin = $this->zip_chemin_dossier_partiel.$fichier_nom;
+ $bdnt_tsv_entete = $this->getVue('versionnage/squelettes/bdnt_partiel_entete', array('champs_partiel' => $donnees['champs_partiel']), '.tpl.tsv');
+ $this->ecrireFichier($fichier_chemin, $bdnt_tsv_entete);
+
+ foreach ($this->noms as $id => &$nom) {
+ $donnees['nom_infos'] = $nom;
+
+ // Ajout de champs spécifique à Taxref généré depuis les données BDNT
+ $donnees['nom_infos']['taxref_rang'] = $this->attribuerRangTaxref($nom['rang']);
+ $donnees['nom_infos']['taxref_statut'] = $this->attribuerStatutTaxref($nom);
+
+ $bdnt_tsv_ligne = $this->getVue('versionnage/squelettes/bdnt_partiel_ligne', $donnees, '.tpl.tsv');
+ if ($bdnt_tsv_ligne != '') {
+ $this->ajouterAuFichier($fichier_chemin, $bdnt_tsv_ligne);
+ }
+ }
+
+ if (file_exists($fichier_chemin)) {
+ $this->ajouterMessage("Écriture du fichier de la bdnt partielle réussie.");
+ $this->signature_md5_partiel = md5_file($fichier_chemin);
+ $this->ajouterMessage("Signature MD5 du fichier bdnt partiel :".$this->signature_md5_partiel);
+ $this->ajouterMessage("Nombre de combinaisons traitées : ".$this->noms_stat['combinaison']);
+ }
+ }
+
+ private function attribuerRangTaxref($rang) {
+ $rang_taxref = '';
+ if (isset($this->rangs_bdnt_taxref[$rang])) {
+ $rang_taxref = $this->rangs_bdnt_taxref[$rang];
+ }
+ return $rang_taxref;
+ }
+
+ private function attribuerStatutTaxref($nom) {
+ $statut_taxref = '';
+ $champs_statuts = array('presence', 'statut_origine', 'statut_introduction', 'statut_culture');
+ foreach ($champs_statuts as $champ) {
+ $valeur = trim($nom[$champ]);
+ if (isset($this->statuts_bdnt_taxref[$champ][$valeur])) {
+ $statut_taxref = $this->statuts_bdnt_taxref[$champ][$valeur];
+ }
+ }
+ return $statut_taxref;
+ }
+
+ private function ecrireFichier($fichier_chemin, &$contenu) {
+ $retour = true;
+ if (file_put_contents($fichier_chemin, $contenu) == false) {
+ $e = "Une erreur est survenu lors de l'écriture du fichier : $fichier_chemin";
+ $this->ajouterMessage($e);
+ $retour = false;
+ }
+ $contenu = null;
+ return $retour;
+ }
+
+ private function ajouterAuFichier($fichier_chemin, &$contenu) {
+ $retour = true;
+ if (file_put_contents($fichier_chemin, $contenu, FILE_APPEND) == false) {
+ $e = "Une erreur est survenu lors de l'ajout de données dans le fichier : $fichier_chemin";
+ $this->ajouterMessage($e);
+ $retour = false;
+ }
+ $contenu = null;
+ return $retour;
+ }
+
+ private function creerFichierDiff() {
+ $donnees = array();
+ $this->derniere_meta = $this->metaDao->getDerniere($this->projet);
+ if (is_null($this->derniere_meta === false)) {
+ $this->ajouterMessage("Un problème est survenu lors de la récupération des métadonnées précédentes.");
+ } else if (is_null($this->derniere_meta)) {
+ $this->ajouterMessage("Premier versionnage pour ce projet, aucun fichier différentiel ne sera créé.");
+ } else {
+ $code_projet_precedent = strtolower($this->derniere_meta['code']).'_v'.str_replace('.', '_', $this->derniere_meta['version']);
+ if ($code_projet_precedent == $this->version_courante) {
+ $e = "La code de la version préalablement versionnée ($code_projet_precedent) est le même que celui ".
+ "de la demande actuel ({$this->version_courante}) pour ce projet, aucun fichier différentiel ne sera créé.";
+ $this->ajouterMessage($e);
+ } else {
+ $this->noms_precedents = $this->referentielDao->getTout($code_projet_precedent);
+ $donnees['diff'] = $this->realiserDiff();
+ $donnees['champs'] = $this->diff_champs_nom;
+ $diff_tsv =& $this->getVue('versionnage/squelettes/diff', &$donnees, '.tpl.tsv');
+ $this->ecrireFichierDiff($diff_tsv);
+ }
+ }
+ return $donnees;
+ }
+
+ private function realiserDiff() {
+ $this->chargerTableauChampsModifTypes();
+ $this->noms_stat['modification'] = 0;
+
+ $this->verifierNomsSupprimes();
+ $this->champs_courants = $this->champs_nom;
+ $diff = array();
+ foreach ($this->noms as $id => $nom) {
+ $this->supprimerNomAExclurePartiel($this->noms, $id);
+ $infos = array();
+ if ($this->etreSupprime($id)) {
+ $infos = $this->traiterDiffSuppression($nom);
+ } else if ($this->etreAjoute($id)) {
+ $infos = $this->traiterDiffAjout($nom);
+ } else {
+ $nom_precedent = $this->noms_precedents[$id];
+ $this->supprimerNomAExclurePartiel($this->noms_precedents, $id);
+ $nom_precedent = $this->nettoyerEspacesTableau($nom_precedent);
+ $infos = $this->traiterDiffModif($nom, $nom_precedent);
+ }
+
+ if (count($infos) > 0) {
+ $this->noms_stat['modification']++;
+ $infos = $this->remplacerTabulation($infos);
+ $infos = $this->remplacerSautsDeLigne($infos);
+ $diff[$id] = $infos;
+ }
+ }
+
+ return $diff;
+ }
+
+ private function verifierNomsSupprimes($edition = '«Complète»') {
+ $this->noms_supprimes = array_diff_key($this->noms_precedents, $this->noms);
+ $e = count($this->noms_supprimes)." lignes ont été supprimées vis à vis de la version ".
+ "précédentes pour l'édition $edition. Cela concerne les noms : ".implode(', ', array_keys($this->noms_supprimes));
+ Debug::printr($e);
+ $this->ajouterMessage($e);
+ }
+
+ private function supprimerNomAExclurePartiel(&$tableau, $id) {
+ if ($tableau[$id]['exclure_taxref'] == '1') {
+ unset($tableau[$id]);
+ }
+ }
+
+ private function nettoyerNomAExclurePartiel($nom) {
+ foreach ($nom as $champ => $valeur) {
+ if (! in_array($champ, $this->champs_nom_partiel)) {
+ unset($nom[$champ]);
+ }
+ }
+ return $nom;
+ }
+
+ private function etreSupprime($id) {
+ $supprime = false;
+ if (array_key_exists($id, $this->noms_supprimes)) {
+ $supprime = true;
+ }
+ return $supprime;
+ }
+
+ private function traiterDiffSuppression(&$nom) {
+ $infos = $nom;
+ $infos['modification_type'] = 'S';
+ $infos['modification_type_1'] = '0';
+ $infos['modification_type_2'] = '0';
+ $infos['modification_type_3'] = '0';
+ return $infos;
+ }
+
+ private function etreAjoute($id) {
+ $ajoute = false;
+ if (! array_key_exists($id, $this->noms_precedents)) {
+ $ajoute = true;
+ }
+ return $ajoute;
+ }
+
+ private function nettoyerEspacesTableau($tableau) {
+ array_walk($tableau, create_function('&$val', '$val = trim($val);'));
+ return $tableau;
+ }
+
+ private function traiterDiffAjout(&$nom) {
+ $infos = $nom;
+ $infos['modification_type'] = 'A';
+ $infos['modification_type_1'] = '0';
+ $infos['modification_type_2'] = '0';
+ $infos['modification_type_3'] = '0';
+ return $infos;
+ }
+
+ private function traiterDiffModif(&$nom, &$nom_precedent) {
+ $infos = array();
+ $nom_diff = array_diff_assoc($nom, $nom_precedent);
+
+ if (count($nom_diff) > 0) {
+ $modif['modification_type'] = 'M';
+ $modif['modification_type_1'] = '0';
+ $modif['modification_type_2'] = '0';
+ $modif['modification_type_3'] = '0';
+
+ foreach ($this->champs_courants as $champ) {
+ if (isset($nom_diff[$champ])) {
+ // Si le champ modifié est vide nous retournons le mot clé "NULL" pour identifier le champ modifié
+ $infos[$champ] = ($nom_diff[$champ] != '') ? $nom_diff[$champ] : 'NULL';
+ $type = $this->getDiffType($champ);
+ $modif['modification_type_'.$type] = '1';
+ } else {
+ if ($champ == 'num_nom') {
+ $infos[$champ] = $nom[$champ];
+ } else {
+ $infos[$champ] = '';
+ }
+ }
+ }
+
+ foreach ($modif as $cle => $val) {
+ $infos[$cle] = $val;
+ }
+ }
+ return $infos;
+ }
+
+ private function chargerTableauChampsModifTypes() {
+ $champs = explode(',', $this->manuel['champs_diff_type']);
+ foreach ($champs as $champ) {
+ list($champ_nom, $type) = explode('=', trim($champ));
+ $this->diff_modif_types[$champ_nom] = $type;
+ }
+ }
+
+ private function getDiffType($champ_nom) {
+ $type = isset($this->diff_modif_types[$champ_nom]) ? $this->diff_modif_types[$champ_nom] : '3';
+ return $type;
+ }
+
+ private function ecrireFichierDiff(&$contenu) {
+ $fichier_nom = $this->getBaseNomFichier().$this->manuel['suffixe_diff'].$this->manuel['ext_fichier'];
+ $fichier_chemin = $this->zip_chemin_dossier.$fichier_nom;
+
+ if ($this->ecrireFichier($fichier_chemin, $contenu)) {
+ $this->ajouterMessage("Écriture du fichier diff réussie.");
+ }
+ }
+
+ private function creerFichierDiffPartiel(&$donnees) {
+ $this->noms_stat_partiel['modification'] = 0;
+ if (! is_null($this->derniere_meta) && $this->derniere_meta !== false) {
+ $donnees['diff_partiel'] = $this->realiserDiffPartiel();
+ $donnees['champs_partiel_diff'] = array_merge($this->champs_nom_partiel, $this->champs_diff);
+ $donnees['dernier_champ'] = end($donnees['champs_partiel_diff']);
+ $diff_tsv_partiel =& $this->getVue('versionnage/squelettes/diff_partiel', &$donnees, '.tpl.tsv');
+ $this->ecrireFichierDiffPartiel($diff_tsv_partiel);
+ } else {
+ $this->ajouterMessage("Aucun fichier différentiel pour l'édition «Partielle» ne sera créé.");
+ }
+ }
+
+ private function realiserDiffPartiel() {
+ $this->noms_stat_partiel['modification'] = 0;
+
+ $this->champs_courants = $this->champs_nom_partiel;
+ $this->verifierNomsSupprimes('«Partielle»');
+ $diff = array();
+ foreach ($this->noms as $id => $nom) {
+ unset($this->noms[$id]);
+
+ // Ajout de champs spécifique à Taxref généré depuis les données BDNT
+ $nom['taxref_rang'] = $this->attribuerRangTaxref($nom['rang']);
+ $nom['taxref_statut'] = $this->attribuerStatutTaxref($nom);
+ $nom = $this->nettoyerNomAExclurePartiel($nom);
+ $infos = array();
+ if ($this->etreSupprime($id)) {
+ $infos = $this->traiterDiffSuppression($nom);
+ } elseif ($this->etreAjoute($id)) {
+ $infos = $this->traiterDiffAjout($nom);
+ } else {
+ $nom_precedent = $this->noms_precedents[$id];
+ unset($this->noms_precedents[$id]);
+
+ // Ajout de champs spécifique à Taxref généré depuis les données BDNT
+ $nom_precedent['taxref_rang'] = $this->attribuerRangTaxref($nom_precedent['rang']);
+ $nom_precedent['taxref_statut'] = $this->attribuerStatutTaxref($nom_precedent);
+ $nom_precedent = $this->nettoyerNomAExclurePartiel($nom_precedent);
+ $nom_precedent = $this->nettoyerEspacesTableau($nom_precedent);
+ $infos = $this->traiterDiffModif($nom, $nom_precedent);
+ }
+
+ if (count($infos) > 0) {
+ $this->noms_stat_partiel['modification']++;
+ $infos = $this->remplacerTabulation($infos);
+ $infos = $this->remplacerSautsDeLigne($infos);
+ $diff[$id] = $infos;
+ }
+ }
+
+ return $diff;
+ }
+
+ private function ecrireFichierDiffPartiel(&$contenu) {
+ $fichier_nom = $this->getBaseNomFichier().$this->manuel['suffixe_diff'].$this->manuel['suffixe_partiel'].$this->manuel['ext_fichier'];
+ $fichier_chemin = $this->zip_chemin_dossier_partiel.$fichier_nom;
+
+ if ($this->ecrireFichier($fichier_chemin, $contenu)) {
+ $this->ajouterMessage("Écriture du fichier diff partiel réussie.");
+ }
+ }
+
+ private function creerFichierMeta() {
+ $donnees = array();
+ $donnees = $this->meta;
+ $donnees['stats'] = $this->noms_stat;
+ $donnees['signature'] = $this->signature_md5;
+
+ $donnees = $this->remplacerTabulation($donnees);
+ $donnees = $this->remplacerSautsDeLigne($donnees);
+
+ $meta_tsv =& $this->getVue('versionnage/squelettes/meta', &$donnees, '.tpl.tsv');
+ $this->ecrireFichierMeta($meta_tsv);
+ return $donnees;
+ }
+
+ private function ecrireFichierMeta(&$contenu) {
+ $fichier_nom = $this->getBaseNomFichier().$this->manuel['suffixe_meta'].$this->manuel['ext_fichier'];
+ $fichier_chemin = $this->zip_chemin_dossier.$fichier_nom;
+
+ if ($this->ecrireFichier($fichier_chemin, $contenu)) {
+ $this->ajouterMessage("Écriture du fichier meta réussie.");
+ $this->archiverMetadonnees();
+ }
+ }
+
+ private function creerFichierMetaPartiel(&$donnees) {
+ $donnees['signature'] = $this->signature_md5_partiel;
+ $donnees['stats'] = $this->noms_stat_partiel;
+ $donnees['notes'] = trim($this->manuel['notes_partiel'].' '.$donnees['notes']);
+ $meta_tsv_partiel =& $this->getVue('versionnage/squelettes/meta', &$donnees, '.tpl.tsv');
+ $this->ecrireFichierMetaPartiel($meta_tsv_partiel);
+ }
+
+ private function ecrireFichierMetaPartiel(&$contenu) {
+ $fichier_nom = $this->getBaseNomFichier().$this->manuel['suffixe_meta'].$this->manuel['suffixe_partiel'].$this->manuel['ext_fichier'];
+ $fichier_chemin = $this->zip_chemin_dossier_partiel.$fichier_nom;
+
+ if ($this->ecrireFichier($fichier_chemin, $contenu)) {
+ $this->ajouterMessage("Écriture du fichier meta partiel réussie.");
+ }
+ }
+
+ private function archiverMetadonnees() {
+ $metadonnees = $this->meta;
+ $metadonnees['code'] = $this->meta['acronyme'];
+ unset($metadonnees['acronyme']);
+ $metadonnees['domaine_taxo'] = $this->meta['dom_tax'];
+ unset($metadonnees['dom_tax']);
+ $metadonnees['domaine_geo'] = $this->meta['dom_geo'];
+ unset($metadonnees['dom_geo']);
+ $metadonnees['domaine_nom'] = $this->meta['dom_code'];
+ unset($metadonnees['dom_code']);
+ $metadonnees['auteur'] = $this->meta['auteur_principal'];
+ unset($metadonnees['auteur_principal']);
+ $metadonnees['date_production'] = $this->meta['date_prod'];
+ unset($metadonnees['date_prod']);
+ $metadonnees['droit'] = $this->meta['copyright'];
+ unset($metadonnees['copyright']);
+
+ $ok = $this->metaDao->ajouter($metadonnees);
+ if ($ok === false) {
+ $this->ajouterMessage("L'archivage des métadonnées a échoué.");
+ }
+ }
+
+ private function nettoyerMemoire() {
+ $this->noms = null;
+ $this->noms_precedents = null;
+ $this->noms_stat = null;
+ }
+
+ private function copierManuel() {
+ $fichier_source = $this->manuel_chemin.$this->manuel_nom;
+ $fichiers_destination[] = $this->zip_chemin_dossier.$this->manuel_nom;
+ $fichiers_destination[] = $this->zip_chemin_dossier_partiel.$this->manuel_nom;
+ foreach ($fichiers_destination as $destination) {
+ if (copy($fichier_source, $destination) === false) {
+ $this->ajouterMessage("La copie du manuel vers '$destination' a échouée.");
+ }
+ }
+ }
+
+ private function creerFichiersZip() {
+ $this->zipper($this->zip_chemin_fichier, $this->zip_chemin_dossier);
+ $this->zipper($this->zip_chemin_fichier_partiel, $this->zip_chemin_dossier_partiel);
+ }
+
+ private function zipper($fichier_zip, $dossier_a_zipper) {
+ $zip = new PclZip($fichier_zip);
+ if ($zip->add($dossier_a_zipper, PCLZIP_OPT_REMOVE_ALL_PATH) == 0) {
+ $e = "La création du fichier zip '$fichier_zip' a échoué avec l'erreur : ".$zip->errorInfo(true);
+ $this->ajouterMessage($e);
+ }
+ }
+
+ private function nettoyerFichiers() {
+ Fichier::supprimerDossier($this->zip_chemin_dossier);
+ Fichier::supprimerDossier($this->zip_chemin_dossier_partiel);
+ }
+
+ private function traiterMessages() {
+ if (isset($this->messages)) {
+ $num_message = 1;
+ foreach ($this->messages as $message) {
+ $message['nom'] = 'Message #'.$num_message++;
+ $this->resultatDao->ajouter($this->traitement['id_traitement'], $message);
+ }
+ }
+ }
+}
+?>
\ No newline at end of file
Index: v1.1/trunk/scripts/modules/tests/configurations/tests.ini
===================================================================
--- v1.1/trunk/scripts/modules/tests/configurations/tests.ini (revision 0)
+++ v1.1/trunk/scripts/modules/tests/configurations/tests.ini (revision 287)
@@ -0,0 +1,521 @@
+[1]
+abr = "Nombre De Champs"
+nom = "Structure -> nombre de champs"
+description = "Le nombre de champs présent dans la table contenant le référentiel doit être supérieur ou égal à 36."
+message = "Le nombre de colonnes est de %s alors qu'il doit être supérieur ou égal à 36."
+[2]
+abr = "Nom Des Champs"
+nom = "Structure -> noms des champs"
+description = "Les noms des champs de la table contenant le référentiel doivent être conformes à ceux définis par le manuel technique."
+message = "Champs manquants : %s."
+[3]
+abr = "Type Des Champs"
+nom = "Structure -> types des champs"
+description = "Les types des champs (text, varchar, int) de la table contenant le référentiel doivent être conformes à ceux définis par le manuel technique."
+entete = "Champ,Type attendu,Type présent"
+[4]
+abr = "Num Nom Cle Primaire"
+nom = "Structure -> num_nom est clé primaire"
+description = "Le champ num_nom doit être clé primaire de la table."
+[5]
+abr = "Taille Des Champs"
+nom = "Structure -> champs tronqués"
+description = "Les champs indiqués en erreur ont atteint leur taille limite de remplissage (ex: varchar(4) pour le champ annee). Un résultat de type KO attire l'attention du coordinateur sur la possibilité que la valeur du champ d'origine ait été tronquée lors de son dépôt dans phpmyadmin."
+entete="Champ,Taille attendu,Taille présente"
+[6]
+abr = "Num Nom Superieur A Zero"
+nom = "num_nom -> supérieur à 0"
+description = "Le champ num_nom doit contenir des nombres entiers supérieurs à 0."
+entete="num_nom"
+[7]
+abr = "Num Nom Retenu Superieur A Zero"
+nom = "num_nom_retenu -> supérieur à 0"
+description = "Le champ num_nom_retenu doit contenir des nombres entiers supérieurs à 0 ou être vide."
+entete="num_nom,num_nom_retenu"
+[8]
+abr = "Existence Num Nom Retenu"
+nom = "num_nom_retenu -> existence"
+description = "Les valeurs du champ num_nom_retenu doivent correspondre à un num_nom."
+entete = "num_nom,num_nom_retenu,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[9]
+abr = "Transmission Num Nom Retenu"
+nom = "num_nom_retenu -> num_nom_retenu transmis au MNHN"
+description = "Les valeurs du champ num_nom_retenu doivent correspondre à un num_nom dont la valeur du champs exclure_taxref est égale à 0."
+entete = "num_nom,num_nom_retenu,Exclusion de Taxref num_nom_retenu"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[10]
+abr = "Synonyme Num Nom Retenu"
+nom = "num_nom_retenu -> renvoie à un nom retenu"
+description = "Un synonyme doit être rattaché à un nom retenu. Son champ num_nom_retenu ne doit pas correspondre à un synonyme, soit num_nom = num_nom_retenu."
+entete = "num_nom,num_nom_retenu,num_nom_retenu du num_nom_retenu"
+[11]
+abr = "Num Tax Sup Egal Zero Unique"
+nom = "num_tax_sup -> égal à 0 unique"
+description = "Un seul enregistrement doit posséder la valeur 0 dans le champ num_tax_sup. Il correspond au premier taxon de la classification."
+entete = "num_nom,num_tax_sup"
+[12]
+abr = "Tax Sup Pour Taxon"
+nom = "Classification -> uniquement pour les taxons"
+description = "Seuls les noms retenus doivent posséder une valeur dans le champ num_tax_sup.
+Si num_nom_retenu est différent de num_nom (cas des synonymes) alors num_tax_sup doit être vide."
+entete = "num_nom,num_tax_sup,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[13]
+abr = "Existence Taxon Superieur"
+nom = "Classification -> existence du taxon supérieur"
+description = "Tous les noms retenus doivent posséder un taxon supérieur existant dans la base (sauf la racine de la classification).
+Si num_nom_retenu est égal à num_nom alors num_tax_sup doit être rempli et correspondre à un num_nom."
+entete = "num_nom,num_tax_sup,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[14]
+abr = "Transmission Taxon Superieur"
+nom = "Classification -> taxon supérieur transmis au MNHN"
+description = "Chaque enregistrement transmis (exclure_taxref = 0) doit posséder un taxon supérieur transmis."
+entete = "num_nom,num_tax_sup,Exclusion de Taxref num_tax_sup"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[15]
+abr = "Classification Rang"
+nom = "Classification -> taxon supérieur avec rang supérieur"
+description = "Pour chaque enregistrement représentant un taxon, le taxon supérieur doit avoir un code de rang inférieur au code de rang du taxon courant (à l'exception des noms ayant le rang de Clade)."
+entete = "num_nom,rang,Taxon supérieur num_nom,Taxon supérieur rang,Exclusion de Taxref"
+title=";;;;0 = non, 1 = oui, 9 = indéterminée"
+[16]
+abr = "Classification"
+nom = "Classification -> racine liée à chaque noeud"
+description = "Pour chaque nom retenu, la classification doit pouvoir être remontée jusqu'à un nom unique possédant une valeur num_tax_sup de 0."
+entete = "num_nom,Message du problème,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[17]
+abr = "Rang"
+nom = "rang"
+description = "Le rang doit correspondre à une valeur numérique définie dans le manuel."
+entete = "num_nom,Rang,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[18]
+abr = "Nom Complet Supra Generique"
+nom = "nom_sci -> noms supra-génériques"
+description = "Si le rang est < à "RANG_GENRE" (> genre), le nom_sci doit correspondre à la valeur du champ nom_supra_generique. Les valeurs des champs cultivar_groupe, cultivar et
+nom_commercial peuvent s'y ajouter."
+entete = "num_nom,nom_sci,nom_sci corrigé,Exclusion de Taxref"
+title=";;Proposition de correction. N'est pas obligatoirement correcte;0 = non, 1 = oui, 9 = indéterminée"
+[19]
+abr = "Nom Complet Genre"
+nom = "nom_sci -> noms de genres"
+description = "Si le rang est = à "RANG_GENRE" (= genre), le nom_sci doit correspondre à la valeur du champ genre. Les valeurs des champs cultivar_groupe, cultivar et nom_commercial peuvent s'y ajouter."
+entete = "num_nom,nom_sci,nom_sci corrigé,Exclusion de Taxref"
+title=";;Proposition de correction. N'est pas obligatoirement correcte;0 = non, 1 = oui, 9 = indéterminée"
+[20]
+abr = "Nom Complet Infra Genre"
+nom = "nom_sci -> noms infra-génériques"
+description = "Si le rang est > à "RANG_GENRE" (< genre) et < à "RANG_SP" (> espèce), le nom_sci doit correspondre à une des formules suivantes :
+genre + ' ' + type_epithete + ' ' + epithete_infra_generique
+genre + ' ' + epithete_infra_generique + ' ' + type_epithete=agg.
+Les valeurs des champs cultivar_groupe, cultivar et nom_commercial peuvent s'y ajouter."
+entete = "num_nom,nom_sci,nom_sci corrigé,Exclusion de Taxref"
+title=";;Proposition de correction. N'est pas obligatoirement correcte;0 = non, 1 = oui, 9 = indéterminée"
+[21]
+abr = "Nom Complet Espece"
+nom = "nom_sci -> noms d'espèce"
+description = "Si le rang est = à "RANG_SP" (= espèce), le nom_sci doit correspondre à la formule :
+genre + ' ' + epithete_sp
+Les valeurs des champs cultivar_groupe, cultivar et nom_commercial peuvent s'y ajouter.
+Des erreurs peuvent être générées lors de la reconstruction des formules d'hybridité à partir des champs cités ci-dessus. Pour les éviter, ne remplir que le champ concernant le rang de l'hybride)."
+entete = "num_nom,nom_sci,nom_sci corrigé,Proposition d'erreur,Exclusion de Taxref"
+title=";;Proposition de correction. N'est pas obligatoirement correcte;;0 = non, 1 = oui, 9 = indéterminée"
+[22]
+abr = "Nom Complet Infra Specifique"
+nom = "nom_sci -> noms infra-spécifiques"
+description = "Si le rang est > à "RANG_SP" (< espèce), le nom_sci doit correspondre à la formule :
+genre + ' ' + epithete_sp + ' ' + type_epithete + ' ' + epithete_infra_specifique
+Les valeurs des champs cultivar_groupe, cultivar et nom_commercial peuvent s'y ajouter.
+Des erreurs peuvent être générées lors de la reconstruction des formules d'hybridité à partir des champs cités ci-dessus. Pour les éviter, ne remplir que le champ concernant le rang de l'hybride."
+entete = "num_nom,nom_sci,nom_sci corrigé,Proposition d'erreur,Exclusion de Taxref"
+title=";;Proposition de correction. N'est pas obligatoirement correcte;;0 = non, 1 = oui, 9 = indéterminée"
+[23]
+abr = "Nom Supra Generique Espaces"
+nom = "nom_supra_generique -> espaces en trop"
+description = "Le champ nom_supra_generique ne doit pas contenir d'espace avant ou après le nom."
+entete = "num_nom,nom_supra_generique erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[24]
+abr = "Nom Supra Generique Syntaxe"
+nom = "nom_supra_generique -> syntaxe"
+description = "Le champ nom_supra_generique contient un mot composé de lettres minuscules, avec ou sans tréma (¨), et de tirets (-).
+La première lettre (avec ou sans tréma) du mot doit être en majuscule."
+entete = "num_nom,nom_supra_generique erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[25]
+abr = "Nom Supra Generique Rang"
+nom = "nom_supra_generique -> rang < "RANG_GENRE
+description = "Si le champ nom_supra_generique n'est pas vide alors le rang du nom doit être inférieur à "RANG_GENRE" (> genre)."
+entete = "num_nom,nom_sci,rang erroné,Exclusion de Taxref"
+title=";;;0 = non, 1 = oui, 9 = indéterminée"
+[26]
+abr = "Genre Espaces"
+nom = "genre -> espaces en trop"
+description = "Le champ genre ne doit pas contenir d'espace avant ou après le nom.
+Si des espaces sont compris dans la valeur du champ, il ne doit pas en avoir plusieurs consécutifs."
+entete = "num_nom,genre erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[27]
+abr = "Genre Syntaxe"
+nom = "genre -> syntaxe"
+description = "Le champ genre peut contenir :
+ - un mot unique composé de lettres minuscules, avec ou sans tréma (¨), et de tirets (-).
+ Il commence par une lettre majuscule (avec ou sans tréma) et peut être précédé par le signe + ou la lettre x suivi d'un espace.
+ - une formule d'hybridité composée d'une série de noms de genre (au moins 2) séparés
+ entre eux par la lettre x entourée de caractères espaces."
+entete = "num_nom,genre erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[28]
+abr = "Genre Rang"
+nom = "genre -> rang >= "RANG_GENRE
+description = "Si le champ genre n'est pas vide alors le rang du nom doit être supérieur ou égal à "RANG_GENRE" (< ou = genre)."
+entete = "num_nom,nom_sci,rang erroné,Exclusion de Taxref"
+title=";;;0 = non, 1 = oui, 9 = indéterminée"
+[29]
+abr = "Epithete Infra Generique Syntaxe"
+nom = "epithete_infra_generique -> syntaxe"
+description = "Le champ epithete_infra_generique est composé de lettres minuscules, avec ou sans tréma (¨), et de tirets (-).
+La première lettre (avec ou sans tréma) doit être en majuscule."
+entete = "num_nom,epithete_infra_generique erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[30]
+abr = "Epithete Infra Generique Rang"
+nom = "epithete_infra_generique -> "RANG_GENRE" < rang < "RANG_SP
+description = "Si le champ epithete_infra_generique n'est pas vide alors le rang du nom doit être compris entre "RANG_GENRE" (< genre) et "RANG_SP" (> espèce)."
+entete = "num_nom,nom_sci,rang erroné,Exclusion de Taxref"
+title=";;;0 = non, 1 = oui, 9 = indéterminée"
+[31]
+abr = "Epithete Infra Generique Espaces"
+nom = "epithete_infra_generique -> espaces en trop"
+description = "Le champ epithete_infra_generique ne doit pas contenir d'espace avant ou après sa valeur."
+entete = "num_nom,epithete_infra_generique erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[32]
+abr = "Epithete Sp Espaces"
+nom = "epithete_sp -> espaces en trop"
+description = "Le champ epithete_sp ne doit pas contenir d'espace avant ou après le nom.
+Si des espaces sont compris dans la valeur du champ, il ne doit pas en avoir plusieurs consécutifs."
+entete = "num_nom,epithete_sp erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[33]
+abr = "Epithete Sp Syntaxe"
+nom = "epithete_sp -> syntaxe"
+description = "Le champ epithete_sp peut contenir :
+ - un mot unique composé de lettres minuscules [a-z] incluant les caractères [ëï-].
+ Il commence par une lettre minuscule [a-zëï].
+ Il peut être précédé par le signe + ou la lettre x suivi d'un espace.
+ - un mot contenant sp. suivi d'un ou plusieurs caractères numériques (1-9)
+ ou d'un seul caractère majuscule (A-Z)
+ - une formule d'hybridité composée d'une série de noms d'espèce (au moins 2)
+ séparés entre eux par la lettre x entourée de caractères espaces."
+entete = "num_nom,epithete_sp erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[34]
+abr = "Epithete Sp Rang"
+nom = "epithete_sp -> rang >= "RANG_SP
+description = "Si le champ epithete_sp n'est pas vide alors le rang du nom doit être supérieur ou égal à "RANG_SP" (< ou = espèce)."
+entete = "num_nom,nom_sci,rang erroné,Exclusion de Taxref"
+title=";;;0 = non, 1 = oui, 9 = indéterminée"
+[35]
+abr = "Type Epithete Espaces"
+nom = "type_epithete -> espaces en trop"
+description = "Le champ type_epithete ne doit pas contenir d'espace."
+entete = "num_nom,type_epithete erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[36]
+abr = "Type Epithete Syntaxe"
+nom = "type_epithete -> syntaxe"
+description = "Le champ type_epithete doit contenir un mot unique composé de lettres minuscules, sans accent, et de tirets (-). Il commence par une lettre minuscule sans accent et se termine, ou pas, par un point."
+entete = "num_nom,type_epithete erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[37]
+abr = "Type Epithete Hybridite"
+nom = "type_epithete -> hybridité"
+description = "Le champ type_epithete ne doit pas contenir de préfixe indiquant l'hybridité comme :
+ - «n-»
+ - «notho-»"
+entete = "num_nom,type_epithete erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[38]
+abr = "Epithete Infra Sp Espaces"
+nom = "epithete_infra_sp -> espaces en trop"
+description = "Le champ epithete_infra_sp ne doit pas contenir d'espace avant ou après le nom.
+Si des espaces sont compris dans la valeur du champ, il ne doit pas y en avoir plusieurs consécutifs."
+entete = "num_nom,epithete_infra_sp erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[39]
+abr = "Epithete Infra Sp Syntaxe"
+nom = "epithete_infra_sp -> syntaxe"
+description = "Le champ epithete_infra_sp peut contenir :
+ - un mot unique composé de lettres minuscules, avec ou sans tréma (¨), et de tirets (-).
+ Il commence par une lettre minuscule (avec ou sans tréma).
+ Il peut être précédé par le signe + ou la lettre x suivi d'un espace.
+ - une formule d'hybridité composée d'une série de noms d'espèce ou d'infra espèce
+ (au moins 2) séparés entre eux par la lettre x entourée de caractères espaces."
+entete = "num_nom,epithete_infra_sp erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[40]
+abr = "Epithete Infra Sp Rang"
+nom = "epithete_infra_sp -> rang > "RANG_SP
+description = "Si le champ epithete_infra_sp n'est pas vide alors le rang du nom doit être supérieur à "RANG_SP" (< espèce)."
+entete = "num_nom,nom_sci,rang erroné,Exclusion de Taxref"
+title=";;;0 = non, 1 = oui, 9 = indéterminée"
+[41]
+abr = "Groupe Cultivar Syntaxe"
+nom = "cultivar_groupe -> syntaxe"
+description = "Le champ cultivar_groupe doit contenir un nom de groupe de cultivar conforme aux règles du code des plantes cultivées, c-à-d composer de caractères alphanumériques (A-Z,a-z et 0-9) incluant signes diacritiques et marques de ponctuations.
+Il ne doit pas contenir le mot Groupe, l'abbréviation «gp» ou des parenthèses.
+Il peut se terminer par l'abréviation «gx» pour distinguer les groupes des grex."
+entete = "num_nom,cultivar_groupe erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[42]
+abr = "Groupe Cultivar Rang"
+nom = "cultivar_groupe -> rang >= "RANG_GENRE
+description = "Si le champ cultivar_groupe n'est pas vide alors le rang du nom doit être supérieur ou égal à "RANG_GENRE" (< ou = genre)."
+entete = "num_nom,nom_sci,rang erroné,Exclusion de Taxref"
+title=";;;0 = non, 1 = oui, 9 = indéterminée"
+[43]
+abr = "Cultivar Syntaxe"
+nom = "cultivar -> syntaxe"
+description = "Le champ cultivar_groupe doit contenir :
+ - un nom de cultivar conforme aux règles du Code Internationnal de Nomenclature
+ des Plantes Cultivées (CINPC), c.-à-d. composer de caractères alphanumériques
+ (A-Z,a-z et 0-9) incluant signes diacritiques et marques de ponctuations.
+ - un nom en alphabet latin, ce qui implique une translittération des noms d'alphabet
+ cyrillique, arabe, chinois...
+ - une lettre majuscule obligatoire pour le premier caractère du premier mot et pour
+ les autres mots importants mais pas pour les mots mineurs.
+Ne doit pas contenir :
+ - cv., convar. ou de guillemets simples (')."
+entete = "num_nom,cultivar erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[44]
+abr = "Cultivar Rang"
+nom = "cultivar -> rang >= "RANG_GENRE
+description = "Si le champ cultivar n'est pas vide alors le rang du nom doit être supérieur ou égal à "RANG_GENRE" (< ou = genre)."
+entete = "num_nom,nom_sci,rang erroné,Exclusion de Taxref"
+title=";;;0 = non, 1 = oui, 9 = indéterminée"
+[45]
+abr = "Nom Commercial Syntaxe"
+nom = "nom_commercial -> syntaxe"
+description = "Le champ nom_commercial doit contenir un nom commercial conforme aux règles du Code Internationnal de Nomenclature des Plantes Cultivées (CINPC) qui se compose de caractères majuscules (A-Z) incluant des signes diacritiques et des espaces."
+entete = "num_nom,cultivar erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[46]
+abr = "Nom Commercial Presence Cultivar"
+nom = "nom_commercial -> groupe_cultivar OU cultivar non vide"
+description = "Si le champ nom_commercial contient un nom commercial alors le champ cultivar OU cultivar_groupe ne doit pas être vide."
+entete = "num_nom,nom_commercial sans cultivar ou cultivar_groupe,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[47]
+abr = "Auteur Syntaxe"
+nom = "auteur -> syntaxe"
+description = "Le champ auteur doit :
+ - contenir les abréviations du(es) nom(s) de l'auteur (des auteurs) ayant publié à l'origine la combinaison latine courante et respectant les standards.
+ - OU débuter par le mot « sensu » et contenir l'intitulé complet des noms de l'auteur, ou des auteurs, ayant publié un nom dont la description ne correspond pas à celle de l'auteur, ou des auteurs, d'origine.
+ - se composer de caractères alphabétiques (A-Z, a-z), incluant les signes diacritiques, le symbole point (.), les paires de parenthèses ( () ), les apostrophes, l'esperluette (&) et l'espace ( ).
+ - contenir une translittération des noms d'alphabet cyrillique, arabe, chinois... en alphabet latin.
+ - inclure entre parenthèses l'intitulé des noms de l'auteur ou des auteurs ayant publié le basionyme.
+ - toujours utiliser l'esperluette (&) à la place du mot « et » pour séparer les noms d'auteurs.
+ - si une citation comprend plus de deux auteurs, citer tous les auteurs (contrairement à la recommandation 46C.2 du CINB).
+ - pas d'espace entre le point de la dernière initiale du prénom et le nom.
+ - peut rester vide si le nom est un autonyme (epithete_sp = epithete_infra_sp)."
+entete = "num_nom,auteur erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[48]
+abr = "Annee Syntaxe"
+nom = "annee -> syntaxe"
+description = "Le champ annee doit :
+ - contenir un nombre de 4 chiffres,
+ - être supérieur ou égal à 1753,
+ - être inférieur ou égal à l'année courante."
+entete = "num_nom, annee erronée,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[49]
+abr = "Biblio Origine Syntaxe"
+nom = "biblio_origine -> syntaxe"
+description = "Le champ biblio_origine se compose de plusieurs parties séparées par des caractères précis qui sont dans l'ordre de gauche à droite :
+ - éventuellement, le mot « in » suivi d'un intitulé auteur (utilisé pour indiquer l'intitulé auteur de l'ouvrage dans lequel la publication est parue),
+ - point-virgule « ; » (si l'info précédent a été renseignée),
+ - abréviation, ou nom, de l'ouvrage ou de la revue selon le standard en vigueur dans le code du nom.
+Cette information ne doit pas contenir de caractère virgule « , »,
+ - virgule « , »,
+ - informations permettant d'identifier plus précisément le document contenant le nom...
+(par exemple, l'éditeur, le tome, le numéro d'édition, le volume...) séparées par des virgules ou d'autres caractères sauf deux points « : ».
+ - deux points « : »,
+ - numéro(s) de la page contenant la publication du nom ou de l'ensemble de pages (première et dernière
+pages de l'ensemble séparées par un tiret « - »). Quelques fois des numéros ou d'autres informations indiquant
+la position du nom dans le document. Le tiret « - » doit toujours servir à séparer un ensemble."
+entete = "num_nom,biblio_origine erronée,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[50]
+abr = "Homonymie Syntaxe"
+nom = "homonyme -> syntaxe"
+description = "Le champ homonyme contient :
+ - une valeur vide,
+ - le chiffre 1."
+entete = "num_nom,homonyme erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[51]
+abr = "Homonymie Existence"
+nom = "homonyme -> existence"
+description = "Si le champ homonyme contient «1» alors plusieurs noms doivent posséder la même valeur dans le champ nom_sci."
+entete = "num_nom,homonyme introuvable,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[52]
+abr = "Basionyme Syntaxe"
+nom = "basionyme -> syntaxe"
+description = "Le champ basionyme contient :
+ - une valeur vide,
+ - un nombre."
+entete = "num_nom,basionyme erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[53]
+abr = "Basionyme Existence"
+nom = "basionyme -> existence"
+description = "Si le champ basionyme contient un nombre alors il doit correspondre à une valeur du champ num_nom."
+entete = "num_nom,basionyme introuvable,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[54]
+abr = "Synonyme Proparte Syntaxe"
+nom = "synonyme_proparte -> syntaxe"
+description = "Le champ synonyme_proparte contient soit :
+ - une valeur vide,
+ - un nombre,
+ - une suite de nombres séparés par des virgules."
+entete = "num_nom,synonyme_proparte erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[55]
+abr = "Synonyme Proparte Existence"
+nom = "synonyme_proparte -> existence"
+description = "Si le champ synonyme_proparte contient un ou plusieurs nombres alors chacun d'entre eux doit correspondre à une valeur du champ num_nom."
+entete = "num_nom, synonyme_proparte introuvable,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[56]
+abr = "Synonyme Douteux Syntaxe"
+nom = "synonyme_douteux -> syntaxe"
+description = "Le champ synonyme_douteux contient soit :
+ - une valeur vide,
+ - le chiffre 1."
+entete = "num_nom,synonyme_douteux erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[57]
+abr = "Synonyme Douteux Num Nom Retenu"
+nom = "synonyme_douteux -> présence num_nom_retenu"
+description = "Si le nom est un synonyme douteux, le champs num_nom_retenu doit être rempli."
+entete = "num_nom,synonyme_douteux,Exclusion de Taxref"
+title=";;;0 = non, 1 = oui, 9 = indéterminée"
+[58]
+abr = "Synonyme Mal Applique Syntaxe"
+nom = "synonyme_mal_applique -> syntaxe"
+description = "Le champ synonyme_mal_applique contient soit :
+ - une valeur vide,
+ - le chiffre 1."
+entete = "num_nom,synonyme_mal_applique erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[59]
+abr = "Synonyme Orthographique Syntaxe"
+nom = "synonyme_orthographique -> syntaxe"
+description = "Le champ synonyme_orthographique contient soit :
+ - une valeur vide,
+ - un nombre."
+entete = "num_nom,synonyme_orthographique erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[60]
+abr = "Synonyme Orthographique Existence"
+nom = "synonyme_orthographique -> existence"
+description = "Si le champ synonyme_orthographique contient un nombre alors il doit correspondre à une valeur du champ num_nom."
+entete = "num_nom,synonyme_orthographique introuvable,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[61]
+abr = "Hybride Parent01 Syntaxe"
+nom = "hybride_parent_01 -> syntaxe"
+description = "Le champ hybride_parent_01 contient soit :
+ - une valeur vide,
+ - un nombre."
+entete = "num_nom,hybride_parent_01 erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[62]
+abr = "Hybride Parent01 Existence"
+nom = "hybride_parent_01 -> existence"
+description = "Si le champ hybride_parent_01 contient un nombre alors il doit correspondre à une valeur du champ num_nom."
+entete = "num_nom,hybride_parent_01 introuvable,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[63]
+abr = "Hybride Parent02 Syntaxe"
+nom = "hybride_parent_02 -> syntaxe"
+description = "Le champ hybride_parent_02 contient soit :
+ - une valeur vide,
+ - un nombre."
+entete = "num_nom,hybride_parent_02 erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[64]
+abr = "Hybride Parent02 Existence"
+nom = "hybride_parent_02 -> existence"
+description = "Si le champ hybride_parent_02 contient un nombre alors il doit correspondre à une valeur du champ num_nom."
+entete = "num_nom,hybride_parent_02 introuvable,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[65]
+abr = "Presence Syntaxe"
+nom = "presence -> syntaxe"
+description = "Le champ presence contient soit :
+ - le symbole tiret «-» précédant une autre information non référencée...
+ - une première lettre en majuscule indiquant le code standard attribué à ce taxon,
+ - éventuellement suivie d'un tiret puis d'une deuxième lettre en majuscule indiquant un code de présence spécifique au référentiel."
+entete = "num_nom,presence erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[66]
+abr = "Statut Origine Syntaxe"
+nom = "statut_origine -> syntaxe"
+description = "Le champ statut_origine peut contenir :
+ - le symbole tiret «-» précédant une autre information non référencée...
+ - une première lettre en majuscule indiquant le code standard attribué à ce taxon.
+ - éventuellement suivie d'un tiret puis d'une deuxième lettre en majuscule indiquant un code de présence spécifique au référentiel."
+entete = "num_nom,statut_origine erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[67]
+abr = "Statut Introduction Syntaxe"
+nom = "statut_introduction -> syntaxe"
+description = "Le champ statut_introduction peut contenir :
+ - le symbole tiret «-» précédant une autre information non référencée...
+ - une première lettre en majuscule indiquant le code standard attribué à ce taxon,
+ - éventuellement suivie d'un tiret puis d'une deuxième lettre en majuscule indiquant un code de présence spécifique au référentiel."
+entete = "num_nom,statut_introduction erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[68]
+abr = "Statut Culture Syntaxe"
+nom = "statut_culture -> syntaxe"
+description = "Le champ statut_culture peut contenir :
+ - le symbole tiret «-» précédant une autre information non référencée...
+ - une première lettre en majuscule indiquant le code standard attribué à ce taxon,
+ - éventuellement suivie d'un tiret puis d'une deuxième lettre en majuscule indiquant un code de présence spécifique au référentiel."
+entete = "num_nom,statut_culture erroné,Exclusion de Taxref"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[69]
+abr = "Exclure Tax Ref Syntaxe"
+nom = "exclure_taxref -> syntaxe"
+description = "Le champ exclure_taxref contient soit :
+ - une valeur vide ou NULL.
+ - le chiffre 0 : si le nom n'a pas à être exclu de TaxRef.
+ - le chiffre 1 : pour exclure le nom de TaxRef
+ - le chiffre 9 : quand la décision sur l'exclusion reste à prendre (le nom sera malgré tout exclu)."
+entete = "num_nom,exclure_taxref erroné"
+[70]
+abr = "Nom francais Unique"
+nom = "nom_francais -> unique"
+description = "Le champ nom français peut contenir :
+ - une valeur vide ou NULL.
+ - un seul nom vernaculaire, ne contenant ni virgules, ni point-virgule"
+entete = "num_nom, nom_francais"
+title=";;0 = non, 1 = oui, 9 = indéterminée"
+[71]
+abr = "Exclure Tax Ref Presence"
+nom = "exclure_taxref -> presence"
+description = "Un nom retenu non-exclu de taxref doit contenir :
+ - une valeur correcte dans la colonne présence.
+ - si possible, une valeur correcte dans au moins une des colonnes de statut"
+entete = "num_nom, nom_sci, presence, statut_origine, statut_introduction, statut_culture"
+title=";;;;;;;"
\ No newline at end of file
Index: v1.1/trunk/scripts/modules/tests/squelettes/message_p.tpl.html
===================================================================
--- v1.1/trunk/scripts/modules/tests/squelettes/message_p.tpl.html (revision 0)
+++ v1.1/trunk/scripts/modules/tests/squelettes/message_p.tpl.html (revision 287)
@@ -0,0 +1 @@
+<p><?=$message?></p>
\ No newline at end of file
Index: v1.1/trunk/scripts/modules/tests/squelettes/message_table.tpl.html
===================================================================
--- v1.1/trunk/scripts/modules/tests/squelettes/message_table.tpl.html (revision 0)
+++ v1.1/trunk/scripts/modules/tests/squelettes/message_table.tpl.html (revision 287)
@@ -0,0 +1,28 @@
+<?php if (isset($erreur)) : ?>
+<p class="attention"><?=$erreur?></p>
+<?php endif; ?>
+<table>
+ <caption><?=count($message['lignes'])?> lignes en erreur</caption>
+ <thead>
+ <tr>
+ <?php foreach ($message['entete'] as $num => $entete) : ?>
+ <th<?=(isset($message['title'][$num]) && !empty($message['title'][$num])) ? ' title="'.$message['title'][$num].'"' : ''?>><?=$entete?></th>
+ <?php endforeach; ?>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($message['lignes'] as $num => $ligne) : ?>
+ <tr>
+ <?php foreach ($ligne as $num => $info) : ?>
+ <td>
+ <?php if (preg_match('/^(num_nom|num_nom_retenu|num_tax_sup)$/', $message['entete'][$num])) : ?>
+ <a href="{numNom::<?=$info?>}" title="Voir la fiche du nom"><?=$info?></a>
+ <?php else : ?>
+ <?=$info?>
+ <?php endif; ?>
+ </td>
+ <?php endforeach; ?>
+ </tr>
+ <?php endforeach; ?>
+ </tbody>
+</table>
\ No newline at end of file
Index: v1.1/trunk/scripts/modules/tests/Tests.php
===================================================================
--- v1.1/trunk/scripts/modules/tests/Tests.php (revision 0)
+++ v1.1/trunk/scripts/modules/tests/Tests.php (revision 287)
@@ -0,0 +1,1848 @@
+<?php
+// Encodage : UTF-8
+// +-------------------------------------------------------------------------------------------------------------------+
+/**
+* Tests de référentiels de nomenclature et taxonomie
+*
+* Description : classe permettant de tester les référentiels selon le manuel technique
+* Utilisation : php script.php tests -p bdnff -a tout
+*
+//Auteur original :
+* @author Jean-Pascal MILCENT <jpm@tela-botanica.org>
+* @copyright Tela-Botanica 1999-2010
+* @link http://www.tela-botanica.org/wikini/RTaxMethodo/wakka.php?wiki=MaNuel
+* @licence GPL v3 & CeCILL v2
+* @version $Id$
+*/
+// +-------------------------------------------------------------------------------------------------------------------+
+// TODO : supprimer l'utilisation du paramêtres 'p' et chercher les infos depuis la bdd
+class Tests extends ScriptCommande {
+
+ const SCRIPT_NOM = 'tests';
+ const MANUEL_VERSION = '4.3';
+
+ private $projet = null;
+ private $traitement = null;
+
+ private $manuel = null;
+ private $manuel_nom = null;
+ private $manuel_chemin = null;
+ private $tests = null;
+ private $colonnes = null;
+ private $analyses = null;
+ private $noms = null;
+
+ private $resultatDao = null;
+ private $traitementDao = null;
+ private $tableStructureDao = null;
+ private $referentielDao = null;
+
+ public function executer() {
+ $this->manuel_nom = 'mtpr_v'.str_replace('.', '_', self::MANUEL_VERSION).'.pdf';
+ $this->manuel_chemin = Config::get('chemin_appli').DS.'..'.DS.'configurations'.DS;
+
+ $manuel_config_nom = 'referentiel_v'.self::MANUEL_VERSION.'.ini';
+ $this->manuel = parse_ini_file($this->manuel_chemin.$manuel_config_nom);
+
+ $this->definirConstantes();
+ $this->tests = parse_ini_file($this->getModuleChemin().DS.'configurations'.DS.'tests.ini', true);
+
+ $this->resultatDao = new ResultatDao();
+ $this->traitementDao = new TraitementDao();
+ Debug::printr('Dans le script test');
+ // Récupération du dernier traitement demandé
+ $this->traitement = $this->traitementDao->getDernierTraitement('tout', self::SCRIPT_NOM);
+ if (isset($this->traitement)) {
+ $this->projet = $this->traitement['referentiel_code']; // Récupération du nom de projet
+ Debug::printr($this->traitement);
+ // Écriture de la date de début du traitement
+ Debug::printr('Debute:'.$this->traitementDao->debuterTraitement($this->traitement['id_traitement']));
+
+ // Nettoyage des traitements obsolètes
+ $traitements_obsoletes = $this->traitementDao->getTraitementsObsoletes($this->projet, self::SCRIPT_NOM);
+ if (isset($traitements_obsoletes)) {
+ Debug::printr('Supp. obsoletes:'.$this->traitementDao->supprimer($traitements_obsoletes));
+ }
+
+ $this->tableStructureDao = new TableStructureDao();
+ $this->referentielDao = new ReferentielDao();
+
+ // Lancement du test demandé
+ $cmd = $this->getParam('a');
+ switch ($cmd) {
+ case 'tout' :
+ Debug::printr('Départ lancement test:');
+ $this->recupererDonnees();
+ $this->lancerTestsAuto();
+ break;
+ case 'test11' :
+ $this->recupererDonnees();
+ Debug::printr('Départ lancement test 11 :');
+ $this->testerExitenceTaxonSuperieurTransmis();
+ break;
+ default :
+ $this->traiterErreur('Erreur : la commande "%s" n\'existe pas!', array($cmd));
+ }
+ // Écriture de la date de fin du traitement
+ Debug::printr('Termine:'.$this->traitementDao->terminerTraitement($this->traitement['id_traitement']));
+ } else {
+ Debug::printr("Aucun dernier traitement trouvé pour le script '".self::SCRIPT_NOM."' !");
+ }
+ }
+
+ /**
+ * Cette méthode définie des constantes qui peuvent ensuite être utilisée dans les fichier ini.
+ * Surtout utile pour le fichier tests.ini
+ */
+ private function definirConstantes() {
+ define('RANG_GENRE', $this->manuel['rang_genre']);
+ define('RANG_SP', $this->manuel['rang_sp']);
+ date_default_timezone_set('Europe/Berlin');
+ define('ANNEE_EN_COURS', date('Y'));
+ define('ANNEE_MINIMUM', 1753);
+ }
+
+ public function recupererDonnees() {
+ // Récupération des données à tester
+ $this->colonnes = $this->tableStructureDao->getColonnes($this->projet);
+ $this->analyses = $this->tableStructureDao->getAnalyse($this->projet);
+ $this->noms = $this->referentielDao->getTout($this->projet);
+ Debug::printr('Nbre noms :'.count($this->noms));
+ }
+
+ public function lancerTestsAuto() {
+ $resultats = array();
+
+ // Lancement des tests unitaires
+ Debug::printr('Lancement des tests unitaires');
+ $tests_numeros = array_keys($this->tests);
+ Debug::printr($tests_numeros);
+ foreach ($tests_numeros as &$numero) {
+ $info = $this->getInfosTest($numero);
+
+ if ($numero < 5) {
+ // Tests spéciaux vérifiant la structure de la table
+ $resultats[] = $this->lancerTestUnitaire($info);
+ } else if ($numero >= 5 && $this->verifierResultats($resultats)) {
+ // Si la structure est bonne nous lançons les autres tests
+ $this->lancerTestUnitaire($info);
+ }
+ }
+ }
+
+ private function getInfosTest($numero) {
+ $info = $this->tests[$numero];
+ $info['methode'] = 'tester'.implode('', explode(' ', ucwords($info['abr'])));
+ $info['numero'] = $numero;
+ $info['nom'] = '#'.$numero.' - '.$info['nom'];
+ $info['resultat'] = false;
+ return $info;
+ }
+
+ public function lancerTestUnitaire($info) {
+ Debug::printr($info['numero'].'::'.$info['methode']);
+ $methodeDeTest = $info['methode'];
+ $erreurs = $this->$methodeDeTest();
+
+ // Analyse des résultats
+ if (isset($erreurs['special'])) {
+ $info = $erreurs;
+ } else {
+ if (count($erreurs) > 0) {
+ $info['message']['entete'] = explode(',', $info['entete']);
+ if (isset($info['title'])) {
+ $info['message']['title'] = explode(';', $info['title']);
+ }
+ $info['message']['lignes'] = $erreurs;
+ unset($erreurs);
+ } else {
+ $info['resultat'] = true;
+ }
+ }
+
+ $this->traiterResultatTest($info);
+
+ if ($info['numero'] < 5) {
+ return ($info['resultat'] ? '1' : '0');
+ }
+ }
+
+ private function verifierResultats(&$resultats) {
+ $ok = true;
+ foreach ($resultats as $resultat) {
+ if ($resultat == '0') {
+ $ok = false;
+ break;
+ }
+ }
+ return $ok;
+ }
+
+ private function traiterResultatTest($info) {
+ if (isset($info['message'])) {
+ if (is_array($info['message'])) {
+ $erreurs_nbre = count($info['message']['lignes']);
+ $erreurs_max = 1000;
+ if ($erreurs_nbre > $erreurs_max) {
+ $info['message']['lignes'] = array_slice($info['message']['lignes'], 0, $erreurs_max);
+ $info['erreur'] = "$erreurs_nbre erreurs ont été détectées seules les $erreurs_max premières sont affichées";
+ }
+ $info['message'] = $this->getVue('tests/squelettes/message_table', $info);
+ } else {
+ $info['message'] = $this->getVue('tests/squelettes/message_p', $info);
+ }
+ }
+ Debug::printr($this->resultatDao->ajouter($this->traitement['id_traitement'], $info));
+ $info = null;
+ }
+
+ //+--------------------------------------------------------------------------------------------------------------+//
+ // TESTS
+
+ /**
+ * Test #01
+ */
+ private function testerNombreDeChamps() {
+ $info = $this->getInfosTest(1);
+ $info['special'] = true;
+
+ $nbre_colonnes = count($this->colonnes);
+ $info['message'] = $nbre_colonnes;
+ if ($nbre_colonnes >= 35) {
+ $info['resultat'] = true;
+ }
+
+ return $info;
+ }
+
+ /**
+ * Test #02
+ */
+ private function testerNomDesChamps() {
+ $info = $this->getInfosTest(2);
+ $info['special'] = true;
+
+ $champs_attendus = explode(',', $this->manuel['champs']);
+ $champs_presents = array();
+ foreach ($this->colonnes as &$colonne) {
+ $champs_presents[$colonne['Field']] = $colonne;
+ }
+
+ $ok = true;
+ $champs_manquant = array();
+ foreach ($champs_attendus as &$champ_attendu) {
+ if (!isset($champs_presents[$champ_attendu])) {
+ $champs_manquant[] = $champ_attendu;
+ $ok = false;
+ }
+ }
+
+ $info['resultat'] = $ok;
+ if (!$ok) {
+ $info['message'] = sprintf($info['message'], implode(', ', $champs_manquant));
+ }
+
+ return $info;
+ }
+
+ /**
+ * Test #03
+ */
+ private function testerTypeDesChamps() {
+ $champs_attendus = explode(',', $this->manuel['champs_type']);
+ $champs_presents = array();
+ foreach ($this->colonnes as &$colonne) {
+ $champs_presents[$colonne['Field']] = $colonne['Type'];
+ }
+
+ // Recercherche des erreurs
+ $champs_erreur = array();
+ foreach ($champs_attendus as &$champ_attendu) {
+ list($champ_attendu_nom, $champ_attendu_type_taille) = explode('=', trim($champ_attendu));
+ list($champ_attendu_type, $champ_attendu_taille) = explode('|', trim($champ_attendu_type_taille));
+
+ if (isset($champs_presents[$champ_attendu_nom])) {
+ $champs_present_type = $champs_presents[$champ_attendu_nom];
+
+ if (($champ_attendu_type == 'VARCHAR' && strstr($champs_present_type, 'varchar') === false)
+ || ($champ_attendu_type == 'TEXT' && strstr($champs_present_type, 'text') === false)
+ || ($champ_attendu_type == 'INT' && strstr($champs_present_type, 'int') === false)
+ || ($champ_attendu_type == 'BOOL' && preg_match('/(?:bool|boolean|tinyint\(1\))/i', $champs_present_type) === false)) {
+ $champs_erreur[] = array($champ_attendu, $champ_attendu_type, $champs_present_type);
+ }
+ }
+ }
+ return $champs_erreur;
+ }
+
+ /**
+ * Test #04
+ */
+ private function testerNumNomClePrimaire() {
+ $info = $this->getInfosTest(4);
+ $info['special'] = true;
+
+ foreach ($this->colonnes as &$colonne) {
+ if ($colonne['Field'] == 'num_nom' && $colonne['Key'] == 'PRI') {
+ $info['resultat'] = true;
+ break;
+ }
+ }
+ return $info;
+ }
+
+
+ /**
+ * Test #05
+ */
+ private function testerTailleDesChamps() {
+ $tailles_champs_maxi = array();
+ foreach ($this->colonnes as &$colonne) {
+ if (preg_match('/^varchar\(([0-9]+)\)$/', $colonne['Type'], $match)) {
+ $tailles_champs_maxi[$colonne['Field']] = $match[1];
+ }
+ }
+
+ $tailles_trouvees = array();
+ foreach ($this->analyses as &$analyse) {
+ if (preg_match('/\.([^.]+)$/', $analyse['Field_name'], $match)) {
+ $tailles_trouvees[$match[1]] = $analyse['Max_length'];
+ }
+ }
+
+ $champs_erreur = array();
+ $champs_attendus = explode(',', $this->manuel['champs']);
+ foreach ($champs_attendus as &$champ_attendu) {
+ if (isset($tailles_champs_maxi[$champ_attendu]) && isset($tailles_trouvees[$champ_attendu])) {
+ if ($tailles_champs_maxi[$champ_attendu] == $tailles_trouvees[$champ_attendu]) {
+ $champs_erreur[] = array($champ_attendu, $tailles_champs_maxi[$champ_attendu], $tailles_trouvees[$champ_attendu]);
+ }
+ }
+ }
+ return $champs_erreur;
+ }
+
+ /**
+ * Test #06
+ */
+ private function testerNumNomSuperieurAZero() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['num_nom'] <= 0) {
+ $noms_erreur[] = array($nom['num_nom']);
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #07
+ */
+ private function testerNumNomRetenuSuperieurAZero() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['num_nom_retenu'] != '' && $nom['num_nom_retenu'] <= 0) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['num_nom_retenu']);
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #08
+ */
+ private function testerExistenceNumNomRetenu() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['num_nom_retenu'] != '' && $nom['num_nom_retenu'] != $nom['num_nom']) {
+ if ($nom['num_nom_retenu'] != 0 && !isset($this->noms[$nom['num_nom_retenu']])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['num_nom_retenu'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #09
+ */
+ private function testerTransmissionNumNomRetenu() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['num_nom_retenu'] != $nom['num_nom'] && $nom['exclure_taxref'] == 0) {
+ if ($nom['num_nom_retenu'] != 0 && isset($this->noms[$nom['num_nom_retenu']])) {
+ if ($this->noms[$nom['num_nom_retenu']]['exclure_taxref'] != 0) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['num_nom_retenu'], $this->noms[$nom['num_nom_retenu']]['exclure_taxref']);
+ }
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #10
+ */
+ private function testerSynonymeNumNomRetenu() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['num_nom_retenu'] != $nom['num_nom']) {
+ if ($nom['num_nom_retenu'] != 0 && isset($this->noms[$nom['num_nom_retenu']])) {
+ if ($this->noms[$nom['num_nom_retenu']]['num_nom'] != $this->noms[$nom['num_nom_retenu']]['num_nom_retenu']) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['num_nom_retenu'], $this->noms[$nom['num_nom_retenu']]['num_nom_retenu']);
+ }
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+
+ /**
+ * Test #11
+ */
+ private function testerNumTaxSupEgalZeroUnique() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if (preg_match('/^0$/', $nom['num_tax_sup'])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['num_tax_sup']);
+ }
+ }
+ // Ce test est spécial car le nombre de noms en erreurs doit être supérieur à 1 et non à 0 pour être KO.
+ if (count($noms_erreur) == 1) {
+ $noms_erreur = array();
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #12
+ */
+ private function testerTaxSupPourTaxon() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['num_nom_retenu'] != $nom['num_nom'] && $nom['num_tax_sup'] != '') {
+ $noms_erreur[] = array($nom['num_nom'], $nom['num_tax_sup'], $nom['exclure_taxref']);
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #13
+ */
+ private function testerExistenceTaxonSuperieur() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['num_nom_retenu'] == $nom['num_nom']) {
+ if ($nom['num_tax_sup'] != 0 && !isset($this->noms[$nom['num_tax_sup']])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['num_tax_sup'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #14
+ */
+ private function testerTransmissionTaxonSuperieur() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['num_nom_retenu'] == $nom['num_nom'] && $nom['exclure_taxref'] == 0) {
+ if ($nom['num_tax_sup'] != 0 && isset($this->noms[$nom['num_tax_sup']])) {
+ if ($this->noms[$nom['num_tax_sup']]['exclure_taxref'] != 0) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['num_tax_sup'], $this->noms[$nom['num_tax_sup']]['exclure_taxref']);
+ }
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+
+ /**
+ * Test #15
+ */
+ private function testerClassificationRang() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['num_nom_retenu'] == $nom['num_nom']) {
+ if (isset($this->noms[$nom['num_tax_sup']])) {
+ $nom_sup = $this->noms[$nom['num_tax_sup']];
+ if ($nom_sup['rang'] >= $nom['rang']) {
+ // Prise en compte de l'exception des clades
+ if (! ($nom_sup['rang'] == 70 && $nom['rang'] == 70)) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['rang'], $nom_sup['num_nom'], $nom_sup['rang'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #16
+ */
+ private function testerClassification() {
+ $noms_erreur = array();
+ $this->noms_ok = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['num_nom_retenu'] == $nom['num_nom']) {
+ if (isset($this->noms_ok[$nom['num_tax_sup']])) {
+ $this->noms_ok[$nom['num_nom']] = true;
+ } else {
+ $this->detection_boucle_infini = array();
+ $classif_ok = $this->remonterClassif($nom);
+ unset($this->detection_boucle_infini);
+
+ if ($classif_ok === true) {
+ $this->noms_ok[$nom['num_nom']] = $classif_ok;
+ } else {
+ $noms_erreur[] = array($nom['num_nom'], $classif_ok, $nom['exclure_taxref']);
+ }
+ }
+ }
+ }
+ unset($this->noms_ok);
+ return $noms_erreur;
+ }
+
+ private function remonterClassif(&$nom) {
+ $this->detection_boucle_infini[$nom['num_nom']] = true;
+ if (preg_match('/^[0-9]*$/', $nom['num_tax_sup'])) {
+ if (isset($this->noms_ok[$nom['num_tax_sup']])) {
+ $this->noms_ok[$nom['num_nom']] = true;
+ return true;
+ } else if (!isset($this->noms[$nom['num_tax_sup']]) && $nom['num_tax_sup'] == '0') {
+ $this->noms_ok[$nom['num_nom']] = true;
+ return true;
+ } else if (!isset($this->noms[$nom['num_tax_sup']]) && $nom['num_tax_sup'] != '0') {
+ return 'Hiérarchie avec le taxon #'.$nom['num_nom'].' ayant un taxon superieur #'.$nom['num_tax_sup'].' inexistant';
+ } else if (isset($this->detection_boucle_infini[$nom['num_tax_sup']])) {
+ return 'Boucle infinie pour le taxon #'.$nom['num_tax_sup'];
+ } else {
+ $retour = $this->remonterClassif($this->noms[$nom['num_tax_sup']]);
+ if ($retour === true) {
+ $this->noms_ok[$nom['num_tax_sup']] = true;
+ }
+ return $retour;
+ }
+ } else {
+ return 'Valeur num_tax_sup incorrecte : '.$nom['num_tax_sup'];
+ }
+ }
+
+ /**
+ * Test #17
+ */
+ private function testerRang() {
+ $rangs = array_flip(explode(',', $this->manuel['rangs']));
+
+ // Réalisation du test
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if (!isset($rangs[$nom['rang']])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['rang'], $nom['exclure_taxref']);
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #18
+ */
+ private function testerNomCompletSupraGenerique() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['rang'] < $this->manuel['rang_genre']) {
+ $suffixe_plte_cultivee = $this->construireSuffixeNomPltCultivee($nom);
+ $nom_sci_ideal = $this->formaterStyleNomGenre($nom['nom_supra_generique']);
+ $nom_sci_ideal .= ($suffixe_plte_cultivee != '' ? ' '.$suffixe_plte_cultivee : '');
+ if ($nom['nom_sci'] != $nom_sci_ideal) {
+ $nom_sci_traite = $this->repererEspace($nom['nom_sci']);
+ $noms_erreur[] = array($nom['num_nom'], $nom_sci_traite, $nom_sci_ideal, $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #19
+ */
+ private function testerNomCompletGenre() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['rang'] == $this->manuel['rang_genre']) {
+ $suffixe_plte_cultivee = $this->construireSuffixeNomPltCultivee($nom);
+ $nom_sci_ideal = $this->formaterStyleNomGenre($nom['genre']);
+ $nom_sci_ideal .= ($suffixe_plte_cultivee != '' ? ' '.$suffixe_plte_cultivee : '');
+ if ($nom['nom_sci'] != $nom_sci_ideal) {
+ $nom_sci_traite = $this->repererEspace($nom['nom_sci']);
+ $noms_erreur[] = array($nom['num_nom'], $nom_sci_traite, $nom_sci_ideal, $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #20
+ */
+ private function testerNomCompletInfraGenre() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['rang'] > $this->manuel['rang_genre'] && $nom['rang'] < $this->manuel['rang_sp']) {
+ $suffixe_plte_cultivee = $this->construireSuffixeNomPltCultivee($nom);
+ $nom_sci_ideal = '';
+ if ($nom['type_epithete'] == 'agg.') {
+ $nom_sci_ideal = $this->formaterStyleNomGenre($nom['genre']).' '.
+ $this->formaterStyleNomGenre($nom['epithete_infra_generique']).' '.
+ $nom['type_epithete'];
+ } else {
+ $nom_sci_ideal = $this->formaterStyleNomGenre($nom['genre']).' '.
+ $nom['type_epithete'].' '.
+ $this->formaterStyleNomGenre($nom['epithete_infra_generique']);
+ }
+ $nom_sci_ideal .= ($suffixe_plte_cultivee != '' ? ' '.$suffixe_plte_cultivee : '');
+ if ($nom['nom_sci'] != $nom_sci_ideal) {
+ $nom_sci_traite = $this->repererEspace($nom['nom_sci']);
+ $noms_erreur[] = array($nom['num_nom'], $nom_sci_traite, $nom_sci_ideal, $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #21
+ */
+ private function testerNomCompletEspece() {
+ $noms_erreur = array();
+ $erreur = '';
+ foreach ($this->noms as &$nom) {
+ if ($nom['rang'] == $this->manuel['rang_sp']) {
+ $suffixe_plte_cultivee = $this->construireSuffixeNomPltCultivee($nom);
+ $nom_sci_ideal = $this->formaterStyleNomGenre($nom['genre']).' ';
+ if (strstr($nom['nom_sci'] , ' x ') != false) {
+ list($nom_sci_ideal, $erreur) = $this->formaterStyleEpitheteSpHybride($nom_sci_ideal, $nom['epithete_sp']);
+ } else {
+ $nom_sci_ideal .= utf8_encode(strtolower(utf8_decode($nom['epithete_sp'])));
+ }
+ $nom_sci_ideal .= ($suffixe_plte_cultivee != '' ? ' '.$suffixe_plte_cultivee : '');
+ $nom_sci_ideal = trim($nom_sci_ideal);
+ if ($nom['nom_sci'] != $nom_sci_ideal) {
+ $nom_sci_traite = $this->repererEspace($nom['nom_sci']);
+ $noms_erreur[] = array($nom['num_nom'], $nom_sci_traite, $nom_sci_ideal, $erreur, $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #22
+ */
+ private function testerNomCompletInfraSpecifique() {
+ $noms_erreur = array();
+ $erreur = null;
+ foreach ($this->noms as &$nom) {
+ if ($nom['rang'] > $this->manuel['rang_sp']) {
+ $nom_sci_ideal = $this->formaterStyleNomGenre($nom['genre']).' '.
+ utf8_encode(strtolower(utf8_decode($nom['epithete_sp']))).' '.
+ utf8_encode(strtolower(utf8_decode($nom['type_epithete']))).' ';
+ $suffixe_plte_cultivee = $this->construireSuffixeNomPltCultivee($nom);
+ if (strstr($nom['nom_sci'] , ' x ') != false) {
+ list($nom_sci_ideal, $erreur) = $this->formaterStyleEpitheteInfraSpHybride($nom_sci_ideal, $nom['epithete_infra_sp'], $nom['genre']);
+ } else {
+ $nom_sci_ideal .= utf8_encode(strtolower(utf8_decode($nom['epithete_infra_sp'])));
+ }
+ $nom_sci_ideal = trim($nom_sci_ideal);
+ $nom_sci_ideal .= ($suffixe_plte_cultivee != '' ? ' '.$suffixe_plte_cultivee : '');
+ if ($nom['nom_sci'] != $nom_sci_ideal) {
+ $nom_sci_traite = $this->repererEspace($nom['nom_sci']);
+ $noms_erreur[] = array($nom['num_nom'], $nom_sci_traite, $nom_sci_ideal, $erreur, $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #23
+ */
+ private function testerNomSupraGeneriqueEspaces() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['nom_supra_generique'] != '') {
+ if (preg_match('/(?:^\s+(?!:\s+)|(?!:\s+)\s+$)/', $nom['nom_supra_generique'])) {
+ $nom_supra_generique_traite = $this->repererEspace($nom['nom_supra_generique']);
+ $noms_erreur[] = array($nom['num_nom'], $nom_supra_generique_traite, $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #24
+ */
+ private function testerNomSupraGeneriqueSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['nom_supra_generique'] != '') {
+ if (!preg_match('/^[A-ZÄËḦÏÖÜẄẌŸ][-a-zäëḧïöẗüẅẍÿ]+$/', $nom['nom_supra_generique'])) {
+ $nom_supra_generique_traite = $this->repererEspace($nom['nom_supra_generique']);
+ $noms_erreur[] = array($nom['num_nom'], $nom_supra_generique_traite, $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #25
+ */
+ private function testerNomSupraGeneriqueRang() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['nom_supra_generique'] != '') {
+ if ($nom['rang'] >= $this->manuel['rang_genre']) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['nom_sci'], $nom['rang'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #26
+ */
+ private function testerGenreEspaces() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['genre'] != '') {
+ if (preg_match('/(?:^\s+(?!:\s+)|(?!:\s+)\s{2,}(?!:\s+)|(?!:\s+)\s+$)/', $nom['genre'])) {
+ $nom_traite = $this->repererEspace($nom['genre']);
+ $noms_erreur[] = array($nom['num_nom'], $nom_traite, $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #27
+ */
+ private function testerGenreSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['genre'] != '') {
+ $mots = explode(' ', $nom['genre']);
+ foreach ($mots as $mot) {
+ if (!(preg_match('/^[+x]$/', $mot) || $this->verifierEpitheteGenre($mot))) {
+ $nom_traite = $this->repererEspace($nom['genre']);
+ $noms_erreur[] = array($nom['num_nom'], $nom_traite, $nom['exclure_taxref']);
+ break;
+ }
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #28
+ */
+ private function testerGenreRang() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['genre'] != '') {
+ if ($nom['rang'] < $this->manuel['rang_genre']) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['nom_sci'], $nom['rang'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #29
+ */
+ private function testerEpitheteInfraGeneriqueSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['epithete_infra_generique'] != '') {
+ if (!preg_match('/^[A-ZÄËḦÏÖÜẄẌŸ][-a-zäëḧïöẗüẅẍÿ]+/', $nom['epithete_infra_generique'])) {
+ $epithete_traite = $this->repererEspace($nom['epithete_infra_generique']);
+ $noms_erreur[] = array($nom['num_nom'], $epithete_traite, $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #30
+ */
+ private function testerEpitheteInfraGeneriqueRang() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['epithete_infra_generique'] != '') {
+ if ($nom['rang'] <= $this->manuel['rang_genre'] || $nom['rang'] >= $this->manuel['rang_sp']) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['nom_sci'], $nom['rang'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #31
+ */
+ private function testerEpitheteInfraGeneriqueEspaces() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['epithete_infra_generique'] != '') {
+ if (preg_match('/(?:^\s+(?!:\s+)|(?!:\s+)\s{2,}(?!:\s+)|(?!:\s+)\s+$)/', $nom['epithete_infra_generique'])) {
+ $epithete_traite = $this->repererEspace($nom['epithete_infra_generique']);
+ $noms_erreur[] = array($nom['num_nom'], $epithete_traite, $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #32
+ */
+ private function testerEpitheteSpEspaces() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['epithete_sp'] != '') {
+ if (preg_match('/(?:^\s+(?!:\s+)|(?!:\s+)\s{2,}(?!:\s+)|(?!:\s+)\s+$)/', $nom['epithete_sp'])) {
+ $epithete_traite = $this->repererEspace($nom['epithete_sp']);
+ $noms_erreur[] = array($nom['num_nom'], $epithete_traite, $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #33
+ */
+ private function testerEpitheteSpSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['epithete_sp'] != '') {
+ $formule_hybridite = (strpos($nom['epithete_sp'], ' x ') !== false);
+ $mots = explode(' ', $nom['epithete_sp']);
+ foreach ($mots as $mot) {
+ // TODO: créer un test qui vérifie la formule d'hybridité en la reconstruisant à partir des parents
+ // afin que seuls des formules valides parviennent à la fonction
+ if (!(preg_match('/^[+x]$/', $mot) || $this->verifierEpitheteSp($mot) ||
+ ($formule_hybridite && $this->verifierEpitheteGenre($mot) && !$this->verifierEstAbbreviationInfraSp($mot)))) {
+ $epithete_traite = $this->repererEspace($nom['epithete_sp']);
+ $noms_erreur[] = array($nom['num_nom'], $epithete_traite, $nom['exclure_taxref']);
+ break;
+ }
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #34
+ */
+ private function testerEpitheteSpRang() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['epithete_sp'] != '') {
+ if ($nom['rang'] < $this->manuel['rang_sp']) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['nom_sci'], $nom['rang'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #35
+ */
+ private function testerTypeEpitheteEspaces() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['type_epithete'] != '') {
+ if (preg_match('/\s+/', $nom['type_epithete'])) {
+ $valeur_traitee = $this->repererEspace($nom['epithete_sp']);
+ $noms_erreur[] = array($nom['num_nom'], $valeur_traitee, $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #36
+ */
+ private function testerTypeEpitheteSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['type_epithete'] != '') {
+ if (!$this->verifierTypeEpithete($nom['type_epithete'])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['type_epithete'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #37
+ */
+ private function testerTypeEpitheteHybridite() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['type_epithete'] != '') {
+ if (preg_match('/^(?:n-|notho-)/', $nom['type_epithete'])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['type_epithete'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #38
+ */
+ private function testerEpitheteInfraSpEspaces() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['epithete_infra_sp'] != '') {
+ if (preg_match('/(?:^\s+(?!:\s+)|(?!:\s+)\s{2,}(?!:\s+)|(?!:\s+)\s+$)/', $nom['epithete_infra_sp'])) {
+ $epithete_traite = $this->repererEspace($nom['epithete_infra_sp']);
+ $noms_erreur[] = array($nom['num_nom'], $epithete_traite, $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #39
+ */
+ private function testerEpitheteInfraSpSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['epithete_infra_sp'] != '') {
+ $mots = explode(' ', $nom['epithete_infra_sp']);
+ $hybride_contient_abbreviation_infra_sp = false;
+ $formule_hybridite = (strpos($nom['epithete_infra_sp'], ' x ') !== false);
+ foreach ($mots as $mot) {
+ if($formule_hybridite && $this->verifierEstAbbreviationInfraSp($mot)) {
+ $hybride_contient_abbreviation_infra_sp = true;
+ }
+ $formule_hybridite_valide = $this->verifierEpitheteGenre($mot) || $this->verifierEpitheteSp($mot) || $hybride_contient_abbreviation_infra_sp;
+ if (!(preg_match('/^[+x]$/', $mot) || $this->verifierTypeEpithete($mot) || $this->verifierEpitheteSp($mot) ||
+ $formule_hybridite_valide)) {
+ $epithete_traite = $this->repererEspace($nom['epithete_infra_sp']);
+ $noms_erreur[] = array($nom['num_nom'], $epithete_traite, $nom['exclure_taxref']);
+ break;
+ }
+ }
+
+ if($formule_hybridite && !$hybride_contient_abbreviation_infra_sp) {
+ $epithete_traite = $this->repererEspace($nom['epithete_infra_sp']);
+ $noms_erreur[] = array($nom['num_nom'], $epithete_traite, $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #40
+ */
+ private function testerEpitheteInfraSpRang() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['epithete_infra_sp'] != '') {
+ if ($nom['rang'] < $this->manuel['rang_sp']) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['nom_sci'], $nom['rang'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #41
+ */
+ private function testerGroupeCultivarSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['cultivar_groupe'] != '') {
+ if (!$this->verifierEpitheteGroupeCultivar($nom['cultivar_groupe'])) {
+ $epithete_traite = $this->repererEspace($nom['cultivar_groupe']);
+ $noms_erreur[] = array($nom['num_nom'], $epithete_traite, $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #42
+ */
+ private function testerGroupeCultivarRang() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['cultivar_groupe'] != '') {
+ if ($nom['rang'] < $this->manuel['rang_genre']) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['nom_sci'], $nom['rang'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #43
+ */
+ private function testerCultivarSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['cultivar'] != '') {
+ if (!$this->verifierEpitheteCultivar($nom['cultivar'])) {
+ $epithete_traite = $this->repererEspace($nom['cultivar']);
+ $noms_erreur[] = array($nom['num_nom'], $epithete_traite, $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #44
+ */
+ private function testerCultivarRang() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['cultivar'] != '') {
+ if ($nom['rang'] < $this->manuel['rang_genre']) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['nom_sci'], $nom['rang'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #45
+ */
+ private function testerNomCommercialSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['nom_commercial'] != '') {
+ if (!$this->verifierNomCommercial($nom['nom_commercial'])) {
+ $epithete_traite = $this->repererEspace($nom['nom_commercial']);
+ $noms_erreur[] = array($nom['num_nom'], $epithete_traite, $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #46
+ */
+ private function testerNomCommercialPresenceCultivar() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ((isset($nom['nom_commercial']) && $nom['nom_commercial'] != '') && ($nom['cultivar'] == '' && $nom['cultivar_groupe'] == '')) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['nom_sci'], $nom['exclure_taxref']);
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #47
+ */
+ private function testerAuteurSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['auteur'] != '') {
+ if (!$this->verifierAuteur($nom['auteur'])) {
+ $intitule_traite = $this->repererEspace($nom['auteur']);
+ $noms_erreur[] = array($nom['num_nom'], $intitule_traite, $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #48
+ */
+ private function testerAnneeSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['annee'] != '') {
+ if (!$this->verifierAnnee($nom['annee'])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['annee'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #49
+ */
+ private function testerBiblioOrigineSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['biblio_origine'] != '') {
+ if (!$this->verifierBiblioOrigine($nom['biblio_origine'])) {
+ $biblio_traite = $this->repererEspace($nom['biblio_origine']);
+ $noms_erreur[] = array($nom['num_nom'], $biblio_traite, $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #50
+ */
+ private function testerHomonymieSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['homonyme'] != '') {
+ if (!$this->verifierBooleen($nom['homonyme'])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['homonyme'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #51
+ */
+ private function testerHomonymieExistence() {
+ $noms_homonymie = $this->classerNomsParNomComplet();
+
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['homonyme'] != '0' && $nom['homonyme'] != '') {
+ if ($noms_homonymie[$nom['nom_sci']] <= 1) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['nom_sci'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ $noms_homonymie = null;
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #52
+ */
+ private function testerBasionymeSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['basionyme'] != '') {
+ if (!$this->verifierNombre($nom['basionyme'])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['basionyme'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #53
+ */
+ private function testerBasionymeExistence() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['basionyme'] != '') {
+ if (!isset($this->noms[$nom['basionyme']])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['basionyme'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #54
+ */
+ private function testerSynonymeProparteSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['synonyme_proparte'] != '') {
+ if (!$this->verifierNombreSuite($nom['synonyme_proparte'])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['synonyme_proparte'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #55
+ */
+ private function testerSynonymeProparteExistence() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['synonyme_proparte'] != '') {
+ $num_nom_a_verifier = explode(',', $nom['synonyme_proparte']);
+ $num_nom_en_erreur = array();
+ foreach ($num_nom_a_verifier as $num_nom) {
+ if (!isset($this->noms[$num_nom])) {
+ $num_nom_en_erreur[] = $num_nom;
+ }
+ }
+ if (count($nbre_en_erreur) > 0) {
+ $noms_erreur[] = array($nom['num_nom'], implode(',', $num_nom_en_erreur), $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #56
+ */
+ private function testerSynonymeDouteuxSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['synonyme_douteux'] != '') {
+ if (!$this->verifierBooleen($nom['synonyme_douteux'])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['synonyme_douteux'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #57
+ */
+ private function testerSynonymeDouteuxNumNomRetenu() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['synonyme_douteux'] == 1 && !$this->verifierNombre($nom['num_nom_retenu'])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['synonyme_douteux'], $nom['exclure_taxref']);
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #58
+ */
+ private function testerSynonymeMalAppliqueSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['synonyme_mal_applique'] != '') {
+ if (!$this->verifierBooleen($nom['synonyme_mal_applique'])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['synonyme_mal_applique'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #59
+ */
+ private function testerSynonymeOrthographiqueSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as $nom) {
+ if ($nom['synonyme_orthographique'] != '') {
+ if (!$this->verifierNombre($nom['synonyme_orthographique'])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['synonyme_orthographique'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #60
+ */
+ private function testerSynonymeOrthographiqueExistence() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['synonyme_orthographique'] != '') {
+ if (!isset($this->noms[$nom['synonyme_orthographique']])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['synonyme_orthographique'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #61
+ */
+ private function testerHybrideParent01Syntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['hybride_parent_01'] != '') {
+ if (!$this->verifierNombre($nom['hybride_parent_01'])) {
+ $noms_erreur[] = array($nom['num_nom'], $this->repererEspace($nom['hybride_parent_01']), $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #62
+ */
+ private function testerHybrideParent01Existence() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['hybride_parent_01'] != '' && $nom['hybride_parent_01'] != '0') {
+ if (!isset($this->noms[$nom['hybride_parent_01']])) {
+ $noms_erreur[] = array($nom['num_nom'], $this->repererEspace($nom['hybride_parent_01']), $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #63
+ */
+ private function testerHybrideParent02Syntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['hybride_parent_02'] != '') {
+ if (!$this->verifierNombre($nom['hybride_parent_02'])) {
+ $noms_erreur[] = array($nom['num_nom'], $this->repererEspace($nom['hybride_parent_02']), $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #64
+ */
+ private function testerHybrideParent02Existence() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['hybride_parent_02'] != '') {
+ if (!isset($this->noms[$nom['hybride_parent_02']]) && $nom['hybride_parent_02'] != '0') {
+ $noms_erreur[] = array($nom['num_nom'], $this->repererEspace($nom['hybride_parent_02']), $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #65
+ */
+ private function testerPresenceSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['presence'] != '') {
+ if (!$this->verifierPresence($nom['presence'])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['presence'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #66
+ */
+ private function testerStatutOrigineSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['statut_origine'] != '') {
+ if (!$this->verifierStatutOrigine($nom['statut_origine'])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['statut_origine'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #67
+ */
+ private function testerStatutIntroductionSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['statut_introduction'] != '') {
+ if (!$this->verifierStatutIntroduction($nom['statut_introduction'])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['statut_introduction'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #68
+ */
+ private function testerStatutCultureSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['statut_culture'] != '') {
+ if (!$this->verifierStatutCulture($nom['statut_culture'])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['statut_culture'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #69
+ */
+ private function testerExclureTaxRefSyntaxe() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['exclure_taxref'] != '' && $nom['exclure_taxref'] != null) {
+ if (!preg_match('/^(?:0|1|9)$/', $nom['exclure_taxref'])) {
+ $noms_erreur[] = array($nom['num_nom'], $nom['exclure_taxref']);
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #70
+ */
+ private function testerNomFrancaisUnique() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ if ($nom['nom_francais'] != '' && $nom['nom_francais'] != null) {
+ if (strpbrk($nom['nom_francais'],',;')) {
+ $noms_erreur[] = array($nom['num_nom'], $this->repererCaracteresInvalidesNomFrancais($nom['nom_francais']));
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ /**
+ * Test #71
+ */
+ private function testerExclureTaxRefPresence() {
+ $noms_erreur = array();
+ foreach ($this->noms as &$nom) {
+ // On vérifie tous les noms retenu nom exclus de taxref de rang inférieur ou égal à l'espèce
+ if ($nom['num_nom_retenu'] == $nom['num_nom'] && $nom['exclure_taxref'] == 0 && $nom['rang'] >= 240) {
+ if($nom['presence'] == '' || !$this->verifierPresence($nom['presence'])) {
+ if(($nom['statut_origine'] == '' || !$this->verifierStatutOrigine($nom['statut_origine'])) &&
+ ($nom['statut_introduction'] == '' || !$this->verifierStatutIntroduction($nom['statut_introduction'])) &&
+ ($nom['statut_culture'] == '' || !$this->verifierStatutCulture($nom['statut_culture'])))
+ {
+ $noms_erreur[] = array($nom['num_nom'], $nom['nom_sci'],
+ $this->mettreEnValeurAvertissement($nom['presence']),
+ $this->mettreEnValeurAvertissement($nom['statut_origine']),
+ $this->mettreEnValeurAvertissement($nom['statut_introduction']),
+ $this->mettreEnValeurAvertissement($nom['statut_culture'])
+ );
+ } else {
+ //TODO: le comportement est identique mais il faudrait pouvoir afficher un avertissement
+ // si le champ présence n'est pas rempli mais que l'une des colonne de statut l'est
+ $noms_erreur[] = array($nom['num_nom'], $nom['nom_sci'],
+ $nom['presence'],
+ $this->mettreEnValeurAvertissement($nom['statut_origine']),
+ $this->mettreEnValeurAvertissement($nom['statut_introduction']),
+ $this->mettreEnValeurAvertissement($nom['statut_culture'])
+ );
+ }
+ }
+ }
+ }
+ return $noms_erreur;
+ }
+
+ //+--------------------------------------------------------------------------------------------------------------+//
+ // MÉTHODES COMMUNES aux TESTS
+
+ private function verifierPresence(&$valeur) {
+ $codes = $this->manuel['codes_presence'];
+ $ok = $this->verifierStatuts($valeur, $codes);
+ return $ok;
+ }
+
+ private function verifierStatutOrigine(&$valeur) {
+ $codes = $this->manuel['codes_statuts_origine'];
+ $ok = $this->verifierStatuts($valeur, $codes);
+ return $ok;
+ }
+
+ private function verifierStatutIntroduction(&$valeur) {
+ $codes = $this->manuel['codes_statuts_introduction'];
+ $ok = $this->verifierStatuts($valeur, $codes);
+ return $ok;
+ }
+
+ private function verifierStatutCulture(&$valeur) {
+ $codes = $this->manuel['codes_statuts_culture'];
+ $ok = $this->verifierStatuts($valeur, $codes);
+ return $ok;
+ }
+
+ private function verifierStatuts(&$valeur, &$codes) {
+ $ok = true;
+ if (!preg_match("/^(?:|-|[$codes](?:-[A-Z])?)$/", $valeur)) {
+ $ok = false;
+ }
+ return $ok;
+ }
+
+ private function verifierBooleen(&$valeur) {
+ $ok = true;
+ if (!preg_match('/^1$/', $valeur)) {
+ $ok = false;
+ }
+ return $ok;
+ }
+
+ private function verifierNombre(&$valeur) {
+ $ok = true;
+ if (!preg_match('/^[0-9]+$/', $valeur)) {
+ $ok = false;
+ }
+ return $ok;
+ }
+
+ private function verifierNombreSuite(&$valeur) {
+ $ok = true;
+ if (!preg_match('/^(?:[0-9]+, ?)*[0-9]+$/', $valeur)) {
+ $ok = false;
+ }
+ return $ok;
+ }
+
+ private function verifierTypeEpithete(&$type) {
+ $ok = false;
+ $rejetes = $this->manuel['type_epithete_rejetes'];
+ if (preg_replace("/^(?:$rejetes)$/", '', $type) == '') {
+ $ok = false;
+ } else if (preg_match('/^[a-z][-a-z]*[.]?$/', $type)) {
+ $ok = true;
+ }
+ return $ok;
+ }
+
+ private function verifierBiblioOrigine(&$intitule) {
+ $ok = true;
+ if (preg_match('/(?:^\s+|\s{2,}|\s+$)/', $intitule)) {
+ $ok = false;// Contient des espaces en trop
+ } else if (!preg_match('/^(?:in [^;]+[;]|)[^,]+?(?:[,][^:]+|)(?:[:].+|)$/', $intitule)) {
+ $ok = false;
+ } else if (preg_match('/(?:(?:^|[,:])\s*(?:[:,]|$))/', $intitule)) {
+ $ok = false;// Contient une mauvaise suite de caractères
+ }
+ return $ok;
+ }
+
+ private function verifierAnnee(&$annee) {
+ $ok = true;
+ if (!preg_match('/^[0-9]{4}$/', $annee)) {
+ $ok = false;
+ } else if ($annee < ANNEE_MINIMUM) {
+ $ok = false;
+ } else if ($annee > ANNEE_EN_COURS) {
+ $ok = false;
+ }
+ return $ok;
+ }
+
+ private function verifierAuteur(&$intitule) {
+ $ok = true;
+ $acceptes = $this->manuel['auteur_acceptes'];
+ if (!preg_match("/^(?:$acceptes)$/", $intitule)) {
+ if (preg_match('/(?:^\s+|\s{2,}|\s+$)/', $intitule)) {
+ $ok = false;// Contient des espaces en trop
+ } else {
+ $mots_rejetes = $this->manuel['auteur_mots_rejetes'];
+ $mots = explode(' ', $intitule);
+ foreach ($mots as $position => $mot) {
+ if (preg_match("/^(?:$mots_rejetes)$/i", $mot)) {
+ $ok = false;// Mot rejeté
+ } else if (preg_match("/^(?:(?:\p{L}|[.'\(\),-])+|[&])$/u", $mot)) {
+ continue;// Mot de l'intitulé auteur
+ } else {
+ $ok = false;
+ }
+ }
+ }
+ }
+ return $ok;
+ }
+
+ private function verifierNomCommercial(&$epithete) {
+ $ok = false;
+ if (preg_match("/^[[:upper:][:punct:][:digit:][:space:]]+$/", $epithete)) {
+ $ok = true;
+ }
+ return $ok;
+ }
+
+ private function verifierEpitheteCultivar(&$epithete) {
+ $ok = true;
+ $acceptes = $this->manuel['cultivar_acceptes'];
+ if (!preg_match("/^(?:$acceptes)$/", $epithete)) {
+ if (preg_match('/(?:^\s+|\s{2,}|\s+$)/', $epithete)) {
+ $ok = false;// Contient des espaces en trop
+ } else {
+ $mots_rejetes = $this->manuel['cultivar_mots_rejetes'];
+ $mots_mineurs = $this->manuel['mots_mineurs'];
+ $mots = explode(' ', $epithete);
+ foreach ($mots as $position => $mot) {
+ if (preg_match("/^(?:$mots_rejetes)$/i", $mot)) {
+ $ok = false;// Mot rejeté
+ } else if ($position > 0 && preg_match("/^(?:$mots_mineurs)$/", $mot)) {
+ continue;// Mot mineur en minuscule qui n'est pas en 1ère position
+ } else {
+ $mots_tiret = explode('-', $mot);
+ foreach ($mots_tiret as $position_tiret => $mot_tiret) {
+ if ($position_tiret > 0 && preg_match("/^(?:$mots_mineurs)$/", $mot_tiret)) {
+ continue;// Mot-tiret mineur en minuscule qui n'est pas en 1ère position
+ } else if (preg_match('/^[[:upper:]][[:lower:]]+$/', $mot_tiret)) {
+ continue;//Mot (ou 'mot-tiret') avec lettre initiale majuscule
+ } else if ($position_tiret == count($mots_tiret) && preg_match('/^[:upper:][:lower:]+[:punct:]?$/', $mot_tiret)) {
+ continue;//Dernier mot (ou 'mot-tiret') avec lettre initiale majuscule, suivi d'un éventuel signe de ponctuation
+ } else {
+ $ok = false;
+ }
+ }
+ }
+ }
+ }
+ }
+ return $ok;
+ }
+
+ private function verifierEpitheteGroupeCultivar(&$epithete) {
+ $ok = true;
+ $acceptes = $this->manuel['cultivar_gp_acceptes'];
+ if (!preg_match("/^(?:$acceptes)$/", $epithete)) {
+ if (preg_match('/(?:^\s+|\s{2,}|\s+$)/', $epithete)) {
+ $ok = false;// Contient des espaces en trop
+ } else {
+ $mots_acceptes = $this->manuel['cultivar_gp_mots_acceptes'];
+ $mots_rejetes = $this->manuel['cultivar_gp_mots_rejetes'];
+ $mots_mineurs = $this->manuel['mots_mineurs'];
+ $mots = explode(' ', $epithete);
+ foreach ($mots as $position => $mot) {
+ if (preg_match("/^(?:$mots_acceptes)$/i", $mot)) {
+ continue;// Mot accepté
+ } else if (preg_match("/^(?:$mots_rejetes)$/i", $mot)) {
+ $ok = false;// Mot rejeté
+ } else if ($position > 0 && preg_match("/^(?:$mots_mineurs)$/", $mot)) {
+ continue;// Mot mineur en minuscule qui n'est pas en 1ère position
+ } else {
+ $mots_tiret = explode('-', $mot);
+ foreach ($mots_tiret as $position_tiret => $mot_tiret) {
+ if ($position_tiret > 0 && preg_match("/^(?:$mots_mineurs)$/", $mot_tiret)) {
+ continue;// Mot-tiret mineur en minuscule qui n'est pas en 1ère position dans le mot
+ } else if (preg_match('/^[[:upper:]][[:lower:]]+$/', $mot_tiret)) {
+ continue;// Mot (ou 'mot-tiret') avec lettre initiale majuscule
+ } else if ($position_tiret == count($mots_tiret) && preg_match('/^[:upper:][:lower:]+[:punct:]?$/', $mot_tiret)) {
+ continue;// Dernier mot (ou 'mot-tiret') avec lettre initiale majuscule, suivi d'un éventuel signe de ponctuation
+ } else {
+ $ok = false;
+ }
+ }
+ }
+ }
+ }
+ }
+ return $ok;
+ }
+
+ private function verifierEpitheteSp(&$epithete) {
+ $ok = false;
+ if (preg_match('/^[a-zäëḧïöẗüẅẍÿ][-a-zäëḧïöẗüẅẍÿ]+$/', $epithete)) {
+ $ok = true;
+ } else if (preg_match('/^sp\.(?:[A-Z]|[1-9][0-9]*)$/', $epithete)) {
+ $ok = true;
+ }
+ return $ok;
+ }
+
+ private function verifierEpitheteGenre(&$epithete) {
+ $ok = false;
+ if (preg_match('/^[A-ZÄËḦÏÖÜẄẌŸ](?:[-a-zäëḧïöẗüẅẍÿ]+|[a-zäëḧïöẗüẅẍÿ]+-[A-ZÄËḦÏÖÜẄẌŸ][a-zäëḧïöẗüẅẍÿ]+)$/', $epithete)) {
+ $ok = true;
+ }
+ return $ok;
+ }
+
+ private function verifierEstAbbreviationInfraSp($mot) {
+ $ok = false;
+ if(preg_match($this->manuel['abbr_rangs_infra_specifique'], $mot)) {
+ $ok = true;
+ }
+ return $ok;
+ }
+
+ private function formaterStyleNomGenre(&$genre) {
+ $genre_fmt = '';
+ if (preg_match('/^\s*([x+])\s+(.+)$/i', $genre, $match)) {
+ $genre_fmt = utf8_encode(strtolower(utf8_decode($match[1]))).' '.utf8_encode(ucfirst(strtolower(utf8_decode($match[2]))));
+ } elseif (preg_match('/^(.+)\s+([x+])\s+(.+)$/i', $genre, $match)) {
+ $genre_fmt = utf8_encode(ucfirst(strtolower(utf8_decode($match[1])))).' '.
+ utf8_encode(strtolower(utf8_decode($match[2]))).' '.utf8_encode(ucfirst(strtolower(utf8_decode($match[3]))));
+ } else {
+ $genre_fmt = utf8_encode(ucfirst(strtolower(utf8_decode($genre))));
+ }
+ return $genre_fmt;
+ }
+
+ private function formaterStyleEpitheteSpHybride(&$genre, &$epithete) {
+ $nom_fmt = '';
+ $erreur = '';
+ if (trim($genre) == '') {
+ if ($epithete != '') {
+ $nom_fmt = $this->formaterFormuleHybridite($epithete);
+ } else {
+ $erreur = "Formule d'hybridité sans épithéte spécifique";
+ }
+ } else {
+ $nom_fmt = $this->formaterNomHybride($genre, $epithete);
+ }
+ return array($nom_fmt, $erreur);
+ }
+
+ private function formaterStyleEpitheteInfraSpHybride(&$nom_sci, &$infra, &$genre = null) {
+ $nom_fmt = '';
+ $erreur = '';
+ if (trim($genre) == '') {
+ if (trim($nom_sci) == '') {
+ if (trim($infra) != '') {
+ $nom_fmt = $this->formaterFormuleHybridite($infra);
+ } else {
+ $erreur = "Formule d'hybridité sans épithéte infraspécifique";
+ }
+ } else {
+ $erreur = "Formule d'hybridité avec épithéte spécifique";
+ }
+ } else {
+ $nom_fmt = $this->formaterNomHybride($nom_sci, $infra);
+ }
+ return array($nom_fmt, $erreur);
+ }
+
+ private function formaterNomHybride(&$nom_sci, &$epithete) {
+ if (preg_match('/^(.+)\s+([x+])\s+(.+)$/i', $epithete, $match) != '') {
+ $nom_fmt = $nom_sci.utf8_encode(ucfirst(strtolower(utf8_decode($match[1])))).' '.
+ utf8_encode(strtolower(utf8_decode($match[2]))).' '.
+ utf8_encode(ucfirst(strtolower(utf8_decode($match[3]))));
+ } elseif (preg_match('/^([x+])\s+(.+)$/i', $epithete, $match) != '') {
+ $nom_fmt = $nom_sci.utf8_encode(strtolower(utf8_decode($match[1]))).' '.
+ utf8_encode(strtolower(utf8_decode($match[2])));
+ } else {
+ $nom_fmt = $nom_sci.utf8_encode(strtolower(utf8_decode($epithete)));
+ }
+ return $nom_fmt;
+ }
+
+ private function formaterFormuleHybridite(&$epithete) {
+ $liste_parents = explode(' x ', $epithete);
+ if (count($liste_parents) == 2) {
+ $nom_fmt = utf8_encode(ucfirst(strtolower(utf8_decode($liste_parents[0])))).' x '.
+ utf8_encode(ucfirst(strtolower(utf8_decode($liste_parents[1]))));
+ } else {
+ for ($i=0; $i<count($liste_parents); $i++) {
+ if (strstr(trim($liste_parents[$i]), ' ') == false) {
+ $nom[] = utf8_encode(ucfirst(strtolower(utf8_decode(trim($liste_parents[$i]))))).' x '.
+ utf8_encode(strtolower(utf8_decode(trim($liste_parents[$i+1]))));
+ $i++;
+ } else {
+ $nom[] = utf8_encode(ucfirst(strtolower(utf8_decode($liste_parents[$i]))));
+ }
+ }
+ $nom_fmt = implode(' x ', $nom);
+ }
+ return $nom_fmt;
+ }
+
+ private function repererEspace($nom_sci) {
+ $nom_sci = str_replace(' ', '<span class="espace">&nbsp;</span>', $nom_sci);
+ return $nom_sci;
+ }
+
+ private function repererCaracteresInvalidesNomFrancais($nom_verna) {
+ $nom_verna = str_replace(',', '<span class="espace">,</span>', $nom_verna);
+ $nom_verna = str_replace(';', '<span class="espace">;</span>', $nom_verna);
+ return $nom_verna;
+ }
+
+ private function mettreEnValeurAvertissement($chaine) {
+ $chaine = '<span class="espace">'.$chaine.'</span>';
+ return $chaine;
+ }
+
+ private function construireSuffixeNomPltCultivee(&$nom) {
+ $suffixe = array();
+ $suffixe[] = $this->construireNomCultivarGroupe($nom);
+ $suffixe[] = $this->construireNomCommercial($nom);
+ $suffixe[] = $this->construireNomCultivar($nom);
+ $suffixe = array_filter($suffixe);
+ return implode(' ', $suffixe);
+ }
+
+ private function construireNomCultivarGroupe(&$nom) {
+ $nom_groupe_cultivar = '';
+ if ($nom['cultivar_groupe'] != '') {
+ if (preg_match('/ gx$/', $nom['cultivar_groupe'])) {
+ $nom_groupe_cultivar = '('.$nom['cultivar_groupe'].')';
+ } else {
+ $nom_groupe_cultivar = '('.$nom['cultivar_groupe'].' Gp)';
+ }
+ }
+ return $nom_groupe_cultivar;
+ }
+
+ private function construireNomCommercial(&$nom) {
+ $nom_commercial = '';
+ if ($nom['nom_commercial'] != '') {
+ $nom_commercial = strtoupper($nom['nom_commercial']);
+ }
+ return $nom_commercial;
+ }
+
+ private function construireNomCultivar(&$nom) {
+ $nom_cultivar = '';
+ if ($nom['cultivar'] != '') {
+ $nom_cultivar = "'".$nom['cultivar']."'";
+ }
+ return $nom_cultivar;
+ }
+
+ private function classerNomsParNomComplet() {
+ $noms_classes = array();
+ foreach ($this->noms as &$nom) {
+ if (!isset($noms_classes[$nom['nom_sci']])) {
+ $noms_classes[$nom['nom_sci']] = 1;
+ } else {
+ $noms_classes[$nom['nom_sci']]++;
+ }
+ }
+ return $noms_classes;
+ }
+}
+?>
\ No newline at end of file
Index: v1.1/trunk/scripts/modules/importation/Importation.php
===================================================================
--- v1.1/trunk/scripts/modules/importation/Importation.php (revision 0)
+++ v1.1/trunk/scripts/modules/importation/Importation.php (revision 287)
@@ -0,0 +1,407 @@
+<?php
+// Encodage : UTF-8
+// +-------------------------------------------------------------------------------------------------------------------+
+/**
+* Importation d'un fichier ref-tax dans une bdnt
+*
+* Description : classe permettant de versionner les référentiels selon le manuel technique
+* Utilisation : php script.php comparaison -r bdnff -a tout -f fichier.txt
+*
+//Auteur original :
+* @author Jean-Pascal MILCENT <jpm@tela-botanica.org>
+* @copyright Tela-Botanica 1999-2010
+* @link http://www.tela-botanica.org/wikini/RTaxMethodo/wakka.php?wiki=MaNuel
+* @licence GPL v3 & CeCILL v2
+* @version $Id$
+*/
+// +-------------------------------------------------------------------------------------------------------------------+
+class Importation extends ScriptCommande {
+
+ const SCRIPT_NOM = 'importation';
+ const MANUEL_VERSION = '4.3';
+
+ private $referentiel = null;
+ /*public $parametres = array(
+ '-r' => array(true, true, 'referentiel de base'),
+ '-f' => array(true, true, 'fichier à comparer'));*/
+ private $noms_supprimes = null;
+ private $noms_colonnes = array();
+ private $rangs_bdnt_taxref = null;
+ private $statuts_bdnt_taxref = null;
+ private $correspondance_colonnes = array("CD_NOM" => "", "CD_SUP" => "", "CD_REF" => "",
+ "RANG" => "rang", "LB_NOM" => "nom_sci", "LB_AUTEUR" => array("auteur","annee"),
+ "FR" => "presence",
+ "FR-FRA" => "presence_Ga", "FR-COR" => "presence_Co", "REU" => "presence",
+ "GUA" => "presence_Guadeloupe", "SMSB" => array("presence_Saint_Martin", "presence_Saint_Barthelemy"),
+ "SM" => "presence_Saint_Martin", "SB" => "presence_Saint_Barthelemy", "MAR" => "presence_Martinique",
+ "GF" => "presence", "MAY" => "presence_Mayotte", "TAAF" => "presence_Taaf",
+ "SPM" => "presence_Guadeloupe", "PF" => "presence_Polynesie", "NC" => "presence_Nouvelle_Caledonie",
+ "BIBLIO" => "biblio_origine", "BDNGM" => "num_nom", "NUM_NOM" => "num_nom", "NOM_VERN" => "nom_francais");
+
+ private $sans_correspondance = array(
+ "FG_VALIDITE", "habitat", "WF", "CLI", "EPA",
+ "EU", "WLD", "ORACLE",
+ "DATE_CREA", "ORIGINE_CREA",
+ "NOM_COMPLET", "NOM_COMPLET_HTML",
+ "GENRE", "ESPECE", "SOUS_ESPECE", "ANNEE");
+
+ /*public function executer() {
+ $this->referentiel = $this->getParam('r');
+ // Lancement du test demandé
+ $fichier = $this->getParam('f');
+ if (file_exists($fichier)) {
+ $manuel_chemin = Config::get('chemin_appli').DS.'..'.DS.'configurations'.DS;
+ $manuel_config_nom = 'referentiel_v'.self::MANUEL_VERSION.'.ini';
+ $this->manuel = parse_ini_file($manuel_chemin.$manuel_config_nom);
+ $this->creerCorrespondanceRangBdntTaxref();
+ $donnees = $this->traiterFichierTaxref($fichier);
+ Debug::printr("Fin du traitement du fichier.");
+ $this->connecterPDO();
+ $this->creerTableTaxref();
+ $this->ajouterColonneCDNOM();
+ Debug::printr("Fin de la création de la table.");
+ $this->ajouterDonneesTaxRef($donnees);
+ Debug::printr("Fin de l'insertion des données.");
+ $this->creerTableComparaison();
+ Debug::printr("Fin de la création de la table comparaison.");
+ }
+ }*/
+
+ public function executer() {
+ // Récupération du dernier traitement demandé
+ $this->traitementDao = new TraitementDao();
+ $this->traitement = $this->traitementDao->getDernierTraitement('tout', self::SCRIPT_NOM);
+ if (isset($this->traitement)) {
+ $this->referentiel = $this->traitement['referentiel_code']; // Récupération du nom de projet
+ $fichier = $this->traitement['script_parametres'];
+ if (file_exists($fichier)) {
+ Debug::printr('Debute:'.$this->traitementDao->debuterTraitement($this->traitement['id_traitement']));
+ // Nettoyage des traitements obsolètes
+ $traitements_obsoletes = $this->traitementDao->getTraitementsObsoletes($this->referentiel, self::SCRIPT_NOM);
+ if (isset($traitements_obsoletes)) {
+ Debug::printr('Supp. obsoletes:'.$this->traitementDao->supprimer($traitements_obsoletes));
+ }
+ Debug::printr("Début du traitement du fichier.");
+ $manuel_chemin = Config::get('chemin_appli').DS.'..'.DS.'configurations'.DS;
+ $manuel_config_nom = 'referentiel_v'.self::MANUEL_VERSION.'.ini';
+ $this->manuel = parse_ini_file($manuel_chemin.$manuel_config_nom);
+ $this->creerCorrespondanceRangBdntTaxref();
+ $donnees = $this->traiterFichierTaxref($fichier);
+ Debug::printr("Fin du traitement du fichier.");
+ $this->connecterPDO();
+ $this->creerTableTaxref();
+ $this->ajouterColonneCDNOM();
+ Debug::printr("Fin de la création de la table.");
+ $this->ajouterDonneesTaxRef($donnees);
+ Debug::printr("Fin de l'insertion des données.");
+ $this->decouperNomSciTaxRef();
+ $this->creerTableComparaison();
+ Debug::printr("Fin de la création de la table comparaison.");
+ Debug::printr('Termine:'.$this->traitementDao->terminerTraitement($this->traitement['id_traitement']));
+ } else {
+ Debug::printr("Fichier introuvable".$fichier);
+ Debug::printr('Termine:'.$this->traitementDao->terminerTraitement($this->traitement['id_traitement']));
+ }
+ } else {
+ Debug::printr("Pas de traitement");
+ }
+ }
+
+
+// +-------------------------------------------------------------------------------------------------------------------+
+ private function creerTableTaxref() {
+ $requete = "DROP TABLE IF EXISTS {$this->referentiel}_taxref; ".
+ "CREATE TABLE {$this->referentiel}_taxref AS SELECT * FROM {$this->referentiel};".
+ "ALTER TABLE {$this->referentiel}_taxref ADD PRIMARY KEY (num_nom);";
+ $resultat = $this->executerRequeter($requete);
+ }
+
+ private function ajouterColonneCDNOM() {
+ $requete = "ALTER TABLE {$this->referentiel}_taxref ADD ".
+ "`CD_NOM` INT( 15 ) NULL DEFAULT NULL COMMENT 'numéro correspondant dans la base taxref.';";
+ $resultat = $this->executerRequeter($requete);
+ }
+
+ private function ajouterDonneesTaxRef($liste_noms) {
+ $i = 0; $j = 0; $requete = "";
+ foreach ($liste_noms as $nom) { $i++;
+ $requete .= "UPDATE {$this->referentiel}_taxref SET ".implode(' , ', $nom).
+ " WHERE {$nom['num_nom']} ;";//echo $requete;
+ if ($i == 1000 || ($j*1000+$i) == count($liste_noms) ) { $j++;
+ $resultat = $this->executerRequeter($requete.'commit;');
+ if ($resultat == null) {
+ $resultat = $this->executerRequeter($requete.'commit;');
+ }
+ $i = 0; $requete = "";
+ }
+ }
+ }
+
+ private function creerTableComparaison() {
+ foreach ($this->noms_colonnes as $colonne) {
+ if (isset($this->correspondance_colonnes[$colonne]) && $this->correspondance_colonnes[$colonne] != "") {
+ $nom_champ = $this->correspondance_colonnes[$colonne];
+ if (is_array($nom_champ)) {
+ foreach ($nom_champ as $nom) {
+ $champs_tax[] = "t.{$nom} AS tax_{$nom}";
+ $champs_tax[] = "b.{$nom} AS {$nom}";
+ $concat[] = "IF(t.{$nom} != b.{$nom}, '{$nom}, ', '')";
+ }
+ } else {
+ $champs_tax[] = "t.{$nom_champ} AS tax_{$nom_champ}";
+ $champs_tax[] = "b.{$nom_champ} AS {$nom_champ}";
+ $concat[] = "IF(t.{$nom_champ} != b.{$nom_champ}, '{$nom_champ}, ', '')";
+ }
+ }
+ }
+ date_default_timezone_set('Europe/London');
+ $requete = "CREATE TABLE {$this->referentiel}_comparaison_".date("Y_m_d_H_i_s")." AS".
+ " SELECT CONCAT(".implode(', ', $concat).") AS difference, ".implode(', ', $champs_tax).
+ " FROM {$this->referentiel} b, {$this->referentiel}_taxref t".
+ " WHERE b.num_nom = t.num_nom AND (b.nom_sci != t.nom_sci or b.auteur != t.auteur or b.annee != t.annee);";
+ //echo $requete;
+ $resultat = $this->executerRequeter($requete);
+ }
+
+// +-------------------------------------------------------------------------------------------------------------------+
+ private function traiterFichierTaxref($fichier) {
+ $donnees = array();
+ if (($pointeur = fopen($fichier, "r")) !== FALSE) {
+ $this->noms_colonnes = fgetcsv($pointeur, 1000, chr(9));
+ $num_nom = 0;
+ while (($ligne = fgetcsv($pointeur, 1000, chr(9))) !== FALSE) {
+ $nombreChamps = count($ligne);
+ $taxref[$ligne[0]] = $ligne;
+ for ($c=0; $c < $nombreChamps; $c++) {
+ if (isset($this->correspondance_colonnes[$this->noms_colonnes[$c]])) {
+ if (is_array($this->correspondance_colonnes[$this->noms_colonnes[$c]])) {
+ if ($this->noms_colonnes[$c] == 'LB_AUTEUR') {
+ if (preg_match('/(.*), +([0-9]{4})/', utf8_encode($ligne[$c]), $matches) == 1) {
+ $nom['auteur'] = 'auteur="'.$matches[1].'"';
+ $nom['annee'] = 'annee="'.trim($matches[2]).'"';
+ } else {
+ $nom['auteur'] = 'auteur="'.utf8_encode($ligne[$c]).'"';
+ $nom['annee'] = 'annee=""';
+ }
+ } else {
+ foreach ($this->correspondance_colonnes[$this->noms_colonnes[$c]] as $a=>$nom_colonne) {
+ $nom[$nom_colonne] = $nom_colonne.'="'.$ligne[$c].'"';
+ }
+ }
+ } elseif ($this->correspondance_colonnes[$this->noms_colonnes[$c]] == "") {
+ $nom[$this->noms_colonnes[$c]] = $ligne[$c];
+ } else {
+ if ($this->correspondance_colonnes[$this->noms_colonnes[$c]] == "rang") {
+ $rang = $this->rangs_bdnt_taxref[$ligne[$c]];
+ // à remettre si on décide de prendre les rangs taxref
+ //$nom[$this->correspondance_colonnes[$this->noms_colonnes[$c]]] =
+ //$this->correspondance_colonnes[$this->noms_colonnes[$c]].'="'.$this->rangs_bdnt_taxref[$ligne[$c]].'"';
+ } elseif ($this->correspondance_colonnes[$this->noms_colonnes[$c]] == "num_nom") {
+ $num_nom = $ligne[$c];
+ $nom[$this->correspondance_colonnes[$this->noms_colonnes[$c]]] =
+ $this->correspondance_colonnes[$this->noms_colonnes[$c]].'="'.$ligne[$c].'"';
+ } elseif ($this->correspondance_colonnes[$this->noms_colonnes[$c]] == "nom_sci") {
+ $nom_sci = $ligne[$c];
+ $nom[$this->correspondance_colonnes[$this->noms_colonnes[$c]]] =
+ $this->correspondance_colonnes[$this->noms_colonnes[$c]].'="'.trim($ligne[$c]).'"';
+ } else {
+ $nom[$this->correspondance_colonnes[$this->noms_colonnes[$c]]] =
+ $this->correspondance_colonnes[$this->noms_colonnes[$c]].'="'.trim($ligne[$c]).'"';
+ }
+ }
+ }
+ }
+ $nom['exclure_taxref'] = 'exclure_taxref="0"';
+ $donnees[$num_nom] = $nom;
+ $correspondance_taxref_bdnt[$nom['CD_NOM']] = $num_nom;
+ }
+ fclose($pointeur);
+ $donnees = $this->changerNumerotation($donnees, $correspondance_taxref_bdnt, $taxref);
+ }
+ return $donnees;
+ }
+
+ private function changerNumerotation($donnees, $correspondance, $taxref) {
+ $i=0;$j=0;$k=0;
+ foreach ($donnees as $num_nom=>$infos) {
+ if (isset($correspondance[$infos['CD_SUP']])) {
+ $donnees[$num_nom]['num_tax_sup'] = "num_tax_sup=".$correspondance[$infos['CD_SUP']];
+ $i++;
+ } elseif ($infos['CD_SUP'] != '') {
+ $donnees[$num_nom]['num_tax_sup'] = "num_tax_sup=''";
+ //$sup[$infos['CD_NOM']] = $taxref[$infos['CD_NOM']];
+ $sup[$infos['CD_SUP']] = $infos['CD_SUP'];
+ }
+ unset($donnees[$num_nom]['CD_SUP']);
+ if (isset($correspondance[$infos['CD_REF']])) {
+ $donnees[$num_nom]['num_nom_retenu'] = "num_nom_retenu=".$correspondance[$infos['CD_REF']];
+ $j++;
+ } elseif ($infos['CD_REF'] != '') {$k++;
+ $donnees[$num_nom]['num_nom_retenu'] = "num_nom_retenu=''";
+ //$ref[$infos['CD_NOM']] = $taxref[$infos['CD_NOM']];
+ $ref[$infos['CD_REF']] = $infos['CD_REF'];
+ } else {
+ Debug::printr($infos['CD_NOM']."n'a pas de valeur pour CD_REF");
+ }
+ unset($donnees[$num_nom]['CD_REF']);
+ $donnees[$num_nom]['CD_NOM'] = 'CD_NOM='.$donnees[$num_nom]['CD_NOM'];
+ }//echo "les taxons supérieurs manquants :".implode(" ,", $sup)." \nles retenus ".implode(" ,", $ref);
+ //$this->ecrireFichierCsv($ref, './retenu_absent.csv');
+ //$this->ajouterTaxonAbsent($abs);
+ //$this->ecrireFichierCsv($sup, './superieur_absent.csv');
+ echo "$j correspondance pour nom retenu $i correspondance pour nom sup $k non pas de correspondance retenu";
+ return $donnees;
+ }
+
+ // rechercher dans reftax les numéros absent dans la base
+ // modifier les tableaux ref et sup pour modifier $donnees (ajout + modif)
+ private function ajouterTaxonAbsent($abs) {
+ $requete = "SELECT ".implode(",", $this->noms_colonnes)." FROM taxref_v5 where CD_NOM IN (".implode(",", $abs).")";
+ echo $requete;
+ }
+
+ private function creerCorrespondanceRangBdntTaxref() {
+ $rangs = explode(',', $this->manuel['rangs_bdnt_taxref']);
+ foreach ($rangs as $rang) {
+ list($id_bdnt, $code_taxref) = explode(':', trim($rang));
+ $this->rangs_bdnt_taxref[$code_taxref] = $id_bdnt;
+ }
+ }
+
+// +-------------------------------------------------------------------------------------------------------------------+
+ private function decouperNomSciTaxRef() {
+ $requete = "SELECT num_nom, nom_sci, rang, type_epithete FROM {$this->referentiel}_taxref WHERE CD_NOM != ''";
+ $resultats = $this->executerRequeter($requete);
+ foreach ($resultats as $nom) {
+ extract($nom);
+ $nomen =array('nom_supra_generique' => 'nom_supra_generique=""', 'genre' => 'genre=""',
+ 'epithete_infra_generique' => 'epithete_infra_generique=""', 'epithete_sp' => 'epithete_sp=""',
+ 'type_epithete' => 'type_epithete=""', 'epithete_infra_sp' => 'epithete_infra_sp=""',
+ 'cultivar_groupe' => 'cultivar_groupe=""', 'cultivar' => 'cultivar=""', 'nom_commercial' => 'nom_commercial=""');
+ $parties_noms = explode(' ', $nom_sci);
+ if ($rang < 220) {
+ $nomen['nom_supra_generique'] = 'nom_supra_generique="'.$nom_sci.'"';
+ } elseif ($rang == 220) {
+ $nomen['genre'] = 'genre="'.trim($nom_sci).'"';
+ } elseif ($rang < 290) {
+ $nomen['genre'] = 'genre="'.$parties_noms[0].'"';
+ $nomen['epithete_infra_generique'] = 'epithete_infra_generique="'.$parties_noms[1].'"';
+ } else {
+ $nomen = array_merge($nomen, $this->decouperEspece($parties_noms));
+ $hybride = $this->etreHybride($parties_noms);
+ if (isset($parties_noms[2]) && ($hybride === false || $hybride > 2)) {
+ $nomen = array_merge($nomen, $this->decouperSousEspece($parties_noms));
+ }
+ }
+ $update = "UPDATE {$this->referentiel}_taxref SET ".implode(' , ', $nomen)." WHERE num_nom = ".$num_nom;
+ $resultat = $this->executerRequeter($update);
+ }
+ return $nomen;
+ }
+
+ private function decouperSousEspece($parties_noms) {
+ if ($this->etreTypeSousEpithete($parties_noms[2]) == true) {
+ $nomen['type_epithete'] = 'type_epithete="'.$parties_noms[2].'"';
+ $nomen['epithete_infra_sp'] = 'epithete_infra_sp="'.$parties_noms[3].'"';
+ } elseif (strpos($parties_noms[2], '(') === 0) {
+ $nomen['cultivar_groupe'] = 'cultivar_groupe="'.trim($parties_noms[2], "(").'"';
+ } elseif (strpos($parties_noms[2], "'") === 0) {
+ $nomen['cultivar'] = 'cultivar="'.trim($parties_noms[2], "'").'"';
+ } elseif (ctype_upper($parties_noms[2]) === true) {
+ $nomen['nom_commercial'] = 'nom_commercial="'.$parties_noms[2].'"';
+ } else {
+ $nomen['epithete_infra_sp'] = 'epithete_infra_sp="'.$parties_noms[2].'"';
+ }
+ return $nomen;
+ }
+
+ private function etreTypeSousEpithete($chaine) {
+ $type = false;
+ $types_epithete = array('subsp.', 'infra-sp.', 'var.', 'subvar.', 'f.', 'subf.', 'f. sp.', 'race', 'proles');
+ if (in_array(utf8_encode($chaine), $types_epithete)) {
+ $type = true;
+ }
+ return $type;
+ }
+
+ private function decouperEspece($parties_noms) {
+ $nomen['genre'] = 'genre="'.$parties_noms[0].'"';
+ $nomen['epithete_sp'] = 'epithete_sp="'.$parties_noms[1].'"';
+ $hybride = $this->etreHybride($parties_noms);
+ $chimere = array_search('+', $parties_noms);
+ if ($hybride != false || $hybride===0) {
+ $nomen = $this->decouperEspeceHybride($hybride, $parties_noms);
+ }
+ return $nomen;
+ }
+
+ private function etreHybride($parties_noms) {
+ $hybride = array_search('x', $parties_noms);
+ return $hybride;
+ }
+
+ private function decouperEspeceHybride($hybride, $parties_noms) {
+ if ($hybride == 0) {
+ $nomen['genre'] = 'genre="'.$parties_noms[0].' '.$parties_noms[1].'"';
+ $nomen['epithete_sp'] = 'epithete_sp="'.$parties_noms[2].'"';
+ } elseif ($hybride == 1 && count($parties_noms) == 4) {
+ $nomen['genre'] = 'genre="'.$parties_noms[0].' '.$parties_noms[1].' '.$parties_noms[2].'"';
+ $nomen['epithete_sp'] = 'epithete_sp="'.$parties_noms[3].'"';
+ } elseif ($hybride == 1 && count($parties_noms) == 3) {
+ $nomen['genre'] = 'genre="'.$parties_noms[0].'"';
+ $nomen['epithete_sp'] = 'epithete_sp="'.$parties_noms[1].' '.$parties_noms[2].'"';
+ } elseif ($hybride == 2) {
+ $nomen['genre'] = 'genre="'.$parties_noms[0].'"';
+ $nomen['epithete_sp'] = 'epithete_sp="'.$parties_noms[1].' '.$parties_noms[2].' '.$parties_noms[3].'"';
+ }
+ return $nomen;
+ }
+
+// +-------------------------------------------------------------------------------------------------------------------+
+ private function ecrireFichierCsv(&$contenu, $fichier) {
+ $retour = true;
+ $fichier = fopen($fichier, "w");
+ fputcsv($fichier, $this->noms_colonnes, chr('9'));
+ foreach ($contenu as $ligne) {
+ if (fputcsv($fichier, $ligne, chr('9')) == false) {
+ $e = "Une erreur est survenu lors de l'écriture du fichier : $fichier";
+ Debug::printr($e);
+ $retour = false;
+ }
+ }
+ $contenu = null;
+ return $retour;
+ }
+
+
+ private function connecterPDO() {
+ Config::charger('./configurations/bdd.ini');
+ try {
+ $dsn = Config::get('bdd_type').':dbname='.Config::get('bdd_nom').';host='.
+ Config::get('bdd_hote');
+ //$dsn = "mysql:dbname=referentiels;host=localhost";
+ $this->bdd = new PDO($dsn, Config::get('bdd_utilisateur'), Config::get('bdd_mot_de_passe'));
+ } catch (PDOException $e) {
+ print_r($e);
+ echo 'La connexion à la base de donnée via PDO a échouée : ' . $e->getMessage();
+ }
+ // Passe en UTF-8 la connexion à la BDD
+ $this->bdd->exec("SET NAMES 'utf8'");
+ // Affiche les erreurs détectées par PDO (sinon mode silencieux => aucune erreur affiché)
+ $this->bdd->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ }
+
+ protected function executerRequeter($requete) {
+ $infos = null;
+ try {
+ $infos = $this->bdd->query($requete);
+ if ($infos === false) {
+ echo $requete;
+ }
+ } catch (PDOException $e) {
+ echo sprintf($e->getFile(), $e->getLine(), $e->getMessage(), $e->getCode(), $requete);
+ }
+ return $infos;
+ }
+}
+?>
\ No newline at end of file
Index: v1.1/trunk/scripts/configurations/commun.defaut.ini
===================================================================
--- v1.1/trunk/scripts/configurations/commun.defaut.ini (revision 0)
+++ v1.1/trunk/scripts/configurations/commun.defaut.ini (revision 287)
@@ -0,0 +1,10 @@
+; Encodage : UTF-8
+; Contient des paramêtres commun à tous les modules
+; Ce fichier est appelé aprés bdd.ini et juste avant le fichier ini du projet qui peut donc écrasé les variables présentent ici
+; Il est nécessaire de copier puis de renommer en commun.ini ce fichier
+; Les commentaires commencent par ';', comme dans php.ini
+
+; Indique le chemin ou stocker le fichier de log
+log_chemin = "php:'/home/'.$_ENV['USER'].'/importation/log/'"
+; Indique si l'on veut avoir un seul fichier de log ecrase a chaque fois (true) ou pas (false)
+log_fichier_ecraser = "php-static:true"
\ No newline at end of file
Index: v1.1/trunk/scripts/configurations/config.defaut.ini
===================================================================
--- v1.1/trunk/scripts/configurations/config.defaut.ini (revision 0)
+++ v1.1/trunk/scripts/configurations/config.defaut.ini (revision 287)
@@ -0,0 +1,52 @@
+; +------------------------------------------------------------------------------------------------------+
+; Général
+; Séparateur de dossier
+ds = DIRECTORY_SEPARATOR
+
+; +------------------------------------------------------------------------------------------------------+
+; Info sur l'application
+info.nom = Scripts de gestion des référentiels
+; Abréviation de l'application
+info.abr = REF-S
+; Version du Framework nécessaire au fonctionnement de cette application
+info.framework.version = 0.2
+;Encodage de l'application
+appli_encodage = "UTF-8"
+; Nom de domaine pour l'URL de base de l'application : 162.38.234.6
+domaine = "localhost"
+; URL de base de l'application, si elle est laissée vide, l'application fonctionnera en Stand-alone
+url_base = "http://{ref:domaine}/referentiel/"
+; Mettre à true si l'application nécessite de s'identifier.
+identification = false
+;Chemin de l'application (pour l'utiliser dans ce fichier)
+chemin_appli = "php:Application::getChemin()"
+; Nom du dossier des modules de script
+dossier_modules = modules
+; Chemin vers le dossier des squelettes de l'application.
+; ATTENTION : il est nécessaire de compléter avec le nom du module et le dossier squelette.
+chemin_squelettes = "{ref:chemin_appli}{ref:dossier_modules}{ref:ds}"
+
+; +------------------------------------------------------------------------------------------------------+
+; Paramètrage de la session
+; Devons nous démarrer une session : oui (true) ou non (false)
+session_demarrage = "php:false"
+
+; +------------------------------------------------------------------------------------------------------+
+; Débogage
+; Indique si oui ou non on veut afficher le débogage.
+fw_debogage = true
+; Indique si oui ou non on veut lancer le chronométrage
+chronometrage = false
+; Indique sous quelle forme les méssages de débogage doivent s'afficher :
+; - Debug::MODE_ECHO : le message est affiché en utilisant echo
+; - Debug::MODE_NOTICE : le message est affiché en utilisant une erreur de type notice
+fw_debogage_mode = "php:Debug::MODE_ECHO"
+
+; +------------------------------------------------------------------------------------------------------+
+; Spécifique à l'application
+; Url du Jrest utilisé pour les services web fournissant les données à l'application
+url_jrest = "{ref:url_base}services/"
+; Chemin de dépôt des fichiers des référentiels
+chemin_referentiel_zip = "/home/jpm/web/referentiel/zip/"
+; Url téléchargement des fichiers zip des référentiels
+url_referentiel_zip_tpl = "http://{ref:domaine}/zip/%s"
\ No newline at end of file
Index: v1.1/trunk/scripts/configurations/bdd.defaut.ini
===================================================================
--- v1.1/trunk/scripts/configurations/bdd.defaut.ini (revision 0)
+++ v1.1/trunk/scripts/configurations/bdd.defaut.ini (revision 287)
@@ -0,0 +1,18 @@
+; Encodage : UTF-8
+; Exemple de fichier de configuration de la Base de Données
+; Ce fichier de configuration est appelé le premier
+; Il est nécessaire de le copier et de le renommer en supprimant de son nom ".defaut"
+; Les commentaires commencent par ';', comme dans php.ini
+
+; Type d'abstraction de bases de données
+bdd_abstraction = "PDO"
+; Indique les options pour MDB2 et DB
+bdd_options = "php:array('debug' => 3)"
+; Indique les paramtres pour le DSN utilisé pour se connecter à la base de données
+bdd_type = "mysql"
+bdd_nom = "referentiels"
+bdd_hote = ""
+; Nom de l'utilisateur de la base de données.
+bdd_utilisateur = "root"
+; Mot de passe de l'utilisateur de la base de données.
+bdd_mot_de_passe = ""
\ No newline at end of file
Index: v1.1/trunk/scripts/configurations
===================================================================
--- v1.1/trunk/scripts/configurations (revision 0)
+++ v1.1/trunk/scripts/configurations (revision 287)
/v1.1/trunk/scripts/configurations
Property changes:
Added: svn:ignore
## -0,0 +1,3 ##
+bdd.ini
+commun.ini
+config.ini
Index: v1.1/trunk/scripts/script.php
===================================================================
--- v1.1/trunk/scripts/script.php (revision 0)
+++ v1.1/trunk/scripts/script.php (revision 287)
@@ -0,0 +1,101 @@
+<?php
+// Encodage : UTF-8
+// +-------------------------------------------------------------------------------------------------------------------+
+/**
+* script
+*
+* Description : initialise le chargement et l'exécution des scripts
+* Fichier d'origine jelix-scripts par Jouanneau Laurent
+* copyright 2005-2007 Jouanneau laurent
+* link http://www.jelix.org
+*
+//Auteur original :
+* @author Jean-Pascal MILCENT <jpm@tela-botanica.org>
+* @copyright Tela-Botanica 1999-2008
+* @licence GPL v3 & CeCILL v2
+* @version $Id$
+*/
+// +-------------------------------------------------------------------------------------------------------------------+
+// Récupération de la config générale
+require dirname(__FILE__).DIRECTORY_SEPARATOR.'scripts.conf.php';
+
+// Gestion de l'auto chargement des classes
+function __autoload($classe)
+{
+ // Gestion des fichiers pésent dans le path
+ $fichier = $classe.'.php';
+ if (file_exists($fichier)) {
+ require_once $fichier;
+ }
+
+ // Gestion des classes PEAR
+ $fichier = ES_CHEMIN_PEAR;
+ if (!substr_count($classe, '_') > 0) {
+ $fichier .= $classe;
+ } else {
+
+ $tab_chemin = explode('_', $classe);
+ foreach ($tab_chemin as $c => $v) {
+ $fichier .= ($c == 0) ? $v.DS : $v;
+ }
+ }
+ $fichier .= '.php';
+ if (file_exists($fichier)) {
+ require_once $fichier;
+ }
+
+ // Gestion des classes principales des modules
+ $module = strtolower(str_replace(' ', '_', preg_replace('/([a-z])([A-Z])/', '$1 $2', $classe)));
+ $fichier = ES_CHEMIN_MODULE.$module.DS.$classe.'.php';
+ if (file_exists($fichier)) {
+ require_once $fichier;
+ }
+
+ // Gestion des bibliothèques des scripts
+ foreach ($GLOBALS['chemins_autoload'] as $chemin) {
+ $fichier = $chemin.$classe.'.php';
+
+ if (file_exists($fichier)) {
+ require_once $fichier;
+ break;
+ }
+ }
+}
+
+// Le fichier autoload.inc.php du Framework de Tela Botanica doit être appelée avant tout autre chose dans l'application.
+// Sinon, rien ne sera chargé.
+// Chemin du fichier chargeant le framework requis
+$framework = dirname(__FILE__).DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'interfaces'.DIRECTORY_SEPARATOR.'framework.php';
+if (!file_exists($framework)) {
+ $e = "Veuillez paramêtrer l'emplacement et la version du Framework dans le fichier $framework";
+ trigger_error($e, E_USER_ERROR);
+} else {
+ // Inclusion du Framework
+ require_once $framework;
+ // Ajout d'information concernant cette application
+ Application::setChemin(__FILE__);// Obligatoire
+ Application::setInfo(Config::get('info'));
+
+ /**
+ * récupération du nom de la commande et éventuellement du nom de l'application
+ */
+ if ($_SERVER['argc'] < 2){
+ die("Erreur: pas de commande. Voir '".$_SERVER['argv'][0]." help'.\n");
+ }
+
+ $argv = $_SERVER['argv'];
+ $script_nom = array_shift($argv); // shift le nom du script
+ $commande_nom = array_shift($argv); // prend le nom de la commande
+
+ // Chargement de la commande
+ $Script = Script::charger($commande_nom);
+ $parametres = Script::getParametres($argv);
+
+ // Lancement de la commande
+ $Script->initialiser($parametres);
+ $Script->executer();
+
+ // Affichage des execptions et erreurs générées par le script
+ echo GestionnaireException::getExceptions();
+}
+?>
\ No newline at end of file
Index: v1.1/trunk/scripts/bibliotheque/pclzip-gnu-lgpl.txt
===================================================================
--- v1.1/trunk/scripts/bibliotheque/pclzip-gnu-lgpl.txt (revision 0)
+++ v1.1/trunk/scripts/bibliotheque/pclzip-gnu-lgpl.txt (revision 287)
@@ -0,0 +1,504 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
/v1.1/trunk/scripts/bibliotheque/pclzip-gnu-lgpl.txt
Property changes:
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: v1.1/trunk/scripts/bibliotheque/PclZip.php
===================================================================
--- v1.1/trunk/scripts/bibliotheque/PclZip.php (revision 0)
+++ v1.1/trunk/scripts/bibliotheque/PclZip.php (revision 287)
@@ -0,0 +1,5694 @@
+<?php
+// --------------------------------------------------------------------------------
+// PhpConcept Library - Zip Module 2.8.2
+// --------------------------------------------------------------------------------
+// License GNU/LGPL - Vincent Blavet - August 2009
+// http://www.phpconcept.net
+// --------------------------------------------------------------------------------
+//
+// Presentation :
+// PclZip is a PHP library that manage ZIP archives.
+// So far tests show that archives generated by PclZip are readable by
+// WinZip application and other tools.
+//
+// Description :
+// See readme.txt and http://www.phpconcept.net
+//
+// Warning :
+// This library and the associated files are non commercial, non professional
+// work.
+// It should not have unexpected results. However if any damage is caused by
+// this software the author can not be responsible.
+// The use of this software is at the risk of the user.
+//
+// --------------------------------------------------------------------------------
+// $Id: pclzip.lib.php,v 1.60 2009/09/30 21:01:04 vblavet Exp $
+// --------------------------------------------------------------------------------
+
+ // ----- Constants
+ if (!defined('PCLZIP_READ_BLOCK_SIZE')) {
+ define( 'PCLZIP_READ_BLOCK_SIZE', 2048 );
+ }
+
+ // ----- File list separator
+ // In version 1.x of PclZip, the separator for file list is a space
+ // (which is not a very smart choice, specifically for windows paths !).
+ // A better separator should be a comma (,). This constant gives you the
+ // abilty to change that.
+ // However notice that changing this value, may have impact on existing
+ // scripts, using space separated filenames.
+ // Recommanded values for compatibility with older versions :
+ //define( 'PCLZIP_SEPARATOR', ' ' );
+ // Recommanded values for smart separation of filenames.
+ if (!defined('PCLZIP_SEPARATOR')) {
+ define( 'PCLZIP_SEPARATOR', ',' );
+ }
+
+ // ----- Error configuration
+ // 0 : PclZip Class integrated error handling
+ // 1 : PclError external library error handling. By enabling this
+ // you must ensure that you have included PclError library.
+ // [2,...] : reserved for futur use
+ if (!defined('PCLZIP_ERROR_EXTERNAL')) {
+ define( 'PCLZIP_ERROR_EXTERNAL', 0 );
+ }
+
+ // ----- Optional static temporary directory
+ // By default temporary files are generated in the script current
+ // path.
+ // If defined :
+ // - MUST BE terminated by a '/'.
+ // - MUST be a valid, already created directory
+ // Samples :
+ // define( 'PCLZIP_TEMPORARY_DIR', '/temp/' );
+ // define( 'PCLZIP_TEMPORARY_DIR', 'C:/Temp/' );
+ if (!defined('PCLZIP_TEMPORARY_DIR')) {
+ define( 'PCLZIP_TEMPORARY_DIR', '' );
+ }
+
+ // ----- Optional threshold ratio for use of temporary files
+ // Pclzip sense the size of the file to add/extract and decide to
+ // use or not temporary file. The algorythm is looking for
+ // memory_limit of PHP and apply a ratio.
+ // threshold = memory_limit * ratio.
+ // Recommended values are under 0.5. Default 0.47.
+ // Samples :
+ // define( 'PCLZIP_TEMPORARY_FILE_RATIO', 0.5 );
+ if (!defined('PCLZIP_TEMPORARY_FILE_RATIO')) {
+ define( 'PCLZIP_TEMPORARY_FILE_RATIO', 0.47 );
+ }
+
+// --------------------------------------------------------------------------------
+// ***** UNDER THIS LINE NOTHING NEEDS TO BE MODIFIED *****
+// --------------------------------------------------------------------------------
+
+ // ----- Global variables
+ $g_pclzip_version = "2.8.2";
+
+ // ----- Error codes
+ // -1 : Unable to open file in binary write mode
+ // -2 : Unable to open file in binary read mode
+ // -3 : Invalid parameters
+ // -4 : File does not exist
+ // -5 : Filename is too long (max. 255)
+ // -6 : Not a valid zip file
+ // -7 : Invalid extracted file size
+ // -8 : Unable to create directory
+ // -9 : Invalid archive extension
+ // -10 : Invalid archive format
+ // -11 : Unable to delete file (unlink)
+ // -12 : Unable to rename file (rename)
+ // -13 : Invalid header checksum
+ // -14 : Invalid archive size
+ define( 'PCLZIP_ERR_USER_ABORTED', 2 );
+ define( 'PCLZIP_ERR_NO_ERROR', 0 );
+ define( 'PCLZIP_ERR_WRITE_OPEN_FAIL', -1 );
+ define( 'PCLZIP_ERR_READ_OPEN_FAIL', -2 );
+ define( 'PCLZIP_ERR_INVALID_PARAMETER', -3 );
+ define( 'PCLZIP_ERR_MISSING_FILE', -4 );
+ define( 'PCLZIP_ERR_FILENAME_TOO_LONG', -5 );
+ define( 'PCLZIP_ERR_INVALID_ZIP', -6 );
+ define( 'PCLZIP_ERR_BAD_EXTRACTED_FILE', -7 );
+ define( 'PCLZIP_ERR_DIR_CREATE_FAIL', -8 );
+ define( 'PCLZIP_ERR_BAD_EXTENSION', -9 );
+ define( 'PCLZIP_ERR_BAD_FORMAT', -10 );
+ define( 'PCLZIP_ERR_DELETE_FILE_FAIL', -11 );
+ define( 'PCLZIP_ERR_RENAME_FILE_FAIL', -12 );
+ define( 'PCLZIP_ERR_BAD_CHECKSUM', -13 );
+ define( 'PCLZIP_ERR_INVALID_ARCHIVE_ZIP', -14 );
+ define( 'PCLZIP_ERR_MISSING_OPTION_VALUE', -15 );
+ define( 'PCLZIP_ERR_INVALID_OPTION_VALUE', -16 );
+ define( 'PCLZIP_ERR_ALREADY_A_DIRECTORY', -17 );
+ define( 'PCLZIP_ERR_UNSUPPORTED_COMPRESSION', -18 );
+ define( 'PCLZIP_ERR_UNSUPPORTED_ENCRYPTION', -19 );
+ define( 'PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE', -20 );
+ define( 'PCLZIP_ERR_DIRECTORY_RESTRICTION', -21 );
+
+ // ----- Options values
+ define( 'PCLZIP_OPT_PATH', 77001 );
+ define( 'PCLZIP_OPT_ADD_PATH', 77002 );
+ define( 'PCLZIP_OPT_REMOVE_PATH', 77003 );
+ define( 'PCLZIP_OPT_REMOVE_ALL_PATH', 77004 );
+ define( 'PCLZIP_OPT_SET_CHMOD', 77005 );
+ define( 'PCLZIP_OPT_EXTRACT_AS_STRING', 77006 );
+ define( 'PCLZIP_OPT_NO_COMPRESSION', 77007 );
+ define( 'PCLZIP_OPT_BY_NAME', 77008 );
+ define( 'PCLZIP_OPT_BY_INDEX', 77009 );
+ define( 'PCLZIP_OPT_BY_EREG', 77010 );
+ define( 'PCLZIP_OPT_BY_PREG', 77011 );
+ define( 'PCLZIP_OPT_COMMENT', 77012 );
+ define( 'PCLZIP_OPT_ADD_COMMENT', 77013 );
+ define( 'PCLZIP_OPT_PREPEND_COMMENT', 77014 );
+ define( 'PCLZIP_OPT_EXTRACT_IN_OUTPUT', 77015 );
+ define( 'PCLZIP_OPT_REPLACE_NEWER', 77016 );
+ define( 'PCLZIP_OPT_STOP_ON_ERROR', 77017 );
+ // Having big trouble with crypt. Need to multiply 2 long int
+ // which is not correctly supported by PHP ...
+ //define( 'PCLZIP_OPT_CRYPT', 77018 );
+ define( 'PCLZIP_OPT_EXTRACT_DIR_RESTRICTION', 77019 );
+ define( 'PCLZIP_OPT_TEMP_FILE_THRESHOLD', 77020 );
+ define( 'PCLZIP_OPT_ADD_TEMP_FILE_THRESHOLD', 77020 ); // alias
+ define( 'PCLZIP_OPT_TEMP_FILE_ON', 77021 );
+ define( 'PCLZIP_OPT_ADD_TEMP_FILE_ON', 77021 ); // alias
+ define( 'PCLZIP_OPT_TEMP_FILE_OFF', 77022 );
+ define( 'PCLZIP_OPT_ADD_TEMP_FILE_OFF', 77022 ); // alias
+
+ // ----- File description attributes
+ define( 'PCLZIP_ATT_FILE_NAME', 79001 );
+ define( 'PCLZIP_ATT_FILE_NEW_SHORT_NAME', 79002 );
+ define( 'PCLZIP_ATT_FILE_NEW_FULL_NAME', 79003 );
+ define( 'PCLZIP_ATT_FILE_MTIME', 79004 );
+ define( 'PCLZIP_ATT_FILE_CONTENT', 79005 );
+ define( 'PCLZIP_ATT_FILE_COMMENT', 79006 );
+
+ // ----- Call backs values
+ define( 'PCLZIP_CB_PRE_EXTRACT', 78001 );
+ define( 'PCLZIP_CB_POST_EXTRACT', 78002 );
+ define( 'PCLZIP_CB_PRE_ADD', 78003 );
+ define( 'PCLZIP_CB_POST_ADD', 78004 );
+ /* For futur use
+ define( 'PCLZIP_CB_PRE_LIST', 78005 );
+ define( 'PCLZIP_CB_POST_LIST', 78006 );
+ define( 'PCLZIP_CB_PRE_DELETE', 78007 );
+ define( 'PCLZIP_CB_POST_DELETE', 78008 );
+ */
+
+ // --------------------------------------------------------------------------------
+ // Class : PclZip
+ // Description :
+ // PclZip is the class that represent a Zip archive.
+ // The public methods allow the manipulation of the archive.
+ // Attributes :
+ // Attributes must not be accessed directly.
+ // Methods :
+ // PclZip() : Object creator
+ // create() : Creates the Zip archive
+ // listContent() : List the content of the Zip archive
+ // extract() : Extract the content of the archive
+ // properties() : List the properties of the archive
+ // --------------------------------------------------------------------------------
+ class PclZip
+ {
+ // ----- Filename of the zip file
+ var $zipname = '';
+
+ // ----- File descriptor of the zip file
+ var $zip_fd = 0;
+
+ // ----- Internal error handling
+ var $error_code = 1;
+ var $error_string = '';
+
+ // ----- Current status of the magic_quotes_runtime
+ // This value store the php configuration for magic_quotes
+ // The class can then disable the magic_quotes and reset it after
+ var $magic_quotes_status;
+
+ // --------------------------------------------------------------------------------
+ // Function : PclZip()
+ // Description :
+ // Creates a PclZip object and set the name of the associated Zip archive
+ // filename.
+ // Note that no real action is taken, if the archive does not exist it is not
+ // created. Use create() for that.
+ // --------------------------------------------------------------------------------
+ function PclZip($p_zipname)
+ {
+
+ // ----- Tests the zlib
+ if (!function_exists('gzopen'))
+ {
+ die('Abort '.basename(__FILE__).' : Missing zlib extensions');
+ }
+
+ // ----- Set the attributes
+ $this->zipname = $p_zipname;
+ $this->zip_fd = 0;
+ $this->magic_quotes_status = -1;
+
+ // ----- Return
+ return;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function :
+ // create($p_filelist, $p_add_dir="", $p_remove_dir="")
+ // create($p_filelist, $p_option, $p_option_value, ...)
+ // Description :
+ // This method supports two different synopsis. The first one is historical.
+ // This method creates a Zip Archive. The Zip file is created in the
+ // filesystem. The files and directories indicated in $p_filelist
+ // are added in the archive. See the parameters description for the
+ // supported format of $p_filelist.
+ // When a directory is in the list, the directory and its content is added
+ // in the archive.
+ // In this synopsis, the function takes an optional variable list of
+ // options. See bellow the supported options.
+ // Parameters :
+ // $p_filelist : An array containing file or directory names, or
+ // a string containing one filename or one directory name, or
+ // a string containing a list of filenames and/or directory
+ // names separated by spaces.
+ // $p_add_dir : A path to add before the real path of the archived file,
+ // in order to have it memorized in the archive.
+ // $p_remove_dir : A path to remove from the real path of the file to archive,
+ // in order to have a shorter path memorized in the archive.
+ // When $p_add_dir and $p_remove_dir are set, $p_remove_dir
+ // is removed first, before $p_add_dir is added.
+ // Options :
+ // PCLZIP_OPT_ADD_PATH :
+ // PCLZIP_OPT_REMOVE_PATH :
+ // PCLZIP_OPT_REMOVE_ALL_PATH :
+ // PCLZIP_OPT_COMMENT :
+ // PCLZIP_CB_PRE_ADD :
+ // PCLZIP_CB_POST_ADD :
+ // Return Values :
+ // 0 on failure,
+ // The list of the added files, with a status of the add action.
+ // (see PclZip::listContent() for list entry format)
+ // --------------------------------------------------------------------------------
+ function create($p_filelist)
+ {
+ $v_result=1;
+
+ // ----- Reset the error handler
+ $this->privErrorReset();
+
+ // ----- Set default values
+ $v_options = array();
+ $v_options[PCLZIP_OPT_NO_COMPRESSION] = FALSE;
+
+ // ----- Look for variable options arguments
+ $v_size = func_num_args();
+
+ // ----- Look for arguments
+ if ($v_size > 1) {
+ // ----- Get the arguments
+ $v_arg_list = func_get_args();
+
+ // ----- Remove from the options list the first argument
+ array_shift($v_arg_list);
+ $v_size--;
+
+ // ----- Look for first arg
+ if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) {
+
+ // ----- Parse the options
+ $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options,
+ array (PCLZIP_OPT_REMOVE_PATH => 'optional',
+ PCLZIP_OPT_REMOVE_ALL_PATH => 'optional',
+ PCLZIP_OPT_ADD_PATH => 'optional',
+ PCLZIP_CB_PRE_ADD => 'optional',
+ PCLZIP_CB_POST_ADD => 'optional',
+ PCLZIP_OPT_NO_COMPRESSION => 'optional',
+ PCLZIP_OPT_COMMENT => 'optional',
+ PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional',
+ PCLZIP_OPT_TEMP_FILE_ON => 'optional',
+ PCLZIP_OPT_TEMP_FILE_OFF => 'optional'
+ //, PCLZIP_OPT_CRYPT => 'optional'
+ ));
+ if ($v_result != 1) {
+ return 0;
+ }
+ }
+
+ // ----- Look for 2 args
+ // Here we need to support the first historic synopsis of the
+ // method.
+ else {
+
+ // ----- Get the first argument
+ $v_options[PCLZIP_OPT_ADD_PATH] = $v_arg_list[0];
+
+ // ----- Look for the optional second argument
+ if ($v_size == 2) {
+ $v_options[PCLZIP_OPT_REMOVE_PATH] = $v_arg_list[1];
+ }
+ else if ($v_size > 2) {
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER,
+ "Invalid number / type of arguments");
+ return 0;
+ }
+ }
+ }
+
+ // ----- Look for default option values
+ $this->privOptionDefaultThreshold($v_options);
+
+ // ----- Init
+ $v_string_list = array();
+ $v_att_list = array();
+ $v_filedescr_list = array();
+ $p_result_list = array();
+
+ // ----- Look if the $p_filelist is really an array
+ if (is_array($p_filelist)) {
+
+ // ----- Look if the first element is also an array
+ // This will mean that this is a file description entry
+ if (isset($p_filelist[0]) && is_array($p_filelist[0])) {
+ $v_att_list = $p_filelist;
+ }
+
+ // ----- The list is a list of string names
+ else {
+ $v_string_list = $p_filelist;
+ }
+ }
+
+ // ----- Look if the $p_filelist is a string
+ else if (is_string($p_filelist)) {
+ // ----- Create a list from the string
+ $v_string_list = explode(PCLZIP_SEPARATOR, $p_filelist);
+ }
+
+ // ----- Invalid variable type for $p_filelist
+ else {
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type p_filelist");
+ return 0;
+ }
+
+ // ----- Reformat the string list
+ if (sizeof($v_string_list) != 0) {
+ foreach ($v_string_list as $v_string) {
+ if ($v_string != '') {
+ $v_att_list[][PCLZIP_ATT_FILE_NAME] = $v_string;
+ }
+ else {
+ }
+ }
+ }
+
+ // ----- For each file in the list check the attributes
+ $v_supported_attributes
+ = array ( PCLZIP_ATT_FILE_NAME => 'mandatory'
+ ,PCLZIP_ATT_FILE_NEW_SHORT_NAME => 'optional'
+ ,PCLZIP_ATT_FILE_NEW_FULL_NAME => 'optional'
+ ,PCLZIP_ATT_FILE_MTIME => 'optional'
+ ,PCLZIP_ATT_FILE_CONTENT => 'optional'
+ ,PCLZIP_ATT_FILE_COMMENT => 'optional'
+ );
+ foreach ($v_att_list as $v_entry) {
+ $v_result = $this->privFileDescrParseAtt($v_entry,
+ $v_filedescr_list[],
+ $v_options,
+ $v_supported_attributes);
+ if ($v_result != 1) {
+ return 0;
+ }
+ }
+
+ // ----- Expand the filelist (expand directories)
+ $v_result = $this->privFileDescrExpand($v_filedescr_list, $v_options);
+ if ($v_result != 1) {
+ return 0;
+ }
+
+ // ----- Call the create fct
+ $v_result = $this->privCreate($v_filedescr_list, $p_result_list, $v_options);
+ if ($v_result != 1) {
+ return 0;
+ }
+
+ // ----- Return
+ return $p_result_list;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function :
+ // add($p_filelist, $p_add_dir="", $p_remove_dir="")
+ // add($p_filelist, $p_option, $p_option_value, ...)
+ // Description :
+ // This method supports two synopsis. The first one is historical.
+ // This methods add the list of files in an existing archive.
+ // If a file with the same name already exists, it is added at the end of the
+ // archive, the first one is still present.
+ // If the archive does not exist, it is created.
+ // Parameters :
+ // $p_filelist : An array containing file or directory names, or
+ // a string containing one filename or one directory name, or
+ // a string containing a list of filenames and/or directory
+ // names separated by spaces.
+ // $p_add_dir : A path to add before the real path of the archived file,
+ // in order to have it memorized in the archive.
+ // $p_remove_dir : A path to remove from the real path of the file to archive,
+ // in order to have a shorter path memorized in the archive.
+ // When $p_add_dir and $p_remove_dir are set, $p_remove_dir
+ // is removed first, before $p_add_dir is added.
+ // Options :
+ // PCLZIP_OPT_ADD_PATH :
+ // PCLZIP_OPT_REMOVE_PATH :
+ // PCLZIP_OPT_REMOVE_ALL_PATH :
+ // PCLZIP_OPT_COMMENT :
+ // PCLZIP_OPT_ADD_COMMENT :
+ // PCLZIP_OPT_PREPEND_COMMENT :
+ // PCLZIP_CB_PRE_ADD :
+ // PCLZIP_CB_POST_ADD :
+ // Return Values :
+ // 0 on failure,
+ // The list of the added files, with a status of the add action.
+ // (see PclZip::listContent() for list entry format)
+ // --------------------------------------------------------------------------------
+ function add($p_filelist)
+ {
+ $v_result=1;
+
+ // ----- Reset the error handler
+ $this->privErrorReset();
+
+ // ----- Set default values
+ $v_options = array();
+ $v_options[PCLZIP_OPT_NO_COMPRESSION] = FALSE;
+
+ // ----- Look for variable options arguments
+ $v_size = func_num_args();
+
+ // ----- Look for arguments
+ if ($v_size > 1) {
+ // ----- Get the arguments
+ $v_arg_list = func_get_args();
+
+ // ----- Remove form the options list the first argument
+ array_shift($v_arg_list);
+ $v_size--;
+
+ // ----- Look for first arg
+ if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) {
+
+ // ----- Parse the options
+ $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options,
+ array (PCLZIP_OPT_REMOVE_PATH => 'optional',
+ PCLZIP_OPT_REMOVE_ALL_PATH => 'optional',
+ PCLZIP_OPT_ADD_PATH => 'optional',
+ PCLZIP_CB_PRE_ADD => 'optional',
+ PCLZIP_CB_POST_ADD => 'optional',
+ PCLZIP_OPT_NO_COMPRESSION => 'optional',
+ PCLZIP_OPT_COMMENT => 'optional',
+ PCLZIP_OPT_ADD_COMMENT => 'optional',
+ PCLZIP_OPT_PREPEND_COMMENT => 'optional',
+ PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional',
+ PCLZIP_OPT_TEMP_FILE_ON => 'optional',
+ PCLZIP_OPT_TEMP_FILE_OFF => 'optional'
+ //, PCLZIP_OPT_CRYPT => 'optional'
+ ));
+ if ($v_result != 1) {
+ return 0;
+ }
+ }
+
+ // ----- Look for 2 args
+ // Here we need to support the first historic synopsis of the
+ // method.
+ else {
+
+ // ----- Get the first argument
+ $v_options[PCLZIP_OPT_ADD_PATH] = $v_add_path = $v_arg_list[0];
+
+ // ----- Look for the optional second argument
+ if ($v_size == 2) {
+ $v_options[PCLZIP_OPT_REMOVE_PATH] = $v_arg_list[1];
+ }
+ else if ($v_size > 2) {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments");
+
+ // ----- Return
+ return 0;
+ }
+ }
+ }
+
+ // ----- Look for default option values
+ $this->privOptionDefaultThreshold($v_options);
+
+ // ----- Init
+ $v_string_list = array();
+ $v_att_list = array();
+ $v_filedescr_list = array();
+ $p_result_list = array();
+
+ // ----- Look if the $p_filelist is really an array
+ if (is_array($p_filelist)) {
+
+ // ----- Look if the first element is also an array
+ // This will mean that this is a file description entry
+ if (isset($p_filelist[0]) && is_array($p_filelist[0])) {
+ $v_att_list = $p_filelist;
+ }
+
+ // ----- The list is a list of string names
+ else {
+ $v_string_list = $p_filelist;
+ }
+ }
+
+ // ----- Look if the $p_filelist is a string
+ else if (is_string($p_filelist)) {
+ // ----- Create a list from the string
+ $v_string_list = explode(PCLZIP_SEPARATOR, $p_filelist);
+ }
+
+ // ----- Invalid variable type for $p_filelist
+ else {
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type '".gettype($p_filelist)."' for p_filelist");
+ return 0;
+ }
+
+ // ----- Reformat the string list
+ if (sizeof($v_string_list) != 0) {
+ foreach ($v_string_list as $v_string) {
+ $v_att_list[][PCLZIP_ATT_FILE_NAME] = $v_string;
+ }
+ }
+
+ // ----- For each file in the list check the attributes
+ $v_supported_attributes
+ = array ( PCLZIP_ATT_FILE_NAME => 'mandatory'
+ ,PCLZIP_ATT_FILE_NEW_SHORT_NAME => 'optional'
+ ,PCLZIP_ATT_FILE_NEW_FULL_NAME => 'optional'
+ ,PCLZIP_ATT_FILE_MTIME => 'optional'
+ ,PCLZIP_ATT_FILE_CONTENT => 'optional'
+ ,PCLZIP_ATT_FILE_COMMENT => 'optional'
+ );
+ foreach ($v_att_list as $v_entry) {
+ $v_result = $this->privFileDescrParseAtt($v_entry,
+ $v_filedescr_list[],
+ $v_options,
+ $v_supported_attributes);
+ if ($v_result != 1) {
+ return 0;
+ }
+ }
+
+ // ----- Expand the filelist (expand directories)
+ $v_result = $this->privFileDescrExpand($v_filedescr_list, $v_options);
+ if ($v_result != 1) {
+ return 0;
+ }
+
+ // ----- Call the create fct
+ $v_result = $this->privAdd($v_filedescr_list, $p_result_list, $v_options);
+ if ($v_result != 1) {
+ return 0;
+ }
+
+ // ----- Return
+ return $p_result_list;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : listContent()
+ // Description :
+ // This public method, gives the list of the files and directories, with their
+ // properties.
+ // The properties of each entries in the list are (used also in other functions) :
+ // filename : Name of the file. For a create or add action it is the filename
+ // given by the user. For an extract function it is the filename
+ // of the extracted file.
+ // stored_filename : Name of the file / directory stored in the archive.
+ // size : Size of the stored file.
+ // compressed_size : Size of the file's data compressed in the archive
+ // (without the headers overhead)
+ // mtime : Last known modification date of the file (UNIX timestamp)
+ // comment : Comment associated with the file
+ // folder : true | false
+ // index : index of the file in the archive
+ // status : status of the action (depending of the action) :
+ // Values are :
+ // ok : OK !
+ // filtered : the file / dir is not extracted (filtered by user)
+ // already_a_directory : the file can not be extracted because a
+ // directory with the same name already exists
+ // write_protected : the file can not be extracted because a file
+ // with the same name already exists and is
+ // write protected
+ // newer_exist : the file was not extracted because a newer file exists
+ // path_creation_fail : the file is not extracted because the folder
+ // does not exist and can not be created
+ // write_error : the file was not extracted because there was a
+ // error while writing the file
+ // read_error : the file was not extracted because there was a error
+ // while reading the file
+ // invalid_header : the file was not extracted because of an archive
+ // format error (bad file header)
+ // Note that each time a method can continue operating when there
+ // is an action error on a file, the error is only logged in the file status.
+ // Return Values :
+ // 0 on an unrecoverable failure,
+ // The list of the files in the archive.
+ // --------------------------------------------------------------------------------
+ function listContent()
+ {
+ $v_result=1;
+
+ // ----- Reset the error handler
+ $this->privErrorReset();
+
+ // ----- Check archive
+ if (!$this->privCheckFormat()) {
+ return(0);
+ }
+
+ // ----- Call the extracting fct
+ $p_list = array();
+ if (($v_result = $this->privList($p_list)) != 1)
+ {
+ unset($p_list);
+ return(0);
+ }
+
+ // ----- Return
+ return $p_list;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function :
+ // extract($p_path="./", $p_remove_path="")
+ // extract([$p_option, $p_option_value, ...])
+ // Description :
+ // This method supports two synopsis. The first one is historical.
+ // This method extract all the files / directories from the archive to the
+ // folder indicated in $p_path.
+ // If you want to ignore the 'root' part of path of the memorized files
+ // you can indicate this in the optional $p_remove_path parameter.
+ // By default, if a newer file with the same name already exists, the
+ // file is not extracted.
+ //
+ // If both PCLZIP_OPT_PATH and PCLZIP_OPT_ADD_PATH aoptions
+ // are used, the path indicated in PCLZIP_OPT_ADD_PATH is append
+ // at the end of the path value of PCLZIP_OPT_PATH.
+ // Parameters :
+ // $p_path : Path where the files and directories are to be extracted
+ // $p_remove_path : First part ('root' part) of the memorized path
+ // (if any similar) to remove while extracting.
+ // Options :
+ // PCLZIP_OPT_PATH :
+ // PCLZIP_OPT_ADD_PATH :
+ // PCLZIP_OPT_REMOVE_PATH :
+ // PCLZIP_OPT_REMOVE_ALL_PATH :
+ // PCLZIP_CB_PRE_EXTRACT :
+ // PCLZIP_CB_POST_EXTRACT :
+ // Return Values :
+ // 0 or a negative value on failure,
+ // The list of the extracted files, with a status of the action.
+ // (see PclZip::listContent() for list entry format)
+ // --------------------------------------------------------------------------------
+ function extract()
+ {
+ $v_result=1;
+
+ // ----- Reset the error handler
+ $this->privErrorReset();
+
+ // ----- Check archive
+ if (!$this->privCheckFormat()) {
+ return(0);
+ }
+
+ // ----- Set default values
+ $v_options = array();
+// $v_path = "./";
+ $v_path = '';
+ $v_remove_path = "";
+ $v_remove_all_path = false;
+
+ // ----- Look for variable options arguments
+ $v_size = func_num_args();
+
+ // ----- Default values for option
+ $v_options[PCLZIP_OPT_EXTRACT_AS_STRING] = FALSE;
+
+ // ----- Look for arguments
+ if ($v_size > 0) {
+ // ----- Get the arguments
+ $v_arg_list = func_get_args();
+
+ // ----- Look for first arg
+ if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) {
+
+ // ----- Parse the options
+ $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options,
+ array (PCLZIP_OPT_PATH => 'optional',
+ PCLZIP_OPT_REMOVE_PATH => 'optional',
+ PCLZIP_OPT_REMOVE_ALL_PATH => 'optional',
+ PCLZIP_OPT_ADD_PATH => 'optional',
+ PCLZIP_CB_PRE_EXTRACT => 'optional',
+ PCLZIP_CB_POST_EXTRACT => 'optional',
+ PCLZIP_OPT_SET_CHMOD => 'optional',
+ PCLZIP_OPT_BY_NAME => 'optional',
+ PCLZIP_OPT_BY_EREG => 'optional',
+ PCLZIP_OPT_BY_PREG => 'optional',
+ PCLZIP_OPT_BY_INDEX => 'optional',
+ PCLZIP_OPT_EXTRACT_AS_STRING => 'optional',
+ PCLZIP_OPT_EXTRACT_IN_OUTPUT => 'optional',
+ PCLZIP_OPT_REPLACE_NEWER => 'optional'
+ ,PCLZIP_OPT_STOP_ON_ERROR => 'optional'
+ ,PCLZIP_OPT_EXTRACT_DIR_RESTRICTION => 'optional',
+ PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional',
+ PCLZIP_OPT_TEMP_FILE_ON => 'optional',
+ PCLZIP_OPT_TEMP_FILE_OFF => 'optional'
+ ));
+ if ($v_result != 1) {
+ return 0;
+ }
+
+ // ----- Set the arguments
+ if (isset($v_options[PCLZIP_OPT_PATH])) {
+ $v_path = $v_options[PCLZIP_OPT_PATH];
+ }
+ if (isset($v_options[PCLZIP_OPT_REMOVE_PATH])) {
+ $v_remove_path = $v_options[PCLZIP_OPT_REMOVE_PATH];
+ }
+ if (isset($v_options[PCLZIP_OPT_REMOVE_ALL_PATH])) {
+ $v_remove_all_path = $v_options[PCLZIP_OPT_REMOVE_ALL_PATH];
+ }
+ if (isset($v_options[PCLZIP_OPT_ADD_PATH])) {
+ // ----- Check for '/' in last path char
+ if ((strlen($v_path) > 0) && (substr($v_path, -1) != '/')) {
+ $v_path .= '/';
+ }
+ $v_path .= $v_options[PCLZIP_OPT_ADD_PATH];
+ }
+ }
+
+ // ----- Look for 2 args
+ // Here we need to support the first historic synopsis of the
+ // method.
+ else {
+
+ // ----- Get the first argument
+ $v_path = $v_arg_list[0];
+
+ // ----- Look for the optional second argument
+ if ($v_size == 2) {
+ $v_remove_path = $v_arg_list[1];
+ }
+ else if ($v_size > 2) {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments");
+
+ // ----- Return
+ return 0;
+ }
+ }
+ }
+
+ // ----- Look for default option values
+ $this->privOptionDefaultThreshold($v_options);
+
+ // ----- Trace
+
+ // ----- Call the extracting fct
+ $p_list = array();
+ $v_result = $this->privExtractByRule($p_list, $v_path, $v_remove_path,
+ $v_remove_all_path, $v_options);
+ if ($v_result < 1) {
+ unset($p_list);
+ return(0);
+ }
+
+ // ----- Return
+ return $p_list;
+ }
+ // --------------------------------------------------------------------------------
+
+
+ // --------------------------------------------------------------------------------
+ // Function :
+ // extractByIndex($p_index, $p_path="./", $p_remove_path="")
+ // extractByIndex($p_index, [$p_option, $p_option_value, ...])
+ // Description :
+ // This method supports two synopsis. The first one is historical.
+ // This method is doing a partial extract of the archive.
+ // The extracted files or folders are identified by their index in the
+ // archive (from 0 to n).
+ // Note that if the index identify a folder, only the folder entry is
+ // extracted, not all the files included in the archive.
+ // Parameters :
+ // $p_index : A single index (integer) or a string of indexes of files to
+ // extract. The form of the string is "0,4-6,8-12" with only numbers
+ // and '-' for range or ',' to separate ranges. No spaces or ';'
+ // are allowed.
+ // $p_path : Path where the files and directories are to be extracted
+ // $p_remove_path : First part ('root' part) of the memorized path
+ // (if any similar) to remove while extracting.
+ // Options :
+ // PCLZIP_OPT_PATH :
+ // PCLZIP_OPT_ADD_PATH :
+ // PCLZIP_OPT_REMOVE_PATH :
+ // PCLZIP_OPT_REMOVE_ALL_PATH :
+ // PCLZIP_OPT_EXTRACT_AS_STRING : The files are extracted as strings and
+ // not as files.
+ // The resulting content is in a new field 'content' in the file
+ // structure.
+ // This option must be used alone (any other options are ignored).
+ // PCLZIP_CB_PRE_EXTRACT :
+ // PCLZIP_CB_POST_EXTRACT :
+ // Return Values :
+ // 0 on failure,
+ // The list of the extracted files, with a status of the action.
+ // (see PclZip::listContent() for list entry format)
+ // --------------------------------------------------------------------------------
+ //function extractByIndex($p_index, options...)
+ function extractByIndex($p_index)
+ {
+ $v_result=1;
+
+ // ----- Reset the error handler
+ $this->privErrorReset();
+
+ // ----- Check archive
+ if (!$this->privCheckFormat()) {
+ return(0);
+ }
+
+ // ----- Set default values
+ $v_options = array();
+// $v_path = "./";
+ $v_path = '';
+ $v_remove_path = "";
+ $v_remove_all_path = false;
+
+ // ----- Look for variable options arguments
+ $v_size = func_num_args();
+
+ // ----- Default values for option
+ $v_options[PCLZIP_OPT_EXTRACT_AS_STRING] = FALSE;
+
+ // ----- Look for arguments
+ if ($v_size > 1) {
+ // ----- Get the arguments
+ $v_arg_list = func_get_args();
+
+ // ----- Remove form the options list the first argument
+ array_shift($v_arg_list);
+ $v_size--;
+
+ // ----- Look for first arg
+ if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) {
+
+ // ----- Parse the options
+ $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options,
+ array (PCLZIP_OPT_PATH => 'optional',
+ PCLZIP_OPT_REMOVE_PATH => 'optional',
+ PCLZIP_OPT_REMOVE_ALL_PATH => 'optional',
+ PCLZIP_OPT_EXTRACT_AS_STRING => 'optional',
+ PCLZIP_OPT_ADD_PATH => 'optional',
+ PCLZIP_CB_PRE_EXTRACT => 'optional',
+ PCLZIP_CB_POST_EXTRACT => 'optional',
+ PCLZIP_OPT_SET_CHMOD => 'optional',
+ PCLZIP_OPT_REPLACE_NEWER => 'optional'
+ ,PCLZIP_OPT_STOP_ON_ERROR => 'optional'
+ ,PCLZIP_OPT_EXTRACT_DIR_RESTRICTION => 'optional',
+ PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional',
+ PCLZIP_OPT_TEMP_FILE_ON => 'optional',
+ PCLZIP_OPT_TEMP_FILE_OFF => 'optional'
+ ));
+ if ($v_result != 1) {
+ return 0;
+ }
+
+ // ----- Set the arguments
+ if (isset($v_options[PCLZIP_OPT_PATH])) {
+ $v_path = $v_options[PCLZIP_OPT_PATH];
+ }
+ if (isset($v_options[PCLZIP_OPT_REMOVE_PATH])) {
+ $v_remove_path = $v_options[PCLZIP_OPT_REMOVE_PATH];
+ }
+ if (isset($v_options[PCLZIP_OPT_REMOVE_ALL_PATH])) {
+ $v_remove_all_path = $v_options[PCLZIP_OPT_REMOVE_ALL_PATH];
+ }
+ if (isset($v_options[PCLZIP_OPT_ADD_PATH])) {
+ // ----- Check for '/' in last path char
+ if ((strlen($v_path) > 0) && (substr($v_path, -1) != '/')) {
+ $v_path .= '/';
+ }
+ $v_path .= $v_options[PCLZIP_OPT_ADD_PATH];
+ }
+ if (!isset($v_options[PCLZIP_OPT_EXTRACT_AS_STRING])) {
+ $v_options[PCLZIP_OPT_EXTRACT_AS_STRING] = FALSE;
+ }
+ else {
+ }
+ }
+
+ // ----- Look for 2 args
+ // Here we need to support the first historic synopsis of the
+ // method.
+ else {
+
+ // ----- Get the first argument
+ $v_path = $v_arg_list[0];
+
+ // ----- Look for the optional second argument
+ if ($v_size == 2) {
+ $v_remove_path = $v_arg_list[1];
+ }
+ else if ($v_size > 2) {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments");
+
+ // ----- Return
+ return 0;
+ }
+ }
+ }
+
+ // ----- Trace
+
+ // ----- Trick
+ // Here I want to reuse extractByRule(), so I need to parse the $p_index
+ // with privParseOptions()
+ $v_arg_trick = array (PCLZIP_OPT_BY_INDEX, $p_index);
+ $v_options_trick = array();
+ $v_result = $this->privParseOptions($v_arg_trick, sizeof($v_arg_trick), $v_options_trick,
+ array (PCLZIP_OPT_BY_INDEX => 'optional' ));
+ if ($v_result != 1) {
+ return 0;
+ }
+ $v_options[PCLZIP_OPT_BY_INDEX] = $v_options_trick[PCLZIP_OPT_BY_INDEX];
+
+ // ----- Look for default option values
+ $this->privOptionDefaultThreshold($v_options);
+
+ // ----- Call the extracting fct
+ if (($v_result = $this->privExtractByRule($p_list, $v_path, $v_remove_path, $v_remove_all_path, $v_options)) < 1) {
+ return(0);
+ }
+
+ // ----- Return
+ return $p_list;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function :
+ // delete([$p_option, $p_option_value, ...])
+ // Description :
+ // This method removes files from the archive.
+ // If no parameters are given, then all the archive is emptied.
+ // Parameters :
+ // None or optional arguments.
+ // Options :
+ // PCLZIP_OPT_BY_INDEX :
+ // PCLZIP_OPT_BY_NAME :
+ // PCLZIP_OPT_BY_EREG :
+ // PCLZIP_OPT_BY_PREG :
+ // Return Values :
+ // 0 on failure,
+ // The list of the files which are still present in the archive.
+ // (see PclZip::listContent() for list entry format)
+ // --------------------------------------------------------------------------------
+ function delete()
+ {
+ $v_result=1;
+
+ // ----- Reset the error handler
+ $this->privErrorReset();
+
+ // ----- Check archive
+ if (!$this->privCheckFormat()) {
+ return(0);
+ }
+
+ // ----- Set default values
+ $v_options = array();
+
+ // ----- Look for variable options arguments
+ $v_size = func_num_args();
+
+ // ----- Look for arguments
+ if ($v_size > 0) {
+ // ----- Get the arguments
+ $v_arg_list = func_get_args();
+
+ // ----- Parse the options
+ $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options,
+ array (PCLZIP_OPT_BY_NAME => 'optional',
+ PCLZIP_OPT_BY_EREG => 'optional',
+ PCLZIP_OPT_BY_PREG => 'optional',
+ PCLZIP_OPT_BY_INDEX => 'optional' ));
+ if ($v_result != 1) {
+ return 0;
+ }
+ }
+
+ // ----- Magic quotes trick
+ $this->privDisableMagicQuotes();
+
+ // ----- Call the delete fct
+ $v_list = array();
+ if (($v_result = $this->privDeleteByRule($v_list, $v_options)) != 1) {
+ $this->privSwapBackMagicQuotes();
+ unset($v_list);
+ return(0);
+ }
+
+ // ----- Magic quotes trick
+ $this->privSwapBackMagicQuotes();
+
+ // ----- Return
+ return $v_list;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : deleteByIndex()
+ // Description :
+ // ***** Deprecated *****
+ // delete(PCLZIP_OPT_BY_INDEX, $p_index) should be prefered.
+ // --------------------------------------------------------------------------------
+ function deleteByIndex($p_index)
+ {
+
+ $p_list = $this->delete(PCLZIP_OPT_BY_INDEX, $p_index);
+
+ // ----- Return
+ return $p_list;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : properties()
+ // Description :
+ // This method gives the properties of the archive.
+ // The properties are :
+ // nb : Number of files in the archive
+ // comment : Comment associated with the archive file
+ // status : not_exist, ok
+ // Parameters :
+ // None
+ // Return Values :
+ // 0 on failure,
+ // An array with the archive properties.
+ // --------------------------------------------------------------------------------
+ function properties()
+ {
+
+ // ----- Reset the error handler
+ $this->privErrorReset();
+
+ // ----- Magic quotes trick
+ $this->privDisableMagicQuotes();
+
+ // ----- Check archive
+ if (!$this->privCheckFormat()) {
+ $this->privSwapBackMagicQuotes();
+ return(0);
+ }
+
+ // ----- Default properties
+ $v_prop = array();
+ $v_prop['comment'] = '';
+ $v_prop['nb'] = 0;
+ $v_prop['status'] = 'not_exist';
+
+ // ----- Look if file exists
+ if (@is_file($this->zipname))
+ {
+ // ----- Open the zip file
+ if (($this->zip_fd = @fopen($this->zipname, 'rb')) == 0)
+ {
+ $this->privSwapBackMagicQuotes();
+
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \''.$this->zipname.'\' in binary read mode');
+
+ // ----- Return
+ return 0;
+ }
+
+ // ----- Read the central directory informations
+ $v_central_dir = array();
+ if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1)
+ {
+ $this->privSwapBackMagicQuotes();
+ return 0;
+ }
+
+ // ----- Close the zip file
+ $this->privCloseFd();
+
+ // ----- Set the user attributes
+ $v_prop['comment'] = $v_central_dir['comment'];
+ $v_prop['nb'] = $v_central_dir['entries'];
+ $v_prop['status'] = 'ok';
+ }
+
+ // ----- Magic quotes trick
+ $this->privSwapBackMagicQuotes();
+
+ // ----- Return
+ return $v_prop;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : duplicate()
+ // Description :
+ // This method creates an archive by copying the content of an other one. If
+ // the archive already exist, it is replaced by the new one without any warning.
+ // Parameters :
+ // $p_archive : The filename of a valid archive, or
+ // a valid PclZip object.
+ // Return Values :
+ // 1 on success.
+ // 0 or a negative value on error (error code).
+ // --------------------------------------------------------------------------------
+ function duplicate($p_archive)
+ {
+ $v_result = 1;
+
+ // ----- Reset the error handler
+ $this->privErrorReset();
+
+ // ----- Look if the $p_archive is a PclZip object
+ if ((is_object($p_archive)) && (get_class($p_archive) == 'pclzip'))
+ {
+
+ // ----- Duplicate the archive
+ $v_result = $this->privDuplicate($p_archive->zipname);
+ }
+
+ // ----- Look if the $p_archive is a string (so a filename)
+ else if (is_string($p_archive))
+ {
+
+ // ----- Check that $p_archive is a valid zip file
+ // TBC : Should also check the archive format
+ if (!is_file($p_archive)) {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "No file with filename '".$p_archive."'");
+ $v_result = PCLZIP_ERR_MISSING_FILE;
+ }
+ else {
+ // ----- Duplicate the archive
+ $v_result = $this->privDuplicate($p_archive);
+ }
+ }
+
+ // ----- Invalid variable
+ else
+ {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type p_archive_to_add");
+ $v_result = PCLZIP_ERR_INVALID_PARAMETER;
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : merge()
+ // Description :
+ // This method merge the $p_archive_to_add archive at the end of the current
+ // one ($this).
+ // If the archive ($this) does not exist, the merge becomes a duplicate.
+ // If the $p_archive_to_add archive does not exist, the merge is a success.
+ // Parameters :
+ // $p_archive_to_add : It can be directly the filename of a valid zip archive,
+ // or a PclZip object archive.
+ // Return Values :
+ // 1 on success,
+ // 0 or negative values on error (see below).
+ // --------------------------------------------------------------------------------
+ function merge($p_archive_to_add)
+ {
+ $v_result = 1;
+
+ // ----- Reset the error handler
+ $this->privErrorReset();
+
+ // ----- Check archive
+ if (!$this->privCheckFormat()) {
+ return(0);
+ }
+
+ // ----- Look if the $p_archive_to_add is a PclZip object
+ if ((is_object($p_archive_to_add)) && (get_class($p_archive_to_add) == 'pclzip'))
+ {
+
+ // ----- Merge the archive
+ $v_result = $this->privMerge($p_archive_to_add);
+ }
+
+ // ----- Look if the $p_archive_to_add is a string (so a filename)
+ else if (is_string($p_archive_to_add))
+ {
+
+ // ----- Create a temporary archive
+ $v_object_archive = new PclZip($p_archive_to_add);
+
+ // ----- Merge the archive
+ $v_result = $this->privMerge($v_object_archive);
+ }
+
+ // ----- Invalid variable
+ else
+ {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type p_archive_to_add");
+ $v_result = PCLZIP_ERR_INVALID_PARAMETER;
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+
+
+ // --------------------------------------------------------------------------------
+ // Function : errorCode()
+ // Description :
+ // Parameters :
+ // --------------------------------------------------------------------------------
+ function errorCode()
+ {
+ if (PCLZIP_ERROR_EXTERNAL == 1) {
+ return(PclErrorCode());
+ }
+ else {
+ return($this->error_code);
+ }
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : errorName()
+ // Description :
+ // Parameters :
+ // --------------------------------------------------------------------------------
+ function errorName($p_with_code=false)
+ {
+ $v_name = array ( PCLZIP_ERR_NO_ERROR => 'PCLZIP_ERR_NO_ERROR',
+ PCLZIP_ERR_WRITE_OPEN_FAIL => 'PCLZIP_ERR_WRITE_OPEN_FAIL',
+ PCLZIP_ERR_READ_OPEN_FAIL => 'PCLZIP_ERR_READ_OPEN_FAIL',
+ PCLZIP_ERR_INVALID_PARAMETER => 'PCLZIP_ERR_INVALID_PARAMETER',
+ PCLZIP_ERR_MISSING_FILE => 'PCLZIP_ERR_MISSING_FILE',
+ PCLZIP_ERR_FILENAME_TOO_LONG => 'PCLZIP_ERR_FILENAME_TOO_LONG',
+ PCLZIP_ERR_INVALID_ZIP => 'PCLZIP_ERR_INVALID_ZIP',
+ PCLZIP_ERR_BAD_EXTRACTED_FILE => 'PCLZIP_ERR_BAD_EXTRACTED_FILE',
+ PCLZIP_ERR_DIR_CREATE_FAIL => 'PCLZIP_ERR_DIR_CREATE_FAIL',
+ PCLZIP_ERR_BAD_EXTENSION => 'PCLZIP_ERR_BAD_EXTENSION',
+ PCLZIP_ERR_BAD_FORMAT => 'PCLZIP_ERR_BAD_FORMAT',
+ PCLZIP_ERR_DELETE_FILE_FAIL => 'PCLZIP_ERR_DELETE_FILE_FAIL',
+ PCLZIP_ERR_RENAME_FILE_FAIL => 'PCLZIP_ERR_RENAME_FILE_FAIL',
+ PCLZIP_ERR_BAD_CHECKSUM => 'PCLZIP_ERR_BAD_CHECKSUM',
+ PCLZIP_ERR_INVALID_ARCHIVE_ZIP => 'PCLZIP_ERR_INVALID_ARCHIVE_ZIP',
+ PCLZIP_ERR_MISSING_OPTION_VALUE => 'PCLZIP_ERR_MISSING_OPTION_VALUE',
+ PCLZIP_ERR_INVALID_OPTION_VALUE => 'PCLZIP_ERR_INVALID_OPTION_VALUE',
+ PCLZIP_ERR_UNSUPPORTED_COMPRESSION => 'PCLZIP_ERR_UNSUPPORTED_COMPRESSION',
+ PCLZIP_ERR_UNSUPPORTED_ENCRYPTION => 'PCLZIP_ERR_UNSUPPORTED_ENCRYPTION'
+ ,PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE => 'PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE'
+ ,PCLZIP_ERR_DIRECTORY_RESTRICTION => 'PCLZIP_ERR_DIRECTORY_RESTRICTION'
+ );
+
+ if (isset($v_name[$this->error_code])) {
+ $v_value = $v_name[$this->error_code];
+ }
+ else {
+ $v_value = 'NoName';
+ }
+
+ if ($p_with_code) {
+ return($v_value.' ('.$this->error_code.')');
+ }
+ else {
+ return($v_value);
+ }
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : errorInfo()
+ // Description :
+ // Parameters :
+ // --------------------------------------------------------------------------------
+ function errorInfo($p_full=false)
+ {
+ if (PCLZIP_ERROR_EXTERNAL == 1) {
+ return(PclErrorString());
+ }
+ else {
+ if ($p_full) {
+ return($this->errorName(true)." : ".$this->error_string);
+ }
+ else {
+ return($this->error_string." [code ".$this->error_code."]");
+ }
+ }
+ }
+ // --------------------------------------------------------------------------------
+
+
+// --------------------------------------------------------------------------------
+// ***** UNDER THIS LINE ARE DEFINED PRIVATE INTERNAL FUNCTIONS *****
+// ***** *****
+// ***** THESES FUNCTIONS MUST NOT BE USED DIRECTLY *****
+// --------------------------------------------------------------------------------
+
+
+
+ // --------------------------------------------------------------------------------
+ // Function : privCheckFormat()
+ // Description :
+ // This method check that the archive exists and is a valid zip archive.
+ // Several level of check exists. (futur)
+ // Parameters :
+ // $p_level : Level of check. Default 0.
+ // 0 : Check the first bytes (magic codes) (default value))
+ // 1 : 0 + Check the central directory (futur)
+ // 2 : 1 + Check each file header (futur)
+ // Return Values :
+ // true on success,
+ // false on error, the error code is set.
+ // --------------------------------------------------------------------------------
+ function privCheckFormat($p_level=0)
+ {
+ $v_result = true;
+
+ // ----- Reset the file system cache
+ clearstatcache();
+
+ // ----- Reset the error handler
+ $this->privErrorReset();
+
+ // ----- Look if the file exits
+ if (!is_file($this->zipname)) {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "Missing archive file '".$this->zipname."'");
+ return(false);
+ }
+
+ // ----- Check that the file is readeable
+ if (!is_readable($this->zipname)) {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to read archive '".$this->zipname."'");
+ return(false);
+ }
+
+ // ----- Check the magic code
+ // TBC
+
+ // ----- Check the central header
+ // TBC
+
+ // ----- Check each file header
+ // TBC
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privParseOptions()
+ // Description :
+ // This internal methods reads the variable list of arguments ($p_options_list,
+ // $p_size) and generate an array with the options and values ($v_result_list).
+ // $v_requested_options contains the options that can be present and those that
+ // must be present.
+ // $v_requested_options is an array, with the option value as key, and 'optional',
+ // or 'mandatory' as value.
+ // Parameters :
+ // See above.
+ // Return Values :
+ // 1 on success.
+ // 0 on failure.
+ // --------------------------------------------------------------------------------
+ function privParseOptions(&$p_options_list, $p_size, &$v_result_list, $v_requested_options=false)
+ {
+ $v_result=1;
+
+ // ----- Read the options
+ $i=0;
+ while ($i<$p_size) {
+
+ // ----- Check if the option is supported
+ if (!isset($v_requested_options[$p_options_list[$i]])) {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid optional parameter '".$p_options_list[$i]."' for this method");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Look for next option
+ switch ($p_options_list[$i]) {
+ // ----- Look for options that request a path value
+ case PCLZIP_OPT_PATH :
+ case PCLZIP_OPT_REMOVE_PATH :
+ case PCLZIP_OPT_ADD_PATH :
+ // ----- Check the number of parameters
+ if (($i+1) >= $p_size) {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Get the value
+ $v_result_list[$p_options_list[$i]] = PclZipUtilTranslateWinPath($p_options_list[$i+1], FALSE);
+ $i++;
+ break;
+
+ case PCLZIP_OPT_TEMP_FILE_THRESHOLD :
+ // ----- Check the number of parameters
+ if (($i+1) >= $p_size) {
+ PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+ return PclZip::errorCode();
+ }
+
+ // ----- Check for incompatible options
+ if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_OFF])) {
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '".PclZipUtilOptionText($p_options_list[$i])."' can not be used with option 'PCLZIP_OPT_TEMP_FILE_OFF'");
+ return PclZip::errorCode();
+ }
+
+ // ----- Check the value
+ $v_value = $p_options_list[$i+1];
+ if ((!is_integer($v_value)) || ($v_value<0)) {
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Integer expected for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+ return PclZip::errorCode();
+ }
+
+ // ----- Get the value (and convert it in bytes)
+ $v_result_list[$p_options_list[$i]] = $v_value*1048576;
+ $i++;
+ break;
+
+ case PCLZIP_OPT_TEMP_FILE_ON :
+ // ----- Check for incompatible options
+ if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_OFF])) {
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '".PclZipUtilOptionText($p_options_list[$i])."' can not be used with option 'PCLZIP_OPT_TEMP_FILE_OFF'");
+ return PclZip::errorCode();
+ }
+
+ $v_result_list[$p_options_list[$i]] = true;
+ break;
+
+ case PCLZIP_OPT_TEMP_FILE_OFF :
+ // ----- Check for incompatible options
+ if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_ON])) {
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '".PclZipUtilOptionText($p_options_list[$i])."' can not be used with option 'PCLZIP_OPT_TEMP_FILE_ON'");
+ return PclZip::errorCode();
+ }
+ // ----- Check for incompatible options
+ if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_THRESHOLD])) {
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '".PclZipUtilOptionText($p_options_list[$i])."' can not be used with option 'PCLZIP_OPT_TEMP_FILE_THRESHOLD'");
+ return PclZip::errorCode();
+ }
+
+ $v_result_list[$p_options_list[$i]] = true;
+ break;
+
+ case PCLZIP_OPT_EXTRACT_DIR_RESTRICTION :
+ // ----- Check the number of parameters
+ if (($i+1) >= $p_size) {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Get the value
+ if ( is_string($p_options_list[$i+1])
+ && ($p_options_list[$i+1] != '')) {
+ $v_result_list[$p_options_list[$i]] = PclZipUtilTranslateWinPath($p_options_list[$i+1], FALSE);
+ $i++;
+ }
+ else {
+ }
+ break;
+
+ // ----- Look for options that request an array of string for value
+ case PCLZIP_OPT_BY_NAME :
+ // ----- Check the number of parameters
+ if (($i+1) >= $p_size) {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Get the value
+ if (is_string($p_options_list[$i+1])) {
+ $v_result_list[$p_options_list[$i]][0] = $p_options_list[$i+1];
+ }
+ else if (is_array($p_options_list[$i+1])) {
+ $v_result_list[$p_options_list[$i]] = $p_options_list[$i+1];
+ }
+ else {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Wrong parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+ $i++;
+ break;
+
+ // ----- Look for options that request an EREG or PREG expression
+ case PCLZIP_OPT_BY_EREG :
+ // ereg() is deprecated starting with PHP 5.3. Move PCLZIP_OPT_BY_EREG
+ // to PCLZIP_OPT_BY_PREG
+ $p_options_list[$i] = PCLZIP_OPT_BY_PREG;
+ case PCLZIP_OPT_BY_PREG :
+ //case PCLZIP_OPT_CRYPT :
+ // ----- Check the number of parameters
+ if (($i+1) >= $p_size) {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Get the value
+ if (is_string($p_options_list[$i+1])) {
+ $v_result_list[$p_options_list[$i]] = $p_options_list[$i+1];
+ }
+ else {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Wrong parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+ $i++;
+ break;
+
+ // ----- Look for options that takes a string
+ case PCLZIP_OPT_COMMENT :
+ case PCLZIP_OPT_ADD_COMMENT :
+ case PCLZIP_OPT_PREPEND_COMMENT :
+ // ----- Check the number of parameters
+ if (($i+1) >= $p_size) {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE,
+ "Missing parameter value for option '"
+ .PclZipUtilOptionText($p_options_list[$i])
+ ."'");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Get the value
+ if (is_string($p_options_list[$i+1])) {
+ $v_result_list[$p_options_list[$i]] = $p_options_list[$i+1];
+ }
+ else {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE,
+ "Wrong parameter value for option '"
+ .PclZipUtilOptionText($p_options_list[$i])
+ ."'");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+ $i++;
+ break;
+
+ // ----- Look for options that request an array of index
+ case PCLZIP_OPT_BY_INDEX :
+ // ----- Check the number of parameters
+ if (($i+1) >= $p_size) {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Get the value
+ $v_work_list = array();
+ if (is_string($p_options_list[$i+1])) {
+
+ // ----- Remove spaces
+ $p_options_list[$i+1] = strtr($p_options_list[$i+1], ' ', '');
+
+ // ----- Parse items
+ $v_work_list = explode(",", $p_options_list[$i+1]);
+ }
+ else if (is_integer($p_options_list[$i+1])) {
+ $v_work_list[0] = $p_options_list[$i+1].'-'.$p_options_list[$i+1];
+ }
+ else if (is_array($p_options_list[$i+1])) {
+ $v_work_list = $p_options_list[$i+1];
+ }
+ else {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Value must be integer, string or array for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Reduce the index list
+ // each index item in the list must be a couple with a start and
+ // an end value : [0,3], [5-5], [8-10], ...
+ // ----- Check the format of each item
+ $v_sort_flag=false;
+ $v_sort_value=0;
+ for ($j=0; $j<sizeof($v_work_list); $j++) {
+ // ----- Explode the item
+ $v_item_list = explode("-", $v_work_list[$j]);
+ $v_size_item_list = sizeof($v_item_list);
+
+ // ----- TBC : Here we might check that each item is a
+ // real integer ...
+
+ // ----- Look for single value
+ if ($v_size_item_list == 1) {
+ // ----- Set the option value
+ $v_result_list[$p_options_list[$i]][$j]['start'] = $v_item_list[0];
+ $v_result_list[$p_options_list[$i]][$j]['end'] = $v_item_list[0];
+ }
+ elseif ($v_size_item_list == 2) {
+ // ----- Set the option value
+ $v_result_list[$p_options_list[$i]][$j]['start'] = $v_item_list[0];
+ $v_result_list[$p_options_list[$i]][$j]['end'] = $v_item_list[1];
+ }
+ else {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Too many values in index range for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+
+ // ----- Look for list sort
+ if ($v_result_list[$p_options_list[$i]][$j]['start'] < $v_sort_value) {
+ $v_sort_flag=true;
+
+ // ----- TBC : An automatic sort should be writen ...
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Invalid order of index range for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+ $v_sort_value = $v_result_list[$p_options_list[$i]][$j]['start'];
+ }
+
+ // ----- Sort the items
+ if ($v_sort_flag) {
+ // TBC : To Be Completed
+ }
+
+ // ----- Next option
+ $i++;
+ break;
+
+ // ----- Look for options that request no value
+ case PCLZIP_OPT_REMOVE_ALL_PATH :
+ case PCLZIP_OPT_EXTRACT_AS_STRING :
+ case PCLZIP_OPT_NO_COMPRESSION :
+ case PCLZIP_OPT_EXTRACT_IN_OUTPUT :
+ case PCLZIP_OPT_REPLACE_NEWER :
+ case PCLZIP_OPT_STOP_ON_ERROR :
+ $v_result_list[$p_options_list[$i]] = true;
+ break;
+
+ // ----- Look for options that request an octal value
+ case PCLZIP_OPT_SET_CHMOD :
+ // ----- Check the number of parameters
+ if (($i+1) >= $p_size) {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Get the value
+ $v_result_list[$p_options_list[$i]] = $p_options_list[$i+1];
+ $i++;
+ break;
+
+ // ----- Look for options that request a call-back
+ case PCLZIP_CB_PRE_EXTRACT :
+ case PCLZIP_CB_POST_EXTRACT :
+ case PCLZIP_CB_PRE_ADD :
+ case PCLZIP_CB_POST_ADD :
+ /* for futur use
+ case PCLZIP_CB_PRE_DELETE :
+ case PCLZIP_CB_POST_DELETE :
+ case PCLZIP_CB_PRE_LIST :
+ case PCLZIP_CB_POST_LIST :
+ */
+ // ----- Check the number of parameters
+ if (($i+1) >= $p_size) {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Get the value
+ $v_function_name = $p_options_list[$i+1];
+
+ // ----- Check that the value is a valid existing function
+ if (!function_exists($v_function_name)) {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Function '".$v_function_name."()' is not an existing function for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Set the attribute
+ $v_result_list[$p_options_list[$i]] = $v_function_name;
+ $i++;
+ break;
+
+ default :
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER,
+ "Unknown parameter '"
+ .$p_options_list[$i]."'");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Next options
+ $i++;
+ }
+
+ // ----- Look for mandatory options
+ if ($v_requested_options !== false) {
+ for ($key=reset($v_requested_options); $key=key($v_requested_options); $key=next($v_requested_options)) {
+ // ----- Look for mandatory option
+ if ($v_requested_options[$key] == 'mandatory') {
+ // ----- Look if present
+ if (!isset($v_result_list[$key])) {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Missing mandatory parameter ".PclZipUtilOptionText($key)."(".$key.")");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+ }
+ }
+ }
+
+ // ----- Look for default values
+ if (!isset($v_result_list[PCLZIP_OPT_TEMP_FILE_THRESHOLD])) {
+
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privOptionDefaultThreshold()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privOptionDefaultThreshold(&$p_options)
+ {
+ $v_result=1;
+
+ if (isset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD])
+ || isset($p_options[PCLZIP_OPT_TEMP_FILE_OFF])) {
+ return $v_result;
+ }
+
+ // ----- Get 'memory_limit' configuration value
+ $v_memory_limit = ini_get('memory_limit');
+ $v_memory_limit = trim($v_memory_limit);
+ $last = strtolower(substr($v_memory_limit, -1));
+
+ if($last == 'g')
+ //$v_memory_limit = $v_memory_limit*1024*1024*1024;
+ $v_memory_limit = $v_memory_limit*1073741824;
+ if($last == 'm')
+ //$v_memory_limit = $v_memory_limit*1024*1024;
+ $v_memory_limit = $v_memory_limit*1048576;
+ if($last == 'k')
+ $v_memory_limit = $v_memory_limit*1024;
+
+ $p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] = floor($v_memory_limit*PCLZIP_TEMPORARY_FILE_RATIO);
+
+
+ // ----- Sanity check : No threshold if value lower than 1M
+ if ($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] < 1048576) {
+ unset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]);
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privFileDescrParseAtt()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // 1 on success.
+ // 0 on failure.
+ // --------------------------------------------------------------------------------
+ function privFileDescrParseAtt(&$p_file_list, &$p_filedescr, $v_options, $v_requested_options=false)
+ {
+ $v_result=1;
+
+ // ----- For each file in the list check the attributes
+ foreach ($p_file_list as $v_key => $v_value) {
+
+ // ----- Check if the option is supported
+ if (!isset($v_requested_options[$v_key])) {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid file attribute '".$v_key."' for this file");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Look for attribute
+ switch ($v_key) {
+ case PCLZIP_ATT_FILE_NAME :
+ if (!is_string($v_value)) {
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type ".gettype($v_value).". String expected for attribute '".PclZipUtilOptionText($v_key)."'");
+ return PclZip::errorCode();
+ }
+
+ $p_filedescr['filename'] = PclZipUtilPathReduction($v_value);
+
+ if ($p_filedescr['filename'] == '') {
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty filename for attribute '".PclZipUtilOptionText($v_key)."'");
+ return PclZip::errorCode();
+ }
+
+ break;
+
+ case PCLZIP_ATT_FILE_NEW_SHORT_NAME :
+ if (!is_string($v_value)) {
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type ".gettype($v_value).". String expected for attribute '".PclZipUtilOptionText($v_key)."'");
+ return PclZip::errorCode();
+ }
+
+ $p_filedescr['new_short_name'] = PclZipUtilPathReduction($v_value);
+
+ if ($p_filedescr['new_short_name'] == '') {
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty short filename for attribute '".PclZipUtilOptionText($v_key)."'");
+ return PclZip::errorCode();
+ }
+ break;
+
+ case PCLZIP_ATT_FILE_NEW_FULL_NAME :
+ if (!is_string($v_value)) {
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type ".gettype($v_value).". String expected for attribute '".PclZipUtilOptionText($v_key)."'");
+ return PclZip::errorCode();
+ }
+
+ $p_filedescr['new_full_name'] = PclZipUtilPathReduction($v_value);
+
+ if ($p_filedescr['new_full_name'] == '') {
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty full filename for attribute '".PclZipUtilOptionText($v_key)."'");
+ return PclZip::errorCode();
+ }
+ break;
+
+ // ----- Look for options that takes a string
+ case PCLZIP_ATT_FILE_COMMENT :
+ if (!is_string($v_value)) {
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type ".gettype($v_value).". String expected for attribute '".PclZipUtilOptionText($v_key)."'");
+ return PclZip::errorCode();
+ }
+
+ $p_filedescr['comment'] = $v_value;
+ break;
+
+ case PCLZIP_ATT_FILE_MTIME :
+ if (!is_integer($v_value)) {
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type ".gettype($v_value).". Integer expected for attribute '".PclZipUtilOptionText($v_key)."'");
+ return PclZip::errorCode();
+ }
+
+ $p_filedescr['mtime'] = $v_value;
+ break;
+
+ case PCLZIP_ATT_FILE_CONTENT :
+ $p_filedescr['content'] = $v_value;
+ break;
+
+ default :
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER,
+ "Unknown parameter '".$v_key."'");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Look for mandatory options
+ if ($v_requested_options !== false) {
+ for ($key=reset($v_requested_options); $key=key($v_requested_options); $key=next($v_requested_options)) {
+ // ----- Look for mandatory option
+ if ($v_requested_options[$key] == 'mandatory') {
+ // ----- Look if present
+ if (!isset($p_file_list[$key])) {
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Missing mandatory parameter ".PclZipUtilOptionText($key)."(".$key.")");
+ return PclZip::errorCode();
+ }
+ }
+ }
+ }
+
+ // end foreach
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privFileDescrExpand()
+ // Description :
+ // This method look for each item of the list to see if its a file, a folder
+ // or a string to be added as file. For any other type of files (link, other)
+ // just ignore the item.
+ // Then prepare the information that will be stored for that file.
+ // When its a folder, expand the folder with all the files that are in that
+ // folder (recursively).
+ // Parameters :
+ // Return Values :
+ // 1 on success.
+ // 0 on failure.
+ // --------------------------------------------------------------------------------
+ function privFileDescrExpand(&$p_filedescr_list, &$p_options)
+ {
+ $v_result=1;
+
+ // ----- Create a result list
+ $v_result_list = array();
+
+ // ----- Look each entry
+ for ($i=0; $i<sizeof($p_filedescr_list); $i++) {
+
+ // ----- Get filedescr
+ $v_descr = $p_filedescr_list[$i];
+
+ // ----- Reduce the filename
+ $v_descr['filename'] = PclZipUtilTranslateWinPath($v_descr['filename'], false);
+ $v_descr['filename'] = PclZipUtilPathReduction($v_descr['filename']);
+
+ // ----- Look for real file or folder
+ if (file_exists($v_descr['filename'])) {
+ if (@is_file($v_descr['filename'])) {
+ $v_descr['type'] = 'file';
+ }
+ else if (@is_dir($v_descr['filename'])) {
+ $v_descr['type'] = 'folder';
+ }
+ else if (@is_link($v_descr['filename'])) {
+ // skip
+ continue;
+ }
+ else {
+ // skip
+ continue;
+ }
+ }
+
+ // ----- Look for string added as file
+ else if (isset($v_descr['content'])) {
+ $v_descr['type'] = 'virtual_file';
+ }
+
+ // ----- Missing file
+ else {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "File '".$v_descr['filename']."' does not exist");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Calculate the stored filename
+ $this->privCalculateStoredFilename($v_descr, $p_options);
+
+ // ----- Add the descriptor in result list
+ $v_result_list[sizeof($v_result_list)] = $v_descr;
+
+ // ----- Look for folder
+ if ($v_descr['type'] == 'folder') {
+ // ----- List of items in folder
+ $v_dirlist_descr = array();
+ $v_dirlist_nb = 0;
+ if ($v_folder_handler = @opendir($v_descr['filename'])) {
+ while (($v_item_handler = @readdir($v_folder_handler)) !== false) {
+
+ // ----- Skip '.' and '..'
+ if (($v_item_handler == '.') || ($v_item_handler == '..')) {
+ continue;
+ }
+
+ // ----- Compose the full filename
+ $v_dirlist_descr[$v_dirlist_nb]['filename'] = $v_descr['filename'].'/'.$v_item_handler;
+
+ // ----- Look for different stored filename
+ // Because the name of the folder was changed, the name of the
+ // files/sub-folders also change
+ if (($v_descr['stored_filename'] != $v_descr['filename'])
+ && (!isset($p_options[PCLZIP_OPT_REMOVE_ALL_PATH]))) {
+ if ($v_descr['stored_filename'] != '') {
+ $v_dirlist_descr[$v_dirlist_nb]['new_full_name'] = $v_descr['stored_filename'].'/'.$v_item_handler;
+ }
+ else {
+ $v_dirlist_descr[$v_dirlist_nb]['new_full_name'] = $v_item_handler;
+ }
+ }
+
+ $v_dirlist_nb++;
+ }
+
+ @closedir($v_folder_handler);
+ }
+ else {
+ // TBC : unable to open folder in read mode
+ }
+
+ // ----- Expand each element of the list
+ if ($v_dirlist_nb != 0) {
+ // ----- Expand
+ if (($v_result = $this->privFileDescrExpand($v_dirlist_descr, $p_options)) != 1) {
+ return $v_result;
+ }
+
+ // ----- Concat the resulting list
+ $v_result_list = array_merge($v_result_list, $v_dirlist_descr);
+ }
+ else {
+ }
+
+ // ----- Free local array
+ unset($v_dirlist_descr);
+ }
+ }
+
+ // ----- Get the result list
+ $p_filedescr_list = $v_result_list;
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privCreate()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privCreate($p_filedescr_list, &$p_result_list, &$p_options)
+ {
+ $v_result=1;
+ $v_list_detail = array();
+
+ // ----- Magic quotes trick
+ $this->privDisableMagicQuotes();
+
+ // ----- Open the file in write mode
+ if (($v_result = $this->privOpenFd('wb')) != 1)
+ {
+ // ----- Return
+ return $v_result;
+ }
+
+ // ----- Add the list of files
+ $v_result = $this->privAddList($p_filedescr_list, $p_result_list, $p_options);
+
+ // ----- Close
+ $this->privCloseFd();
+
+ // ----- Magic quotes trick
+ $this->privSwapBackMagicQuotes();
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privAdd()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privAdd($p_filedescr_list, &$p_result_list, &$p_options)
+ {
+ $v_result=1;
+ $v_list_detail = array();
+
+ // ----- Look if the archive exists or is empty
+ if ((!is_file($this->zipname)) || (filesize($this->zipname) == 0))
+ {
+
+ // ----- Do a create
+ $v_result = $this->privCreate($p_filedescr_list, $p_result_list, $p_options);
+
+ // ----- Return
+ return $v_result;
+ }
+ // ----- Magic quotes trick
+ $this->privDisableMagicQuotes();
+
+ // ----- Open the zip file
+ if (($v_result=$this->privOpenFd('rb')) != 1)
+ {
+ // ----- Magic quotes trick
+ $this->privSwapBackMagicQuotes();
+
+ // ----- Return
+ return $v_result;
+ }
+
+ // ----- Read the central directory informations
+ $v_central_dir = array();
+ if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1)
+ {
+ $this->privCloseFd();
+ $this->privSwapBackMagicQuotes();
+ return $v_result;
+ }
+
+ // ----- Go to beginning of File
+ @rewind($this->zip_fd);
+
+ // ----- Creates a temporay file
+ $v_zip_temp_name = PCLZIP_TEMPORARY_DIR.uniqid('pclzip-').'.tmp';
+
+ // ----- Open the temporary file in write mode
+ if (($v_zip_temp_fd = @fopen($v_zip_temp_name, 'wb')) == 0)
+ {
+ $this->privCloseFd();
+ $this->privSwapBackMagicQuotes();
+
+ PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \''.$v_zip_temp_name.'\' in binary write mode');
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Copy the files from the archive to the temporary file
+ // TBC : Here I should better append the file and go back to erase the central dir
+ $v_size = $v_central_dir['offset'];
+ while ($v_size != 0)
+ {
+ $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+ $v_buffer = fread($this->zip_fd, $v_read_size);
+ @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size);
+ $v_size -= $v_read_size;
+ }
+
+ // ----- Swap the file descriptor
+ // Here is a trick : I swap the temporary fd with the zip fd, in order to use
+ // the following methods on the temporary fil and not the real archive
+ $v_swap = $this->zip_fd;
+ $this->zip_fd = $v_zip_temp_fd;
+ $v_zip_temp_fd = $v_swap;
+
+ // ----- Add the files
+ $v_header_list = array();
+ if (($v_result = $this->privAddFileList($p_filedescr_list, $v_header_list, $p_options)) != 1)
+ {
+ fclose($v_zip_temp_fd);
+ $this->privCloseFd();
+ @unlink($v_zip_temp_name);
+ $this->privSwapBackMagicQuotes();
+
+ // ----- Return
+ return $v_result;
+ }
+
+ // ----- Store the offset of the central dir
+ $v_offset = @ftell($this->zip_fd);
+
+ // ----- Copy the block of file headers from the old archive
+ $v_size = $v_central_dir['size'];
+ while ($v_size != 0)
+ {
+ $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+ $v_buffer = @fread($v_zip_temp_fd, $v_read_size);
+ @fwrite($this->zip_fd, $v_buffer, $v_read_size);
+ $v_size -= $v_read_size;
+ }
+
+ // ----- Create the Central Dir files header
+ for ($i=0, $v_count=0; $i<sizeof($v_header_list); $i++)
+ {
+ // ----- Create the file header
+ if ($v_header_list[$i]['status'] == 'ok') {
+ if (($v_result = $this->privWriteCentralFileHeader($v_header_list[$i])) != 1) {
+ fclose($v_zip_temp_fd);
+ $this->privCloseFd();
+ @unlink($v_zip_temp_name);
+ $this->privSwapBackMagicQuotes();
+
+ // ----- Return
+ return $v_result;
+ }
+ $v_count++;
+ }
+
+ // ----- Transform the header to a 'usable' info
+ $this->privConvertHeader2FileInfo($v_header_list[$i], $p_result_list[$i]);
+ }
+
+ // ----- Zip file comment
+ $v_comment = $v_central_dir['comment'];
+ if (isset($p_options[PCLZIP_OPT_COMMENT])) {
+ $v_comment = $p_options[PCLZIP_OPT_COMMENT];
+ }
+ if (isset($p_options[PCLZIP_OPT_ADD_COMMENT])) {
+ $v_comment = $v_comment.$p_options[PCLZIP_OPT_ADD_COMMENT];
+ }
+ if (isset($p_options[PCLZIP_OPT_PREPEND_COMMENT])) {
+ $v_comment = $p_options[PCLZIP_OPT_PREPEND_COMMENT].$v_comment;
+ }
+
+ // ----- Calculate the size of the central header
+ $v_size = @ftell($this->zip_fd)-$v_offset;
+
+ // ----- Create the central dir footer
+ if (($v_result = $this->privWriteCentralHeader($v_count+$v_central_dir['entries'], $v_size, $v_offset, $v_comment)) != 1)
+ {
+ // ----- Reset the file list
+ unset($v_header_list);
+ $this->privSwapBackMagicQuotes();
+
+ // ----- Return
+ return $v_result;
+ }
+
+ // ----- Swap back the file descriptor
+ $v_swap = $this->zip_fd;
+ $this->zip_fd = $v_zip_temp_fd;
+ $v_zip_temp_fd = $v_swap;
+
+ // ----- Close
+ $this->privCloseFd();
+
+ // ----- Close the temporary file
+ @fclose($v_zip_temp_fd);
+
+ // ----- Magic quotes trick
+ $this->privSwapBackMagicQuotes();
+
+ // ----- Delete the zip file
+ // TBC : I should test the result ...
+ @unlink($this->zipname);
+
+ // ----- Rename the temporary file
+ // TBC : I should test the result ...
+ //@rename($v_zip_temp_name, $this->zipname);
+ PclZipUtilRename($v_zip_temp_name, $this->zipname);
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privOpenFd()
+ // Description :
+ // Parameters :
+ // --------------------------------------------------------------------------------
+ function privOpenFd($p_mode)
+ {
+ $v_result=1;
+
+ // ----- Look if already open
+ if ($this->zip_fd != 0)
+ {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Zip file \''.$this->zipname.'\' already open');
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Open the zip file
+ if (($this->zip_fd = @fopen($this->zipname, $p_mode)) == 0)
+ {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \''.$this->zipname.'\' in '.$p_mode.' mode');
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privCloseFd()
+ // Description :
+ // Parameters :
+ // --------------------------------------------------------------------------------
+ function privCloseFd()
+ {
+ $v_result=1;
+
+ if ($this->zip_fd != 0)
+ @fclose($this->zip_fd);
+ $this->zip_fd = 0;
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privAddList()
+ // Description :
+ // $p_add_dir and $p_remove_dir will give the ability to memorize a path which is
+ // different from the real path of the file. This is usefull if you want to have PclTar
+ // running in any directory, and memorize relative path from an other directory.
+ // Parameters :
+ // $p_list : An array containing the file or directory names to add in the tar
+ // $p_result_list : list of added files with their properties (specially the status field)
+ // $p_add_dir : Path to add in the filename path archived
+ // $p_remove_dir : Path to remove in the filename path archived
+ // Return Values :
+ // --------------------------------------------------------------------------------
+// function privAddList($p_list, &$p_result_list, $p_add_dir, $p_remove_dir, $p_remove_all_dir, &$p_options)
+ function privAddList($p_filedescr_list, &$p_result_list, &$p_options)
+ {
+ $v_result=1;
+
+ // ----- Add the files
+ $v_header_list = array();
+ if (($v_result = $this->privAddFileList($p_filedescr_list, $v_header_list, $p_options)) != 1)
+ {
+ // ----- Return
+ return $v_result;
+ }
+
+ // ----- Store the offset of the central dir
+ $v_offset = @ftell($this->zip_fd);
+
+ // ----- Create the Central Dir files header
+ for ($i=0,$v_count=0; $i<sizeof($v_header_list); $i++)
+ {
+ // ----- Create the file header
+ if ($v_header_list[$i]['status'] == 'ok') {
+ if (($v_result = $this->privWriteCentralFileHeader($v_header_list[$i])) != 1) {
+ // ----- Return
+ return $v_result;
+ }
+ $v_count++;
+ }
+
+ // ----- Transform the header to a 'usable' info
+ $this->privConvertHeader2FileInfo($v_header_list[$i], $p_result_list[$i]);
+ }
+
+ // ----- Zip file comment
+ $v_comment = '';
+ if (isset($p_options[PCLZIP_OPT_COMMENT])) {
+ $v_comment = $p_options[PCLZIP_OPT_COMMENT];
+ }
+
+ // ----- Calculate the size of the central header
+ $v_size = @ftell($this->zip_fd)-$v_offset;
+
+ // ----- Create the central dir footer
+ if (($v_result = $this->privWriteCentralHeader($v_count, $v_size, $v_offset, $v_comment)) != 1)
+ {
+ // ----- Reset the file list
+ unset($v_header_list);
+
+ // ----- Return
+ return $v_result;
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privAddFileList()
+ // Description :
+ // Parameters :
+ // $p_filedescr_list : An array containing the file description
+ // or directory names to add in the zip
+ // $p_result_list : list of added files with their properties (specially the status field)
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privAddFileList($p_filedescr_list, &$p_result_list, &$p_options)
+ {
+ $v_result=1;
+ $v_header = array();
+
+ // ----- Recuperate the current number of elt in list
+ $v_nb = sizeof($p_result_list);
+
+ // ----- Loop on the files
+ for ($j=0; ($j<sizeof($p_filedescr_list)) && ($v_result==1); $j++) {
+ // ----- Format the filename
+ $p_filedescr_list[$j]['filename']
+ = PclZipUtilTranslateWinPath($p_filedescr_list[$j]['filename'], false);
+
+
+ // ----- Skip empty file names
+ // TBC : Can this be possible ? not checked in DescrParseAtt ?
+ if ($p_filedescr_list[$j]['filename'] == "") {
+ continue;
+ }
+
+ // ----- Check the filename
+ if ( ($p_filedescr_list[$j]['type'] != 'virtual_file')
+ && (!file_exists($p_filedescr_list[$j]['filename']))) {
+ PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "File '".$p_filedescr_list[$j]['filename']."' does not exist");
+ return PclZip::errorCode();
+ }
+
+ // ----- Look if it is a file or a dir with no all path remove option
+ // or a dir with all its path removed
+// if ( (is_file($p_filedescr_list[$j]['filename']))
+// || ( is_dir($p_filedescr_list[$j]['filename'])
+ if ( ($p_filedescr_list[$j]['type'] == 'file')
+ || ($p_filedescr_list[$j]['type'] == 'virtual_file')
+ || ( ($p_filedescr_list[$j]['type'] == 'folder')
+ && ( !isset($p_options[PCLZIP_OPT_REMOVE_ALL_PATH])
+ || !$p_options[PCLZIP_OPT_REMOVE_ALL_PATH]))
+ ) {
+
+ // ----- Add the file
+ $v_result = $this->privAddFile($p_filedescr_list[$j], $v_header,
+ $p_options);
+ if ($v_result != 1) {
+ return $v_result;
+ }
+
+ // ----- Store the file infos
+ $p_result_list[$v_nb++] = $v_header;
+ }
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privAddFile()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privAddFile($p_filedescr, &$p_header, &$p_options)
+ {
+ $v_result=1;
+
+ // ----- Working variable
+ $p_filename = $p_filedescr['filename'];
+
+ // TBC : Already done in the fileAtt check ... ?
+ if ($p_filename == "") {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid file list parameter (invalid or empty list)");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Look for a stored different filename
+ /* TBC : Removed
+ if (isset($p_filedescr['stored_filename'])) {
+ $v_stored_filename = $p_filedescr['stored_filename'];
+ }
+ else {
+ $v_stored_filename = $p_filedescr['stored_filename'];
+ }
+ */
+
+ // ----- Set the file properties
+ clearstatcache();
+ $p_header['version'] = 20;
+ $p_header['version_extracted'] = 10;
+ $p_header['flag'] = 0;
+ $p_header['compression'] = 0;
+ $p_header['crc'] = 0;
+ $p_header['compressed_size'] = 0;
+ $p_header['filename_len'] = strlen($p_filename);
+ $p_header['extra_len'] = 0;
+ $p_header['disk'] = 0;
+ $p_header['internal'] = 0;
+ $p_header['offset'] = 0;
+ $p_header['filename'] = $p_filename;
+// TBC : Removed $p_header['stored_filename'] = $v_stored_filename;
+ $p_header['stored_filename'] = $p_filedescr['stored_filename'];
+ $p_header['extra'] = '';
+ $p_header['status'] = 'ok';
+ $p_header['index'] = -1;
+
+ // ----- Look for regular file
+ if ($p_filedescr['type']=='file') {
+ $p_header['external'] = 0x00000000;
+ $p_header['size'] = filesize($p_filename);
+ }
+
+ // ----- Look for regular folder
+ else if ($p_filedescr['type']=='folder') {
+ $p_header['external'] = 0x00000010;
+ $p_header['mtime'] = filemtime($p_filename);
+ $p_header['size'] = filesize($p_filename);
+ }
+
+ // ----- Look for virtual file
+ else if ($p_filedescr['type'] == 'virtual_file') {
+ $p_header['external'] = 0x00000000;
+ $p_header['size'] = strlen($p_filedescr['content']);
+ }
+
+
+ // ----- Look for filetime
+ if (isset($p_filedescr['mtime'])) {
+ $p_header['mtime'] = $p_filedescr['mtime'];
+ }
+ else if ($p_filedescr['type'] == 'virtual_file') {
+ $p_header['mtime'] = time();
+ }
+ else {
+ $p_header['mtime'] = filemtime($p_filename);
+ }
+
+ // ------ Look for file comment
+ if (isset($p_filedescr['comment'])) {
+ $p_header['comment_len'] = strlen($p_filedescr['comment']);
+ $p_header['comment'] = $p_filedescr['comment'];
+ }
+ else {
+ $p_header['comment_len'] = 0;
+ $p_header['comment'] = '';
+ }
+
+ // ----- Look for pre-add callback
+ if (isset($p_options[PCLZIP_CB_PRE_ADD])) {
+
+ // ----- Generate a local information
+ $v_local_header = array();
+ $this->privConvertHeader2FileInfo($p_header, $v_local_header);
+
+ // ----- Call the callback
+ // Here I do not use call_user_func() because I need to send a reference to the
+ // header.
+// eval('$v_result = '.$p_options[PCLZIP_CB_PRE_ADD].'(PCLZIP_CB_PRE_ADD, $v_local_header);');
+ $v_result = $p_options[PCLZIP_CB_PRE_ADD](PCLZIP_CB_PRE_ADD, $v_local_header);
+ if ($v_result == 0) {
+ // ----- Change the file status
+ $p_header['status'] = "skipped";
+ $v_result = 1;
+ }
+
+ // ----- Update the informations
+ // Only some fields can be modified
+ if ($p_header['stored_filename'] != $v_local_header['stored_filename']) {
+ $p_header['stored_filename'] = PclZipUtilPathReduction($v_local_header['stored_filename']);
+ }
+ }
+
+ // ----- Look for empty stored filename
+ if ($p_header['stored_filename'] == "") {
+ $p_header['status'] = "filtered";
+ }
+
+ // ----- Check the path length
+ if (strlen($p_header['stored_filename']) > 0xFF) {
+ $p_header['status'] = 'filename_too_long';
+ }
+
+ // ----- Look if no error, or file not skipped
+ if ($p_header['status'] == 'ok') {
+
+ // ----- Look for a file
+ if ($p_filedescr['type'] == 'file') {
+ // ----- Look for using temporary file to zip
+ if ( (!isset($p_options[PCLZIP_OPT_TEMP_FILE_OFF]))
+ && (isset($p_options[PCLZIP_OPT_TEMP_FILE_ON])
+ || (isset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD])
+ && ($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] <= $p_header['size'])) ) ) {
+ $v_result = $this->privAddFileUsingTempFile($p_filedescr, $p_header, $p_options);
+ if ($v_result < PCLZIP_ERR_NO_ERROR) {
+ return $v_result;
+ }
+ }
+
+ // ----- Use "in memory" zip algo
+ else {
+
+ // ----- Open the source file
+ if (($v_file = @fopen($p_filename, "rb")) == 0) {
+ PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to open file '$p_filename' in binary read mode");
+ return PclZip::errorCode();
+ }
+
+ // ----- Read the file content
+ $v_content = @fread($v_file, $p_header['size']);
+
+ // ----- Close the file
+ @fclose($v_file);
+
+ // ----- Calculate the CRC
+ $p_header['crc'] = @crc32($v_content);
+
+ // ----- Look for no compression
+ if ($p_options[PCLZIP_OPT_NO_COMPRESSION]) {
+ // ----- Set header parameters
+ $p_header['compressed_size'] = $p_header['size'];
+ $p_header['compression'] = 0;
+ }
+
+ // ----- Look for normal compression
+ else {
+ // ----- Compress the content
+ $v_content = @gzdeflate($v_content);
+
+ // ----- Set header parameters
+ $p_header['compressed_size'] = strlen($v_content);
+ $p_header['compression'] = 8;
+ }
+
+ // ----- Call the header generation
+ if (($v_result = $this->privWriteFileHeader($p_header)) != 1) {
+ @fclose($v_file);
+ return $v_result;
+ }
+
+ // ----- Write the compressed (or not) content
+ @fwrite($this->zip_fd, $v_content, $p_header['compressed_size']);
+
+ }
+
+ }
+
+ // ----- Look for a virtual file (a file from string)
+ else if ($p_filedescr['type'] == 'virtual_file') {
+
+ $v_content = $p_filedescr['content'];
+
+ // ----- Calculate the CRC
+ $p_header['crc'] = @crc32($v_content);
+
+ // ----- Look for no compression
+ if ($p_options[PCLZIP_OPT_NO_COMPRESSION]) {
+ // ----- Set header parameters
+ $p_header['compressed_size'] = $p_header['size'];
+ $p_header['compression'] = 0;
+ }
+
+ // ----- Look for normal compression
+ else {
+ // ----- Compress the content
+ $v_content = @gzdeflate($v_content);
+
+ // ----- Set header parameters
+ $p_header['compressed_size'] = strlen($v_content);
+ $p_header['compression'] = 8;
+ }
+
+ // ----- Call the header generation
+ if (($v_result = $this->privWriteFileHeader($p_header)) != 1) {
+ @fclose($v_file);
+ return $v_result;
+ }
+
+ // ----- Write the compressed (or not) content
+ @fwrite($this->zip_fd, $v_content, $p_header['compressed_size']);
+ }
+
+ // ----- Look for a directory
+ else if ($p_filedescr['type'] == 'folder') {
+ // ----- Look for directory last '/'
+ if (@substr($p_header['stored_filename'], -1) != '/') {
+ $p_header['stored_filename'] .= '/';
+ }
+
+ // ----- Set the file properties
+ $p_header['size'] = 0;
+ //$p_header['external'] = 0x41FF0010; // Value for a folder : to be checked
+ $p_header['external'] = 0x00000010; // Value for a folder : to be checked
+
+ // ----- Call the header generation
+ if (($v_result = $this->privWriteFileHeader($p_header)) != 1)
+ {
+ return $v_result;
+ }
+ }
+ }
+
+ // ----- Look for post-add callback
+ if (isset($p_options[PCLZIP_CB_POST_ADD])) {
+
+ // ----- Generate a local information
+ $v_local_header = array();
+ $this->privConvertHeader2FileInfo($p_header, $v_local_header);
+
+ // ----- Call the callback
+ // Here I do not use call_user_func() because I need to send a reference to the
+ // header.
+// eval('$v_result = '.$p_options[PCLZIP_CB_POST_ADD].'(PCLZIP_CB_POST_ADD, $v_local_header);');
+ $v_result = $p_options[PCLZIP_CB_POST_ADD](PCLZIP_CB_POST_ADD, $v_local_header);
+ if ($v_result == 0) {
+ // ----- Ignored
+ $v_result = 1;
+ }
+
+ // ----- Update the informations
+ // Nothing can be modified
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privAddFileUsingTempFile()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privAddFileUsingTempFile($p_filedescr, &$p_header, &$p_options)
+ {
+ $v_result=PCLZIP_ERR_NO_ERROR;
+
+ // ----- Working variable
+ $p_filename = $p_filedescr['filename'];
+
+
+ // ----- Open the source file
+ if (($v_file = @fopen($p_filename, "rb")) == 0) {
+ PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to open file '$p_filename' in binary read mode");
+ return PclZip::errorCode();
+ }
+
+ // ----- Creates a compressed temporary file
+ $v_gzip_temp_name = PCLZIP_TEMPORARY_DIR.uniqid('pclzip-').'.gz';
+ if (($v_file_compressed = @gzopen($v_gzip_temp_name, "wb")) == 0) {
+ fclose($v_file);
+ PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, 'Unable to open temporary file \''.$v_gzip_temp_name.'\' in binary write mode');
+ return PclZip::errorCode();
+ }
+
+ // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks
+ $v_size = filesize($p_filename);
+ while ($v_size != 0) {
+ $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+ $v_buffer = @fread($v_file, $v_read_size);
+ //$v_binary_data = pack('a'.$v_read_size, $v_buffer);
+ @gzputs($v_file_compressed, $v_buffer, $v_read_size);
+ $v_size -= $v_read_size;
+ }
+
+ // ----- Close the file
+ @fclose($v_file);
+ @gzclose($v_file_compressed);
+
+ // ----- Check the minimum file size
+ if (filesize($v_gzip_temp_name) < 18) {
+ PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'gzip temporary file \''.$v_gzip_temp_name.'\' has invalid filesize - should be minimum 18 bytes');
+ return PclZip::errorCode();
+ }
+
+ // ----- Extract the compressed attributes
+ if (($v_file_compressed = @fopen($v_gzip_temp_name, "rb")) == 0) {
+ PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \''.$v_gzip_temp_name.'\' in binary read mode');
+ return PclZip::errorCode();
+ }
+
+ // ----- Read the gzip file header
+ $v_binary_data = @fread($v_file_compressed, 10);
+ $v_data_header = unpack('a1id1/a1id2/a1cm/a1flag/Vmtime/a1xfl/a1os', $v_binary_data);
+
+ // ----- Check some parameters
+ $v_data_header['os'] = bin2hex($v_data_header['os']);
+
+ // ----- Read the gzip file footer
+ @fseek($v_file_compressed, filesize($v_gzip_temp_name)-8);
+ $v_binary_data = @fread($v_file_compressed, 8);
+ $v_data_footer = unpack('Vcrc/Vcompressed_size', $v_binary_data);
+
+ // ----- Set the attributes
+ $p_header['compression'] = ord($v_data_header['cm']);
+ //$p_header['mtime'] = $v_data_header['mtime'];
+ $p_header['crc'] = $v_data_footer['crc'];
+ $p_header['compressed_size'] = filesize($v_gzip_temp_name)-18;
+
+ // ----- Close the file
+ @fclose($v_file_compressed);
+
+ // ----- Call the header generation
+ if (($v_result = $this->privWriteFileHeader($p_header)) != 1) {
+ return $v_result;
+ }
+
+ // ----- Add the compressed data
+ if (($v_file_compressed = @fopen($v_gzip_temp_name, "rb")) == 0)
+ {
+ PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \''.$v_gzip_temp_name.'\' in binary read mode');
+ return PclZip::errorCode();
+ }
+
+ // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks
+ fseek($v_file_compressed, 10);
+ $v_size = $p_header['compressed_size'];
+ while ($v_size != 0)
+ {
+ $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+ $v_buffer = @fread($v_file_compressed, $v_read_size);
+ //$v_binary_data = pack('a'.$v_read_size, $v_buffer);
+ @fwrite($this->zip_fd, $v_buffer, $v_read_size);
+ $v_size -= $v_read_size;
+ }
+
+ // ----- Close the file
+ @fclose($v_file_compressed);
+
+ // ----- Unlink the temporary file
+ @unlink($v_gzip_temp_name);
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privCalculateStoredFilename()
+ // Description :
+ // Based on file descriptor properties and global options, this method
+ // calculate the filename that will be stored in the archive.
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privCalculateStoredFilename(&$p_filedescr, &$p_options)
+ {
+ $v_result=1;
+
+ // ----- Working variables
+ $p_filename = $p_filedescr['filename'];
+ if (isset($p_options[PCLZIP_OPT_ADD_PATH])) {
+ $p_add_dir = $p_options[PCLZIP_OPT_ADD_PATH];
+ }
+ else {
+ $p_add_dir = '';
+ }
+ if (isset($p_options[PCLZIP_OPT_REMOVE_PATH])) {
+ $p_remove_dir = $p_options[PCLZIP_OPT_REMOVE_PATH];
+ }
+ else {
+ $p_remove_dir = '';
+ }
+ if (isset($p_options[PCLZIP_OPT_REMOVE_ALL_PATH])) {
+ $p_remove_all_dir = $p_options[PCLZIP_OPT_REMOVE_ALL_PATH];
+ }
+ else {
+ $p_remove_all_dir = 0;
+ }
+
+
+ // ----- Look for full name change
+ if (isset($p_filedescr['new_full_name'])) {
+ // ----- Remove drive letter if any
+ $v_stored_filename = PclZipUtilTranslateWinPath($p_filedescr['new_full_name']);
+ }
+
+ // ----- Look for path and/or short name change
+ else {
+
+ // ----- Look for short name change
+ // Its when we cahnge just the filename but not the path
+ if (isset($p_filedescr['new_short_name'])) {
+ $v_path_info = pathinfo($p_filename);
+ $v_dir = '';
+ if ($v_path_info['dirname'] != '') {
+ $v_dir = $v_path_info['dirname'].'/';
+ }
+ $v_stored_filename = $v_dir.$p_filedescr['new_short_name'];
+ }
+ else {
+ // ----- Calculate the stored filename
+ $v_stored_filename = $p_filename;
+ }
+
+ // ----- Look for all path to remove
+ if ($p_remove_all_dir) {
+ $v_stored_filename = basename($p_filename);
+ }
+ // ----- Look for partial path remove
+ else if ($p_remove_dir != "") {
+ if (substr($p_remove_dir, -1) != '/')
+ $p_remove_dir .= "/";
+
+ if ( (substr($p_filename, 0, 2) == "./")
+ || (substr($p_remove_dir, 0, 2) == "./")) {
+
+ if ( (substr($p_filename, 0, 2) == "./")
+ && (substr($p_remove_dir, 0, 2) != "./")) {
+ $p_remove_dir = "./".$p_remove_dir;
+ }
+ if ( (substr($p_filename, 0, 2) != "./")
+ && (substr($p_remove_dir, 0, 2) == "./")) {
+ $p_remove_dir = substr($p_remove_dir, 2);
+ }
+ }
+
+ $v_compare = PclZipUtilPathInclusion($p_remove_dir,
+ $v_stored_filename);
+ if ($v_compare > 0) {
+ if ($v_compare == 2) {
+ $v_stored_filename = "";
+ }
+ else {
+ $v_stored_filename = substr($v_stored_filename,
+ strlen($p_remove_dir));
+ }
+ }
+ }
+
+ // ----- Remove drive letter if any
+ $v_stored_filename = PclZipUtilTranslateWinPath($v_stored_filename);
+
+ // ----- Look for path to add
+ if ($p_add_dir != "") {
+ if (substr($p_add_dir, -1) == "/")
+ $v_stored_filename = $p_add_dir.$v_stored_filename;
+ else
+ $v_stored_filename = $p_add_dir."/".$v_stored_filename;
+ }
+ }
+
+ // ----- Filename (reduce the path of stored name)
+ $v_stored_filename = PclZipUtilPathReduction($v_stored_filename);
+ $p_filedescr['stored_filename'] = $v_stored_filename;
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privWriteFileHeader()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privWriteFileHeader(&$p_header)
+ {
+ $v_result=1;
+
+ // ----- Store the offset position of the file
+ $p_header['offset'] = ftell($this->zip_fd);
+
+ // ----- Transform UNIX mtime to DOS format mdate/mtime
+ $v_date = getdate($p_header['mtime']);
+ $v_mtime = ($v_date['hours']<<11) + ($v_date['minutes']<<5) + $v_date['seconds']/2;
+ $v_mdate = (($v_date['year']-1980)<<9) + ($v_date['mon']<<5) + $v_date['mday'];
+
+ // ----- Packed data
+ $v_binary_data = pack("VvvvvvVVVvv", 0x04034b50,
+ $p_header['version_extracted'], $p_header['flag'],
+ $p_header['compression'], $v_mtime, $v_mdate,
+ $p_header['crc'], $p_header['compressed_size'],
+ $p_header['size'],
+ strlen($p_header['stored_filename']),
+ $p_header['extra_len']);
+
+ // ----- Write the first 148 bytes of the header in the archive
+ fputs($this->zip_fd, $v_binary_data, 30);
+
+ // ----- Write the variable fields
+ if (strlen($p_header['stored_filename']) != 0)
+ {
+ fputs($this->zip_fd, $p_header['stored_filename'], strlen($p_header['stored_filename']));
+ }
+ if ($p_header['extra_len'] != 0)
+ {
+ fputs($this->zip_fd, $p_header['extra'], $p_header['extra_len']);
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privWriteCentralFileHeader()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privWriteCentralFileHeader(&$p_header)
+ {
+ $v_result=1;
+
+ // TBC
+ //for(reset($p_header); $key = key($p_header); next($p_header)) {
+ //}
+
+ // ----- Transform UNIX mtime to DOS format mdate/mtime
+ $v_date = getdate($p_header['mtime']);
+ $v_mtime = ($v_date['hours']<<11) + ($v_date['minutes']<<5) + $v_date['seconds']/2;
+ $v_mdate = (($v_date['year']-1980)<<9) + ($v_date['mon']<<5) + $v_date['mday'];
+
+
+ // ----- Packed data
+ $v_binary_data = pack("VvvvvvvVVVvvvvvVV", 0x02014b50,
+ $p_header['version'], $p_header['version_extracted'],
+ $p_header['flag'], $p_header['compression'],
+ $v_mtime, $v_mdate, $p_header['crc'],
+ $p_header['compressed_size'], $p_header['size'],
+ strlen($p_header['stored_filename']),
+ $p_header['extra_len'], $p_header['comment_len'],
+ $p_header['disk'], $p_header['internal'],
+ $p_header['external'], $p_header['offset']);
+
+ // ----- Write the 42 bytes of the header in the zip file
+ fputs($this->zip_fd, $v_binary_data, 46);
+
+ // ----- Write the variable fields
+ if (strlen($p_header['stored_filename']) != 0)
+ {
+ fputs($this->zip_fd, $p_header['stored_filename'], strlen($p_header['stored_filename']));
+ }
+ if ($p_header['extra_len'] != 0)
+ {
+ fputs($this->zip_fd, $p_header['extra'], $p_header['extra_len']);
+ }
+ if ($p_header['comment_len'] != 0)
+ {
+ fputs($this->zip_fd, $p_header['comment'], $p_header['comment_len']);
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privWriteCentralHeader()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privWriteCentralHeader($p_nb_entries, $p_size, $p_offset, $p_comment)
+ {
+ $v_result=1;
+
+ // ----- Packed data
+ $v_binary_data = pack("VvvvvVVv", 0x06054b50, 0, 0, $p_nb_entries,
+ $p_nb_entries, $p_size,
+ $p_offset, strlen($p_comment));
+
+ // ----- Write the 22 bytes of the header in the zip file
+ fputs($this->zip_fd, $v_binary_data, 22);
+
+ // ----- Write the variable fields
+ if (strlen($p_comment) != 0)
+ {
+ fputs($this->zip_fd, $p_comment, strlen($p_comment));
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privList()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privList(&$p_list)
+ {
+ $v_result=1;
+
+ // ----- Magic quotes trick
+ $this->privDisableMagicQuotes();
+
+ // ----- Open the zip file
+ if (($this->zip_fd = @fopen($this->zipname, 'rb')) == 0)
+ {
+ // ----- Magic quotes trick
+ $this->privSwapBackMagicQuotes();
+
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \''.$this->zipname.'\' in binary read mode');
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Read the central directory informations
+ $v_central_dir = array();
+ if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1)
+ {
+ $this->privSwapBackMagicQuotes();
+ return $v_result;
+ }
+
+ // ----- Go to beginning of Central Dir
+ @rewind($this->zip_fd);
+ if (@fseek($this->zip_fd, $v_central_dir['offset']))
+ {
+ $this->privSwapBackMagicQuotes();
+
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size');
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Read each entry
+ for ($i=0; $i<$v_central_dir['entries']; $i++)
+ {
+ // ----- Read the file header
+ if (($v_result = $this->privReadCentralFileHeader($v_header)) != 1)
+ {
+ $this->privSwapBackMagicQuotes();
+ return $v_result;
+ }
+ $v_header['index'] = $i;
+
+ // ----- Get the only interesting attributes
+ $this->privConvertHeader2FileInfo($v_header, $p_list[$i]);
+ unset($v_header);
+ }
+
+ // ----- Close the zip file
+ $this->privCloseFd();
+
+ // ----- Magic quotes trick
+ $this->privSwapBackMagicQuotes();
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privConvertHeader2FileInfo()
+ // Description :
+ // This function takes the file informations from the central directory
+ // entries and extract the interesting parameters that will be given back.
+ // The resulting file infos are set in the array $p_info
+ // $p_info['filename'] : Filename with full path. Given by user (add),
+ // extracted in the filesystem (extract).
+ // $p_info['stored_filename'] : Stored filename in the archive.
+ // $p_info['size'] = Size of the file.
+ // $p_info['compressed_size'] = Compressed size of the file.
+ // $p_info['mtime'] = Last modification date of the file.
+ // $p_info['comment'] = Comment associated with the file.
+ // $p_info['folder'] = true/false : indicates if the entry is a folder or not.
+ // $p_info['status'] = status of the action on the file.
+ // $p_info['crc'] = CRC of the file content.
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privConvertHeader2FileInfo($p_header, &$p_info)
+ {
+ $v_result=1;
+
+ // ----- Get the interesting attributes
+ $v_temp_path = PclZipUtilPathReduction($p_header['filename']);
+ $p_info['filename'] = $v_temp_path;
+ $v_temp_path = PclZipUtilPathReduction($p_header['stored_filename']);
+ $p_info['stored_filename'] = $v_temp_path;
+ $p_info['size'] = $p_header['size'];
+ $p_info['compressed_size'] = $p_header['compressed_size'];
+ $p_info['mtime'] = $p_header['mtime'];
+ $p_info['comment'] = $p_header['comment'];
+ $p_info['folder'] = (($p_header['external']&0x00000010)==0x00000010);
+ $p_info['index'] = $p_header['index'];
+ $p_info['status'] = $p_header['status'];
+ $p_info['crc'] = $p_header['crc'];
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privExtractByRule()
+ // Description :
+ // Extract a file or directory depending of rules (by index, by name, ...)
+ // Parameters :
+ // $p_file_list : An array where will be placed the properties of each
+ // extracted file
+ // $p_path : Path to add while writing the extracted files
+ // $p_remove_path : Path to remove (from the file memorized path) while writing the
+ // extracted files. If the path does not match the file path,
+ // the file is extracted with its memorized path.
+ // $p_remove_path does not apply to 'list' mode.
+ // $p_path and $p_remove_path are commulative.
+ // Return Values :
+ // 1 on success,0 or less on error (see error code list)
+ // --------------------------------------------------------------------------------
+ function privExtractByRule(&$p_file_list, $p_path, $p_remove_path, $p_remove_all_path, &$p_options)
+ {
+ $v_result=1;
+
+ // ----- Magic quotes trick
+ $this->privDisableMagicQuotes();
+
+ // ----- Check the path
+ if ( ($p_path == "")
+ || ( (substr($p_path, 0, 1) != "/")
+ && (substr($p_path, 0, 3) != "../")
+ && (substr($p_path,1,2)!=":/")))
+ $p_path = "./".$p_path;
+
+ // ----- Reduce the path last (and duplicated) '/'
+ if (($p_path != "./") && ($p_path != "/"))
+ {
+ // ----- Look for the path end '/'
+ while (substr($p_path, -1) == "/")
+ {
+ $p_path = substr($p_path, 0, strlen($p_path)-1);
+ }
+ }
+
+ // ----- Look for path to remove format (should end by /)
+ if (($p_remove_path != "") && (substr($p_remove_path, -1) != '/'))
+ {
+ $p_remove_path .= '/';
+ }
+ $p_remove_path_size = strlen($p_remove_path);
+
+ // ----- Open the zip file
+ if (($v_result = $this->privOpenFd('rb')) != 1)
+ {
+ $this->privSwapBackMagicQuotes();
+ return $v_result;
+ }
+
+ // ----- Read the central directory informations
+ $v_central_dir = array();
+ if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1)
+ {
+ // ----- Close the zip file
+ $this->privCloseFd();
+ $this->privSwapBackMagicQuotes();
+
+ return $v_result;
+ }
+
+ // ----- Start at beginning of Central Dir
+ $v_pos_entry = $v_central_dir['offset'];
+
+ // ----- Read each entry
+ $j_start = 0;
+ for ($i=0, $v_nb_extracted=0; $i<$v_central_dir['entries']; $i++)
+ {
+
+ // ----- Read next Central dir entry
+ @rewind($this->zip_fd);
+ if (@fseek($this->zip_fd, $v_pos_entry))
+ {
+ // ----- Close the zip file
+ $this->privCloseFd();
+ $this->privSwapBackMagicQuotes();
+
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size');
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Read the file header
+ $v_header = array();
+ if (($v_result = $this->privReadCentralFileHeader($v_header)) != 1)
+ {
+ // ----- Close the zip file
+ $this->privCloseFd();
+ $this->privSwapBackMagicQuotes();
+
+ return $v_result;
+ }
+
+ // ----- Store the index
+ $v_header['index'] = $i;
+
+ // ----- Store the file position
+ $v_pos_entry = ftell($this->zip_fd);
+
+ // ----- Look for the specific extract rules
+ $v_extract = false;
+
+ // ----- Look for extract by name rule
+ if ( (isset($p_options[PCLZIP_OPT_BY_NAME]))
+ && ($p_options[PCLZIP_OPT_BY_NAME] != 0)) {
+
+ // ----- Look if the filename is in the list
+ for ($j=0; ($j<sizeof($p_options[PCLZIP_OPT_BY_NAME])) && (!$v_extract); $j++) {
+
+ // ----- Look for a directory
+ if (substr($p_options[PCLZIP_OPT_BY_NAME][$j], -1) == "/") {
+
+ // ----- Look if the directory is in the filename path
+ if ( (strlen($v_header['stored_filename']) > strlen($p_options[PCLZIP_OPT_BY_NAME][$j]))
+ && (substr($v_header['stored_filename'], 0, strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) == $p_options[PCLZIP_OPT_BY_NAME][$j])) {
+ $v_extract = true;
+ }
+ }
+ // ----- Look for a filename
+ elseif ($v_header['stored_filename'] == $p_options[PCLZIP_OPT_BY_NAME][$j]) {
+ $v_extract = true;
+ }
+ }
+ }
+
+ // ----- Look for extract by ereg rule
+ // ereg() is deprecated with PHP 5.3
+ /*
+ else if ( (isset($p_options[PCLZIP_OPT_BY_EREG]))
+ && ($p_options[PCLZIP_OPT_BY_EREG] != "")) {
+
+ if (ereg($p_options[PCLZIP_OPT_BY_EREG], $v_header['stored_filename'])) {
+ $v_extract = true;
+ }
+ }
+ */
+
+ // ----- Look for extract by preg rule
+ else if ( (isset($p_options[PCLZIP_OPT_BY_PREG]))
+ && ($p_options[PCLZIP_OPT_BY_PREG] != "")) {
+
+ if (preg_match($p_options[PCLZIP_OPT_BY_PREG], $v_header['stored_filename'])) {
+ $v_extract = true;
+ }
+ }
+
+ // ----- Look for extract by index rule
+ else if ( (isset($p_options[PCLZIP_OPT_BY_INDEX]))
+ && ($p_options[PCLZIP_OPT_BY_INDEX] != 0)) {
+
+ // ----- Look if the index is in the list
+ for ($j=$j_start; ($j<sizeof($p_options[PCLZIP_OPT_BY_INDEX])) && (!$v_extract); $j++) {
+
+ if (($i>=$p_options[PCLZIP_OPT_BY_INDEX][$j]['start']) && ($i<=$p_options[PCLZIP_OPT_BY_INDEX][$j]['end'])) {
+ $v_extract = true;
+ }
+ if ($i>=$p_options[PCLZIP_OPT_BY_INDEX][$j]['end']) {
+ $j_start = $j+1;
+ }
+
+ if ($p_options[PCLZIP_OPT_BY_INDEX][$j]['start']>$i) {
+ break;
+ }
+ }
+ }
+
+ // ----- Look for no rule, which means extract all the archive
+ else {
+ $v_extract = true;
+ }
+
+ // ----- Check compression method
+ if ( ($v_extract)
+ && ( ($v_header['compression'] != 8)
+ && ($v_header['compression'] != 0))) {
+ $v_header['status'] = 'unsupported_compression';
+
+ // ----- Look for PCLZIP_OPT_STOP_ON_ERROR
+ if ( (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR]))
+ && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) {
+
+ $this->privSwapBackMagicQuotes();
+
+ PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_COMPRESSION,
+ "Filename '".$v_header['stored_filename']."' is "
+ ."compressed by an unsupported compression "
+ ."method (".$v_header['compression'].") ");
+
+ return PclZip::errorCode();
+ }
+ }
+
+ // ----- Check encrypted files
+ if (($v_extract) && (($v_header['flag'] & 1) == 1)) {
+ $v_header['status'] = 'unsupported_encryption';
+
+ // ----- Look for PCLZIP_OPT_STOP_ON_ERROR
+ if ( (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR]))
+ && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) {
+
+ $this->privSwapBackMagicQuotes();
+
+ PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_ENCRYPTION,
+ "Unsupported encryption for "
+ ." filename '".$v_header['stored_filename']
+ ."'");
+
+ return PclZip::errorCode();
+ }
+ }
+
+ // ----- Look for real extraction
+ if (($v_extract) && ($v_header['status'] != 'ok')) {
+ $v_result = $this->privConvertHeader2FileInfo($v_header,
+ $p_file_list[$v_nb_extracted++]);
+ if ($v_result != 1) {
+ $this->privCloseFd();
+ $this->privSwapBackMagicQuotes();
+ return $v_result;
+ }
+
+ $v_extract = false;
+ }
+
+ // ----- Look for real extraction
+ if ($v_extract)
+ {
+
+ // ----- Go to the file position
+ @rewind($this->zip_fd);
+ if (@fseek($this->zip_fd, $v_header['offset']))
+ {
+ // ----- Close the zip file
+ $this->privCloseFd();
+
+ $this->privSwapBackMagicQuotes();
+
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size');
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Look for extraction as string
+ if ($p_options[PCLZIP_OPT_EXTRACT_AS_STRING]) {
+
+ $v_string = '';
+
+ // ----- Extracting the file
+ $v_result1 = $this->privExtractFileAsString($v_header, $v_string, $p_options);
+ if ($v_result1 < 1) {
+ $this->privCloseFd();
+ $this->privSwapBackMagicQuotes();
+ return $v_result1;
+ }
+
+ // ----- Get the only interesting attributes
+ if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted])) != 1)
+ {
+ // ----- Close the zip file
+ $this->privCloseFd();
+ $this->privSwapBackMagicQuotes();
+
+ return $v_result;
+ }
+
+ // ----- Set the file content
+ $p_file_list[$v_nb_extracted]['content'] = $v_string;
+
+ // ----- Next extracted file
+ $v_nb_extracted++;
+
+ // ----- Look for user callback abort
+ if ($v_result1 == 2) {
+ break;
+ }
+ }
+ // ----- Look for extraction in standard output
+ elseif ( (isset($p_options[PCLZIP_OPT_EXTRACT_IN_OUTPUT]))
+ && ($p_options[PCLZIP_OPT_EXTRACT_IN_OUTPUT])) {
+ // ----- Extracting the file in standard output
+ $v_result1 = $this->privExtractFileInOutput($v_header, $p_options);
+ if ($v_result1 < 1) {
+ $this->privCloseFd();
+ $this->privSwapBackMagicQuotes();
+ return $v_result1;
+ }
+
+ // ----- Get the only interesting attributes
+ if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++])) != 1) {
+ $this->privCloseFd();
+ $this->privSwapBackMagicQuotes();
+ return $v_result;
+ }
+
+ // ----- Look for user callback abort
+ if ($v_result1 == 2) {
+ break;
+ }
+ }
+ // ----- Look for normal extraction
+ else {
+ // ----- Extracting the file
+ $v_result1 = $this->privExtractFile($v_header,
+ $p_path, $p_remove_path,
+ $p_remove_all_path,
+ $p_options);
+ if ($v_result1 < 1) {
+ $this->privCloseFd();
+ $this->privSwapBackMagicQuotes();
+ return $v_result1;
+ }
+
+ // ----- Get the only interesting attributes
+ if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++])) != 1)
+ {
+ // ----- Close the zip file
+ $this->privCloseFd();
+ $this->privSwapBackMagicQuotes();
+
+ return $v_result;
+ }
+
+ // ----- Look for user callback abort
+ if ($v_result1 == 2) {
+ break;
+ }
+ }
+ }
+ }
+
+ // ----- Close the zip file
+ $this->privCloseFd();
+ $this->privSwapBackMagicQuotes();
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privExtractFile()
+ // Description :
+ // Parameters :
+ // Return Values :
+ //
+ // 1 : ... ?
+ // PCLZIP_ERR_USER_ABORTED(2) : User ask for extraction stop in callback
+ // --------------------------------------------------------------------------------
+ function privExtractFile(&$p_entry, $p_path, $p_remove_path, $p_remove_all_path, &$p_options)
+ {
+ $v_result=1;
+
+ // ----- Read the file header
+ if (($v_result = $this->privReadFileHeader($v_header)) != 1)
+ {
+ // ----- Return
+ return $v_result;
+ }
+
+
+ // ----- Check that the file header is coherent with $p_entry info
+ if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) {
+ // TBC
+ }
+
+ // ----- Look for all path to remove
+ if ($p_remove_all_path == true) {
+ // ----- Look for folder entry that not need to be extracted
+ if (($p_entry['external']&0x00000010)==0x00000010) {
+
+ $p_entry['status'] = "filtered";
+
+ return $v_result;
+ }
+
+ // ----- Get the basename of the path
+ $p_entry['filename'] = basename($p_entry['filename']);
+ }
+
+ // ----- Look for path to remove
+ else if ($p_remove_path != "")
+ {
+ if (PclZipUtilPathInclusion($p_remove_path, $p_entry['filename']) == 2)
+ {
+
+ // ----- Change the file status
+ $p_entry['status'] = "filtered";
+
+ // ----- Return
+ return $v_result;
+ }
+
+ $p_remove_path_size = strlen($p_remove_path);
+ if (substr($p_entry['filename'], 0, $p_remove_path_size) == $p_remove_path)
+ {
+
+ // ----- Remove the path
+ $p_entry['filename'] = substr($p_entry['filename'], $p_remove_path_size);
+
+ }
+ }
+
+ // ----- Add the path
+ if ($p_path != '') {
+ $p_entry['filename'] = $p_path."/".$p_entry['filename'];
+ }
+
+ // ----- Check a base_dir_restriction
+ if (isset($p_options[PCLZIP_OPT_EXTRACT_DIR_RESTRICTION])) {
+ $v_inclusion
+ = PclZipUtilPathInclusion($p_options[PCLZIP_OPT_EXTRACT_DIR_RESTRICTION],
+ $p_entry['filename']);
+ if ($v_inclusion == 0) {
+
+ PclZip::privErrorLog(PCLZIP_ERR_DIRECTORY_RESTRICTION,
+ "Filename '".$p_entry['filename']."' is "
+ ."outside PCLZIP_OPT_EXTRACT_DIR_RESTRICTION");
+
+ return PclZip::errorCode();
+ }
+ }
+
+ // ----- Look for pre-extract callback
+ if (isset($p_options[PCLZIP_CB_PRE_EXTRACT])) {
+
+ // ----- Generate a local information
+ $v_local_header = array();
+ $this->privConvertHeader2FileInfo($p_entry, $v_local_header);
+
+ // ----- Call the callback
+ // Here I do not use call_user_func() because I need to send a reference to the
+ // header.
+// eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);');
+ $v_result = $p_options[PCLZIP_CB_PRE_EXTRACT](PCLZIP_CB_PRE_EXTRACT, $v_local_header);
+ if ($v_result == 0) {
+ // ----- Change the file status
+ $p_entry['status'] = "skipped";
+ $v_result = 1;
+ }
+
+ // ----- Look for abort result
+ if ($v_result == 2) {
+ // ----- This status is internal and will be changed in 'skipped'
+ $p_entry['status'] = "aborted";
+ $v_result = PCLZIP_ERR_USER_ABORTED;
+ }
+
+ // ----- Update the informations
+ // Only some fields can be modified
+ $p_entry['filename'] = $v_local_header['filename'];
+ }
+
+
+ // ----- Look if extraction should be done
+ if ($p_entry['status'] == 'ok') {
+
+ // ----- Look for specific actions while the file exist
+ if (file_exists($p_entry['filename']))
+ {
+
+ // ----- Look if file is a directory
+ if (is_dir($p_entry['filename']))
+ {
+
+ // ----- Change the file status
+ $p_entry['status'] = "already_a_directory";
+
+ // ----- Look for PCLZIP_OPT_STOP_ON_ERROR
+ // For historical reason first PclZip implementation does not stop
+ // when this kind of error occurs.
+ if ( (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR]))
+ && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) {
+
+ PclZip::privErrorLog(PCLZIP_ERR_ALREADY_A_DIRECTORY,
+ "Filename '".$p_entry['filename']."' is "
+ ."already used by an existing directory");
+
+ return PclZip::errorCode();
+ }
+ }
+ // ----- Look if file is write protected
+ else if (!is_writeable($p_entry['filename']))
+ {
+
+ // ----- Change the file status
+ $p_entry['status'] = "write_protected";
+
+ // ----- Look for PCLZIP_OPT_STOP_ON_ERROR
+ // For historical reason first PclZip implementation does not stop
+ // when this kind of error occurs.
+ if ( (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR]))
+ && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) {
+
+ PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL,
+ "Filename '".$p_entry['filename']."' exists "
+ ."and is write protected");
+
+ return PclZip::errorCode();
+ }
+ }
+
+ // ----- Look if the extracted file is older
+ else if (filemtime($p_entry['filename']) > $p_entry['mtime'])
+ {
+ // ----- Change the file status
+ if ( (isset($p_options[PCLZIP_OPT_REPLACE_NEWER]))
+ && ($p_options[PCLZIP_OPT_REPLACE_NEWER]===true)) {
+ }
+ else {
+ $p_entry['status'] = "newer_exist";
+
+ // ----- Look for PCLZIP_OPT_STOP_ON_ERROR
+ // For historical reason first PclZip implementation does not stop
+ // when this kind of error occurs.
+ if ( (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR]))
+ && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) {
+
+ PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL,
+ "Newer version of '".$p_entry['filename']."' exists "
+ ."and option PCLZIP_OPT_REPLACE_NEWER is not selected");
+
+ return PclZip::errorCode();
+ }
+ }
+ }
+ else {
+ }
+ }
+
+ // ----- Check the directory availability and create it if necessary
+ else {
+ if ((($p_entry['external']&0x00000010)==0x00000010) || (substr($p_entry['filename'], -1) == '/'))
+ $v_dir_to_check = $p_entry['filename'];
+ else if (!strstr($p_entry['filename'], "/"))
+ $v_dir_to_check = "";
+ else
+ $v_dir_to_check = dirname($p_entry['filename']);
+
+ if (($v_result = $this->privDirCheck($v_dir_to_check, (($p_entry['external']&0x00000010)==0x00000010))) != 1) {
+
+ // ----- Change the file status
+ $p_entry['status'] = "path_creation_fail";
+
+ // ----- Return
+ //return $v_result;
+ $v_result = 1;
+ }
+ }
+ }
+
+ // ----- Look if extraction should be done
+ if ($p_entry['status'] == 'ok') {
+
+ // ----- Do the extraction (if not a folder)
+ if (!(($p_entry['external']&0x00000010)==0x00000010))
+ {
+ // ----- Look for not compressed file
+ if ($p_entry['compression'] == 0) {
+
+ // ----- Opening destination file
+ if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0)
+ {
+
+ // ----- Change the file status
+ $p_entry['status'] = "write_error";
+
+ // ----- Return
+ return $v_result;
+ }
+
+
+ // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks
+ $v_size = $p_entry['compressed_size'];
+ while ($v_size != 0)
+ {
+ $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+ $v_buffer = @fread($this->zip_fd, $v_read_size);
+ /* Try to speed up the code
+ $v_binary_data = pack('a'.$v_read_size, $v_buffer);
+ @fwrite($v_dest_file, $v_binary_data, $v_read_size);
+ */
+ @fwrite($v_dest_file, $v_buffer, $v_read_size);
+ $v_size -= $v_read_size;
+ }
+
+ // ----- Closing the destination file
+ fclose($v_dest_file);
+
+ // ----- Change the file mtime
+ touch($p_entry['filename'], $p_entry['mtime']);
+
+
+ }
+ else {
+ // ----- TBC
+ // Need to be finished
+ if (($p_entry['flag'] & 1) == 1) {
+ PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_ENCRYPTION, 'File \''.$p_entry['filename'].'\' is encrypted. Encrypted files are not supported.');
+ return PclZip::errorCode();
+ }
+
+
+ // ----- Look for using temporary file to unzip
+ if ( (!isset($p_options[PCLZIP_OPT_TEMP_FILE_OFF]))
+ && (isset($p_options[PCLZIP_OPT_TEMP_FILE_ON])
+ || (isset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD])
+ && ($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] <= $p_entry['size'])) ) ) {
+ $v_result = $this->privExtractFileUsingTempFile($p_entry, $p_options);
+ if ($v_result < PCLZIP_ERR_NO_ERROR) {
+ return $v_result;
+ }
+ }
+
+ // ----- Look for extract in memory
+ else {
+
+
+ // ----- Read the compressed file in a buffer (one shot)
+ $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']);
+
+ // ----- Decompress the file
+ $v_file_content = @gzinflate($v_buffer);
+ unset($v_buffer);
+ if ($v_file_content === FALSE) {
+
+ // ----- Change the file status
+ // TBC
+ $p_entry['status'] = "error";
+
+ return $v_result;
+ }
+
+ // ----- Opening destination file
+ if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) {
+
+ // ----- Change the file status
+ $p_entry['status'] = "write_error";
+
+ return $v_result;
+ }
+
+ // ----- Write the uncompressed data
+ @fwrite($v_dest_file, $v_file_content, $p_entry['size']);
+ unset($v_file_content);
+
+ // ----- Closing the destination file
+ @fclose($v_dest_file);
+
+ }
+
+ // ----- Change the file mtime
+ @touch($p_entry['filename'], $p_entry['mtime']);
+ }
+
+ // ----- Look for chmod option
+ if (isset($p_options[PCLZIP_OPT_SET_CHMOD])) {
+
+ // ----- Change the mode of the file
+ @chmod($p_entry['filename'], $p_options[PCLZIP_OPT_SET_CHMOD]);
+ }
+
+ }
+ }
+
+ // ----- Change abort status
+ if ($p_entry['status'] == "aborted") {
+ $p_entry['status'] = "skipped";
+ }
+
+ // ----- Look for post-extract callback
+ elseif (isset($p_options[PCLZIP_CB_POST_EXTRACT])) {
+
+ // ----- Generate a local information
+ $v_local_header = array();
+ $this->privConvertHeader2FileInfo($p_entry, $v_local_header);
+
+ // ----- Call the callback
+ // Here I do not use call_user_func() because I need to send a reference to the
+ // header.
+// eval('$v_result = '.$p_options[PCLZIP_CB_POST_EXTRACT].'(PCLZIP_CB_POST_EXTRACT, $v_local_header);');
+ $v_result = $p_options[PCLZIP_CB_POST_EXTRACT](PCLZIP_CB_POST_EXTRACT, $v_local_header);
+
+ // ----- Look for abort result
+ if ($v_result == 2) {
+ $v_result = PCLZIP_ERR_USER_ABORTED;
+ }
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privExtractFileUsingTempFile()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privExtractFileUsingTempFile(&$p_entry, &$p_options)
+ {
+ $v_result=1;
+
+ // ----- Creates a temporary file
+ $v_gzip_temp_name = PCLZIP_TEMPORARY_DIR.uniqid('pclzip-').'.gz';
+ if (($v_dest_file = @fopen($v_gzip_temp_name, "wb")) == 0) {
+ fclose($v_file);
+ PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, 'Unable to open temporary file \''.$v_gzip_temp_name.'\' in binary write mode');
+ return PclZip::errorCode();
+ }
+
+
+ // ----- Write gz file format header
+ $v_binary_data = pack('va1a1Va1a1', 0x8b1f, Chr($p_entry['compression']), Chr(0x00), time(), Chr(0x00), Chr(3));
+ @fwrite($v_dest_file, $v_binary_data, 10);
+
+ // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks
+ $v_size = $p_entry['compressed_size'];
+ while ($v_size != 0)
+ {
+ $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+ $v_buffer = @fread($this->zip_fd, $v_read_size);
+ //$v_binary_data = pack('a'.$v_read_size, $v_buffer);
+ @fwrite($v_dest_file, $v_buffer, $v_read_size);
+ $v_size -= $v_read_size;
+ }
+
+ // ----- Write gz file format footer
+ $v_binary_data = pack('VV', $p_entry['crc'], $p_entry['size']);
+ @fwrite($v_dest_file, $v_binary_data, 8);
+
+ // ----- Close the temporary file
+ @fclose($v_dest_file);
+
+ // ----- Opening destination file
+ if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) {
+ $p_entry['status'] = "write_error";
+ return $v_result;
+ }
+
+ // ----- Open the temporary gz file
+ if (($v_src_file = @gzopen($v_gzip_temp_name, 'rb')) == 0) {
+ @fclose($v_dest_file);
+ $p_entry['status'] = "read_error";
+ PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \''.$v_gzip_temp_name.'\' in binary read mode');
+ return PclZip::errorCode();
+ }
+
+
+ // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks
+ $v_size = $p_entry['size'];
+ while ($v_size != 0) {
+ $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+ $v_buffer = @gzread($v_src_file, $v_read_size);
+ //$v_binary_data = pack('a'.$v_read_size, $v_buffer);
+ @fwrite($v_dest_file, $v_buffer, $v_read_size);
+ $v_size -= $v_read_size;
+ }
+ @fclose($v_dest_file);
+ @gzclose($v_src_file);
+
+ // ----- Delete the temporary file
+ @unlink($v_gzip_temp_name);
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privExtractFileInOutput()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privExtractFileInOutput(&$p_entry, &$p_options)
+ {
+ $v_result=1;
+
+ // ----- Read the file header
+ if (($v_result = $this->privReadFileHeader($v_header)) != 1) {
+ return $v_result;
+ }
+
+
+ // ----- Check that the file header is coherent with $p_entry info
+ if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) {
+ // TBC
+ }
+
+ // ----- Look for pre-extract callback
+ if (isset($p_options[PCLZIP_CB_PRE_EXTRACT])) {
+
+ // ----- Generate a local information
+ $v_local_header = array();
+ $this->privConvertHeader2FileInfo($p_entry, $v_local_header);
+
+ // ----- Call the callback
+ // Here I do not use call_user_func() because I need to send a reference to the
+ // header.
+// eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);');
+ $v_result = $p_options[PCLZIP_CB_PRE_EXTRACT](PCLZIP_CB_PRE_EXTRACT, $v_local_header);
+ if ($v_result == 0) {
+ // ----- Change the file status
+ $p_entry['status'] = "skipped";
+ $v_result = 1;
+ }
+
+ // ----- Look for abort result
+ if ($v_result == 2) {
+ // ----- This status is internal and will be changed in 'skipped'
+ $p_entry['status'] = "aborted";
+ $v_result = PCLZIP_ERR_USER_ABORTED;
+ }
+
+ // ----- Update the informations
+ // Only some fields can be modified
+ $p_entry['filename'] = $v_local_header['filename'];
+ }
+
+ // ----- Trace
+
+ // ----- Look if extraction should be done
+ if ($p_entry['status'] == 'ok') {
+
+ // ----- Do the extraction (if not a folder)
+ if (!(($p_entry['external']&0x00000010)==0x00000010)) {
+ // ----- Look for not compressed file
+ if ($p_entry['compressed_size'] == $p_entry['size']) {
+
+ // ----- Read the file in a buffer (one shot)
+ $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']);
+
+ // ----- Send the file to the output
+ echo $v_buffer;
+ unset($v_buffer);
+ }
+ else {
+
+ // ----- Read the compressed file in a buffer (one shot)
+ $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']);
+
+ // ----- Decompress the file
+ $v_file_content = gzinflate($v_buffer);
+ unset($v_buffer);
+
+ // ----- Send the file to the output
+ echo $v_file_content;
+ unset($v_file_content);
+ }
+ }
+ }
+
+ // ----- Change abort status
+ if ($p_entry['status'] == "aborted") {
+ $p_entry['status'] = "skipped";
+ }
+
+ // ----- Look for post-extract callback
+ elseif (isset($p_options[PCLZIP_CB_POST_EXTRACT])) {
+
+ // ----- Generate a local information
+ $v_local_header = array();
+ $this->privConvertHeader2FileInfo($p_entry, $v_local_header);
+
+ // ----- Call the callback
+ // Here I do not use call_user_func() because I need to send a reference to the
+ // header.
+// eval('$v_result = '.$p_options[PCLZIP_CB_POST_EXTRACT].'(PCLZIP_CB_POST_EXTRACT, $v_local_header);');
+ $v_result = $p_options[PCLZIP_CB_POST_EXTRACT](PCLZIP_CB_POST_EXTRACT, $v_local_header);
+
+ // ----- Look for abort result
+ if ($v_result == 2) {
+ $v_result = PCLZIP_ERR_USER_ABORTED;
+ }
+ }
+
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privExtractFileAsString()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privExtractFileAsString(&$p_entry, &$p_string, &$p_options)
+ {
+ $v_result=1;
+
+ // ----- Read the file header
+ $v_header = array();
+ if (($v_result = $this->privReadFileHeader($v_header)) != 1)
+ {
+ // ----- Return
+ return $v_result;
+ }
+
+
+ // ----- Check that the file header is coherent with $p_entry info
+ if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) {
+ // TBC
+ }
+
+ // ----- Look for pre-extract callback
+ if (isset($p_options[PCLZIP_CB_PRE_EXTRACT])) {
+
+ // ----- Generate a local information
+ $v_local_header = array();
+ $this->privConvertHeader2FileInfo($p_entry, $v_local_header);
+
+ // ----- Call the callback
+ // Here I do not use call_user_func() because I need to send a reference to the
+ // header.
+// eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);');
+ $v_result = $p_options[PCLZIP_CB_PRE_EXTRACT](PCLZIP_CB_PRE_EXTRACT, $v_local_header);
+ if ($v_result == 0) {
+ // ----- Change the file status
+ $p_entry['status'] = "skipped";
+ $v_result = 1;
+ }
+
+ // ----- Look for abort result
+ if ($v_result == 2) {
+ // ----- This status is internal and will be changed in 'skipped'
+ $p_entry['status'] = "aborted";
+ $v_result = PCLZIP_ERR_USER_ABORTED;
+ }
+
+ // ----- Update the informations
+ // Only some fields can be modified
+ $p_entry['filename'] = $v_local_header['filename'];
+ }
+
+
+ // ----- Look if extraction should be done
+ if ($p_entry['status'] == 'ok') {
+
+ // ----- Do the extraction (if not a folder)
+ if (!(($p_entry['external']&0x00000010)==0x00000010)) {
+ // ----- Look for not compressed file
+ // if ($p_entry['compressed_size'] == $p_entry['size'])
+ if ($p_entry['compression'] == 0) {
+
+ // ----- Reading the file
+ $p_string = @fread($this->zip_fd, $p_entry['compressed_size']);
+ }
+ else {
+
+ // ----- Reading the file
+ $v_data = @fread($this->zip_fd, $p_entry['compressed_size']);
+
+ // ----- Decompress the file
+ if (($p_string = @gzinflate($v_data)) === FALSE) {
+ // TBC
+ }
+ }
+
+ // ----- Trace
+ }
+ else {
+ // TBC : error : can not extract a folder in a string
+ }
+
+ }
+
+ // ----- Change abort status
+ if ($p_entry['status'] == "aborted") {
+ $p_entry['status'] = "skipped";
+ }
+
+ // ----- Look for post-extract callback
+ elseif (isset($p_options[PCLZIP_CB_POST_EXTRACT])) {
+
+ // ----- Generate a local information
+ $v_local_header = array();
+ $this->privConvertHeader2FileInfo($p_entry, $v_local_header);
+
+ // ----- Swap the content to header
+ $v_local_header['content'] = $p_string;
+ $p_string = '';
+
+ // ----- Call the callback
+ // Here I do not use call_user_func() because I need to send a reference to the
+ // header.
+// eval('$v_result = '.$p_options[PCLZIP_CB_POST_EXTRACT].'(PCLZIP_CB_POST_EXTRACT, $v_local_header);');
+ $v_result = $p_options[PCLZIP_CB_POST_EXTRACT](PCLZIP_CB_POST_EXTRACT, $v_local_header);
+
+ // ----- Swap back the content to header
+ $p_string = $v_local_header['content'];
+ unset($v_local_header['content']);
+
+ // ----- Look for abort result
+ if ($v_result == 2) {
+ $v_result = PCLZIP_ERR_USER_ABORTED;
+ }
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privReadFileHeader()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privReadFileHeader(&$p_header)
+ {
+ $v_result=1;
+
+ // ----- Read the 4 bytes signature
+ $v_binary_data = @fread($this->zip_fd, 4);
+ $v_data = unpack('Vid', $v_binary_data);
+
+ // ----- Check signature
+ if ($v_data['id'] != 0x04034b50)
+ {
+
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Invalid archive structure');
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Read the first 42 bytes of the header
+ $v_binary_data = fread($this->zip_fd, 26);
+
+ // ----- Look for invalid block size
+ if (strlen($v_binary_data) != 26)
+ {
+ $p_header['filename'] = "";
+ $p_header['status'] = "invalid_header";
+
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Invalid block size : ".strlen($v_binary_data));
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Extract the values
+ $v_data = unpack('vversion/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len', $v_binary_data);
+
+ // ----- Get filename
+ $p_header['filename'] = fread($this->zip_fd, $v_data['filename_len']);
+
+ // ----- Get extra_fields
+ if ($v_data['extra_len'] != 0) {
+ $p_header['extra'] = fread($this->zip_fd, $v_data['extra_len']);
+ }
+ else {
+ $p_header['extra'] = '';
+ }
+
+ // ----- Extract properties
+ $p_header['version_extracted'] = $v_data['version'];
+ $p_header['compression'] = $v_data['compression'];
+ $p_header['size'] = $v_data['size'];
+ $p_header['compressed_size'] = $v_data['compressed_size'];
+ $p_header['crc'] = $v_data['crc'];
+ $p_header['flag'] = $v_data['flag'];
+ $p_header['filename_len'] = $v_data['filename_len'];
+
+ // ----- Recuperate date in UNIX format
+ $p_header['mdate'] = $v_data['mdate'];
+ $p_header['mtime'] = $v_data['mtime'];
+ if ($p_header['mdate'] && $p_header['mtime'])
+ {
+ // ----- Extract time
+ $v_hour = ($p_header['mtime'] & 0xF800) >> 11;
+ $v_minute = ($p_header['mtime'] & 0x07E0) >> 5;
+ $v_seconde = ($p_header['mtime'] & 0x001F)*2;
+
+ // ----- Extract date
+ $v_year = (($p_header['mdate'] & 0xFE00) >> 9) + 1980;
+ $v_month = ($p_header['mdate'] & 0x01E0) >> 5;
+ $v_day = $p_header['mdate'] & 0x001F;
+
+ // ----- Get UNIX date format
+ $p_header['mtime'] = @mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year);
+
+ }
+ else
+ {
+ $p_header['mtime'] = time();
+ }
+
+ // TBC
+ //for(reset($v_data); $key = key($v_data); next($v_data)) {
+ //}
+
+ // ----- Set the stored filename
+ $p_header['stored_filename'] = $p_header['filename'];
+
+ // ----- Set the status field
+ $p_header['status'] = "ok";
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privReadCentralFileHeader()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privReadCentralFileHeader(&$p_header)
+ {
+ $v_result=1;
+
+ // ----- Read the 4 bytes signature
+ $v_binary_data = @fread($this->zip_fd, 4);
+ $v_data = unpack('Vid', $v_binary_data);
+
+ // ----- Check signature
+ if ($v_data['id'] != 0x02014b50)
+ {
+
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Invalid archive structure');
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Read the first 42 bytes of the header
+ $v_binary_data = fread($this->zip_fd, 42);
+
+ // ----- Look for invalid block size
+ if (strlen($v_binary_data) != 42)
+ {
+ $p_header['filename'] = "";
+ $p_header['status'] = "invalid_header";
+
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Invalid block size : ".strlen($v_binary_data));
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Extract the values
+ $p_header = unpack('vversion/vversion_extracted/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len/vcomment_len/vdisk/vinternal/Vexternal/Voffset', $v_binary_data);
+
+ // ----- Get filename
+ if ($p_header['filename_len'] != 0)
+ $p_header['filename'] = fread($this->zip_fd, $p_header['filename_len']);
+ else
+ $p_header['filename'] = '';
+
+ // ----- Get extra
+ if ($p_header['extra_len'] != 0)
+ $p_header['extra'] = fread($this->zip_fd, $p_header['extra_len']);
+ else
+ $p_header['extra'] = '';
+
+ // ----- Get comment
+ if ($p_header['comment_len'] != 0)
+ $p_header['comment'] = fread($this->zip_fd, $p_header['comment_len']);
+ else
+ $p_header['comment'] = '';
+
+ // ----- Extract properties
+
+ // ----- Recuperate date in UNIX format
+ //if ($p_header['mdate'] && $p_header['mtime'])
+ // TBC : bug : this was ignoring time with 0/0/0
+ if (1)
+ {
+ // ----- Extract time
+ $v_hour = ($p_header['mtime'] & 0xF800) >> 11;
+ $v_minute = ($p_header['mtime'] & 0x07E0) >> 5;
+ $v_seconde = ($p_header['mtime'] & 0x001F)*2;
+
+ // ----- Extract date
+ $v_year = (($p_header['mdate'] & 0xFE00) >> 9) + 1980;
+ $v_month = ($p_header['mdate'] & 0x01E0) >> 5;
+ $v_day = $p_header['mdate'] & 0x001F;
+
+ // ----- Get UNIX date format
+ $p_header['mtime'] = @mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year);
+
+ }
+ else
+ {
+ $p_header['mtime'] = time();
+ }
+
+ // ----- Set the stored filename
+ $p_header['stored_filename'] = $p_header['filename'];
+
+ // ----- Set default status to ok
+ $p_header['status'] = 'ok';
+
+ // ----- Look if it is a directory
+ if (substr($p_header['filename'], -1) == '/') {
+ //$p_header['external'] = 0x41FF0010;
+ $p_header['external'] = 0x00000010;
+ }
+
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privCheckFileHeaders()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // 1 on success,
+ // 0 on error;
+ // --------------------------------------------------------------------------------
+ function privCheckFileHeaders(&$p_local_header, &$p_central_header)
+ {
+ $v_result=1;
+
+ // ----- Check the static values
+ // TBC
+ if ($p_local_header['filename'] != $p_central_header['filename']) {
+ }
+ if ($p_local_header['version_extracted'] != $p_central_header['version_extracted']) {
+ }
+ if ($p_local_header['flag'] != $p_central_header['flag']) {
+ }
+ if ($p_local_header['compression'] != $p_central_header['compression']) {
+ }
+ if ($p_local_header['mtime'] != $p_central_header['mtime']) {
+ }
+ if ($p_local_header['filename_len'] != $p_central_header['filename_len']) {
+ }
+
+ // ----- Look for flag bit 3
+ if (($p_local_header['flag'] & 8) == 8) {
+ $p_local_header['size'] = $p_central_header['size'];
+ $p_local_header['compressed_size'] = $p_central_header['compressed_size'];
+ $p_local_header['crc'] = $p_central_header['crc'];
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privReadEndCentralDir()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privReadEndCentralDir(&$p_central_dir)
+ {
+ $v_result=1;
+
+ // ----- Go to the end of the zip file
+ $v_size = filesize($this->zipname);
+ @fseek($this->zip_fd, $v_size);
+ if (@ftell($this->zip_fd) != $v_size)
+ {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Unable to go to the end of the archive \''.$this->zipname.'\'');
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- First try : look if this is an archive with no commentaries (most of the time)
+ // in this case the end of central dir is at 22 bytes of the file end
+ $v_found = 0;
+ if ($v_size > 26) {
+ @fseek($this->zip_fd, $v_size-22);
+ if (($v_pos = @ftell($this->zip_fd)) != ($v_size-22))
+ {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Unable to seek back to the middle of the archive \''.$this->zipname.'\'');
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Read for bytes
+ $v_binary_data = @fread($this->zip_fd, 4);
+ $v_data = @unpack('Vid', $v_binary_data);
+
+ // ----- Check signature
+ if ($v_data['id'] == 0x06054b50) {
+ $v_found = 1;
+ }
+
+ $v_pos = ftell($this->zip_fd);
+ }
+
+ // ----- Go back to the maximum possible size of the Central Dir End Record
+ if (!$v_found) {
+ $v_maximum_size = 65557; // 0xFFFF + 22;
+ if ($v_maximum_size > $v_size)
+ $v_maximum_size = $v_size;
+ @fseek($this->zip_fd, $v_size-$v_maximum_size);
+ if (@ftell($this->zip_fd) != ($v_size-$v_maximum_size))
+ {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Unable to seek back to the middle of the archive \''.$this->zipname.'\'');
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Read byte per byte in order to find the signature
+ $v_pos = ftell($this->zip_fd);
+ $v_bytes = 0x00000000;
+ while ($v_pos < $v_size)
+ {
+ // ----- Read a byte
+ $v_byte = @fread($this->zip_fd, 1);
+
+ // ----- Add the byte
+ //$v_bytes = ($v_bytes << 8) | Ord($v_byte);
+ // Note we mask the old value down such that once shifted we can never end up with more than a 32bit number
+ // Otherwise on systems where we have 64bit integers the check below for the magic number will fail.
+ $v_bytes = ( ($v_bytes & 0xFFFFFF) << 8) | Ord($v_byte);
+
+ // ----- Compare the bytes
+ if ($v_bytes == 0x504b0506)
+ {
+ $v_pos++;
+ break;
+ }
+
+ $v_pos++;
+ }
+
+ // ----- Look if not found end of central dir
+ if ($v_pos == $v_size)
+ {
+
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Unable to find End of Central Dir Record signature");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+ }
+
+ // ----- Read the first 18 bytes of the header
+ $v_binary_data = fread($this->zip_fd, 18);
+
+ // ----- Look for invalid block size
+ if (strlen($v_binary_data) != 18)
+ {
+
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Invalid End of Central Dir Record size : ".strlen($v_binary_data));
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Extract the values
+ $v_data = unpack('vdisk/vdisk_start/vdisk_entries/ventries/Vsize/Voffset/vcomment_size', $v_binary_data);
+
+ // ----- Check the global size
+ if (($v_pos + $v_data['comment_size'] + 18) != $v_size) {
+
+ // ----- Removed in release 2.2 see readme file
+ // The check of the file size is a little too strict.
+ // Some bugs where found when a zip is encrypted/decrypted with 'crypt'.
+ // While decrypted, zip has training 0 bytes
+ if (0) {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT,
+ 'The central dir is not at the end of the archive.'
+ .' Some trailing bytes exists after the archive.');
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+ }
+
+ // ----- Get comment
+ if ($v_data['comment_size'] != 0) {
+ $p_central_dir['comment'] = fread($this->zip_fd, $v_data['comment_size']);
+ }
+ else
+ $p_central_dir['comment'] = '';
+
+ $p_central_dir['entries'] = $v_data['entries'];
+ $p_central_dir['disk_entries'] = $v_data['disk_entries'];
+ $p_central_dir['offset'] = $v_data['offset'];
+ $p_central_dir['size'] = $v_data['size'];
+ $p_central_dir['disk'] = $v_data['disk'];
+ $p_central_dir['disk_start'] = $v_data['disk_start'];
+
+ // TBC
+ //for(reset($p_central_dir); $key = key($p_central_dir); next($p_central_dir)) {
+ //}
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privDeleteByRule()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privDeleteByRule(&$p_result_list, &$p_options)
+ {
+ $v_result=1;
+ $v_list_detail = array();
+
+ // ----- Open the zip file
+ if (($v_result=$this->privOpenFd('rb')) != 1)
+ {
+ // ----- Return
+ return $v_result;
+ }
+
+ // ----- Read the central directory informations
+ $v_central_dir = array();
+ if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1)
+ {
+ $this->privCloseFd();
+ return $v_result;
+ }
+
+ // ----- Go to beginning of File
+ @rewind($this->zip_fd);
+
+ // ----- Scan all the files
+ // ----- Start at beginning of Central Dir
+ $v_pos_entry = $v_central_dir['offset'];
+ @rewind($this->zip_fd);
+ if (@fseek($this->zip_fd, $v_pos_entry))
+ {
+ // ----- Close the zip file
+ $this->privCloseFd();
+
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size');
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Read each entry
+ $v_header_list = array();
+ $j_start = 0;
+ for ($i=0, $v_nb_extracted=0; $i<$v_central_dir['entries']; $i++)
+ {
+
+ // ----- Read the file header
+ $v_header_list[$v_nb_extracted] = array();
+ if (($v_result = $this->privReadCentralFileHeader($v_header_list[$v_nb_extracted])) != 1)
+ {
+ // ----- Close the zip file
+ $this->privCloseFd();
+
+ return $v_result;
+ }
+
+
+ // ----- Store the index
+ $v_header_list[$v_nb_extracted]['index'] = $i;
+
+ // ----- Look for the specific extract rules
+ $v_found = false;
+
+ // ----- Look for extract by name rule
+ if ( (isset($p_options[PCLZIP_OPT_BY_NAME]))
+ && ($p_options[PCLZIP_OPT_BY_NAME] != 0)) {
+
+ // ----- Look if the filename is in the list
+ for ($j=0; ($j<sizeof($p_options[PCLZIP_OPT_BY_NAME])) && (!$v_found); $j++) {
+
+ // ----- Look for a directory
+ if (substr($p_options[PCLZIP_OPT_BY_NAME][$j], -1) == "/") {
+
+ // ----- Look if the directory is in the filename path
+ if ( (strlen($v_header_list[$v_nb_extracted]['stored_filename']) > strlen($p_options[PCLZIP_OPT_BY_NAME][$j]))
+ && (substr($v_header_list[$v_nb_extracted]['stored_filename'], 0, strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) == $p_options[PCLZIP_OPT_BY_NAME][$j])) {
+ $v_found = true;
+ }
+ elseif ( (($v_header_list[$v_nb_extracted]['external']&0x00000010)==0x00000010) /* Indicates a folder */
+ && ($v_header_list[$v_nb_extracted]['stored_filename'].'/' == $p_options[PCLZIP_OPT_BY_NAME][$j])) {
+ $v_found = true;
+ }
+ }
+ // ----- Look for a filename
+ elseif ($v_header_list[$v_nb_extracted]['stored_filename'] == $p_options[PCLZIP_OPT_BY_NAME][$j]) {
+ $v_found = true;
+ }
+ }
+ }
+
+ // ----- Look for extract by ereg rule
+ // ereg() is deprecated with PHP 5.3
+ /*
+ else if ( (isset($p_options[PCLZIP_OPT_BY_EREG]))
+ && ($p_options[PCLZIP_OPT_BY_EREG] != "")) {
+
+ if (ereg($p_options[PCLZIP_OPT_BY_EREG], $v_header_list[$v_nb_extracted]['stored_filename'])) {
+ $v_found = true;
+ }
+ }
+ */
+
+ // ----- Look for extract by preg rule
+ else if ( (isset($p_options[PCLZIP_OPT_BY_PREG]))
+ && ($p_options[PCLZIP_OPT_BY_PREG] != "")) {
+
+ if (preg_match($p_options[PCLZIP_OPT_BY_PREG], $v_header_list[$v_nb_extracted]['stored_filename'])) {
+ $v_found = true;
+ }
+ }
+
+ // ----- Look for extract by index rule
+ else if ( (isset($p_options[PCLZIP_OPT_BY_INDEX]))
+ && ($p_options[PCLZIP_OPT_BY_INDEX] != 0)) {
+
+ // ----- Look if the index is in the list
+ for ($j=$j_start; ($j<sizeof($p_options[PCLZIP_OPT_BY_INDEX])) && (!$v_found); $j++) {
+
+ if (($i>=$p_options[PCLZIP_OPT_BY_INDEX][$j]['start']) && ($i<=$p_options[PCLZIP_OPT_BY_INDEX][$j]['end'])) {
+ $v_found = true;
+ }
+ if ($i>=$p_options[PCLZIP_OPT_BY_INDEX][$j]['end']) {
+ $j_start = $j+1;
+ }
+
+ if ($p_options[PCLZIP_OPT_BY_INDEX][$j]['start']>$i) {
+ break;
+ }
+ }
+ }
+ else {
+ $v_found = true;
+ }
+
+ // ----- Look for deletion
+ if ($v_found)
+ {
+ unset($v_header_list[$v_nb_extracted]);
+ }
+ else
+ {
+ $v_nb_extracted++;
+ }
+ }
+
+ // ----- Look if something need to be deleted
+ if ($v_nb_extracted > 0) {
+
+ // ----- Creates a temporay file
+ $v_zip_temp_name = PCLZIP_TEMPORARY_DIR.uniqid('pclzip-').'.tmp';
+
+ // ----- Creates a temporary zip archive
+ $v_temp_zip = new PclZip($v_zip_temp_name);
+
+ // ----- Open the temporary zip file in write mode
+ if (($v_result = $v_temp_zip->privOpenFd('wb')) != 1) {
+ $this->privCloseFd();
+
+ // ----- Return
+ return $v_result;
+ }
+
+ // ----- Look which file need to be kept
+ for ($i=0; $i<sizeof($v_header_list); $i++) {
+
+ // ----- Calculate the position of the header
+ @rewind($this->zip_fd);
+ if (@fseek($this->zip_fd, $v_header_list[$i]['offset'])) {
+ // ----- Close the zip file
+ $this->privCloseFd();
+ $v_temp_zip->privCloseFd();
+ @unlink($v_zip_temp_name);
+
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size');
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Read the file header
+ $v_local_header = array();
+ if (($v_result = $this->privReadFileHeader($v_local_header)) != 1) {
+ // ----- Close the zip file
+ $this->privCloseFd();
+ $v_temp_zip->privCloseFd();
+ @unlink($v_zip_temp_name);
+
+ // ----- Return
+ return $v_result;
+ }
+
+ // ----- Check that local file header is same as central file header
+ if ($this->privCheckFileHeaders($v_local_header,
+ $v_header_list[$i]) != 1) {
+ // TBC
+ }
+ unset($v_local_header);
+
+ // ----- Write the file header
+ if (($v_result = $v_temp_zip->privWriteFileHeader($v_header_list[$i])) != 1) {
+ // ----- Close the zip file
+ $this->privCloseFd();
+ $v_temp_zip->privCloseFd();
+ @unlink($v_zip_temp_name);
+
+ // ----- Return
+ return $v_result;
+ }
+
+ // ----- Read/write the data block
+ if (($v_result = PclZipUtilCopyBlock($this->zip_fd, $v_temp_zip->zip_fd, $v_header_list[$i]['compressed_size'])) != 1) {
+ // ----- Close the zip file
+ $this->privCloseFd();
+ $v_temp_zip->privCloseFd();
+ @unlink($v_zip_temp_name);
+
+ // ----- Return
+ return $v_result;
+ }
+ }
+
+ // ----- Store the offset of the central dir
+ $v_offset = @ftell($v_temp_zip->zip_fd);
+
+ // ----- Re-Create the Central Dir files header
+ for ($i=0; $i<sizeof($v_header_list); $i++) {
+ // ----- Create the file header
+ if (($v_result = $v_temp_zip->privWriteCentralFileHeader($v_header_list[$i])) != 1) {
+ $v_temp_zip->privCloseFd();
+ $this->privCloseFd();
+ @unlink($v_zip_temp_name);
+
+ // ----- Return
+ return $v_result;
+ }
+
+ // ----- Transform the header to a 'usable' info
+ $v_temp_zip->privConvertHeader2FileInfo($v_header_list[$i], $p_result_list[$i]);
+ }
+
+
+ // ----- Zip file comment
+ $v_comment = '';
+ if (isset($p_options[PCLZIP_OPT_COMMENT])) {
+ $v_comment = $p_options[PCLZIP_OPT_COMMENT];
+ }
+
+ // ----- Calculate the size of the central header
+ $v_size = @ftell($v_temp_zip->zip_fd)-$v_offset;
+
+ // ----- Create the central dir footer
+ if (($v_result = $v_temp_zip->privWriteCentralHeader(sizeof($v_header_list), $v_size, $v_offset, $v_comment)) != 1) {
+ // ----- Reset the file list
+ unset($v_header_list);
+ $v_temp_zip->privCloseFd();
+ $this->privCloseFd();
+ @unlink($v_zip_temp_name);
+
+ // ----- Return
+ return $v_result;
+ }
+
+ // ----- Close
+ $v_temp_zip->privCloseFd();
+ $this->privCloseFd();
+
+ // ----- Delete the zip file
+ // TBC : I should test the result ...
+ @unlink($this->zipname);
+
+ // ----- Rename the temporary file
+ // TBC : I should test the result ...
+ //@rename($v_zip_temp_name, $this->zipname);
+ PclZipUtilRename($v_zip_temp_name, $this->zipname);
+
+ // ----- Destroy the temporary archive
+ unset($v_temp_zip);
+ }
+
+ // ----- Remove every files : reset the file
+ else if ($v_central_dir['entries'] != 0) {
+ $this->privCloseFd();
+
+ if (($v_result = $this->privOpenFd('wb')) != 1) {
+ return $v_result;
+ }
+
+ if (($v_result = $this->privWriteCentralHeader(0, 0, 0, '')) != 1) {
+ return $v_result;
+ }
+
+ $this->privCloseFd();
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privDirCheck()
+ // Description :
+ // Check if a directory exists, if not it creates it and all the parents directory
+ // which may be useful.
+ // Parameters :
+ // $p_dir : Directory path to check.
+ // Return Values :
+ // 1 : OK
+ // -1 : Unable to create directory
+ // --------------------------------------------------------------------------------
+ function privDirCheck($p_dir, $p_is_dir=false)
+ {
+ $v_result = 1;
+
+
+ // ----- Remove the final '/'
+ if (($p_is_dir) && (substr($p_dir, -1)=='/'))
+ {
+ $p_dir = substr($p_dir, 0, strlen($p_dir)-1);
+ }
+
+ // ----- Check the directory availability
+ if ((is_dir($p_dir)) || ($p_dir == ""))
+ {
+ return 1;
+ }
+
+ // ----- Extract parent directory
+ $p_parent_dir = dirname($p_dir);
+
+ // ----- Just a check
+ if ($p_parent_dir != $p_dir)
+ {
+ // ----- Look for parent directory
+ if ($p_parent_dir != "")
+ {
+ if (($v_result = $this->privDirCheck($p_parent_dir)) != 1)
+ {
+ return $v_result;
+ }
+ }
+ }
+
+ // ----- Create the directory
+ if (!@mkdir($p_dir, 0777))
+ {
+ // ----- Error log
+ PclZip::privErrorLog(PCLZIP_ERR_DIR_CREATE_FAIL, "Unable to create directory '$p_dir'");
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privMerge()
+ // Description :
+ // If $p_archive_to_add does not exist, the function exit with a success result.
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privMerge(&$p_archive_to_add)
+ {
+ $v_result=1;
+
+ // ----- Look if the archive_to_add exists
+ if (!is_file($p_archive_to_add->zipname))
+ {
+
+ // ----- Nothing to merge, so merge is a success
+ $v_result = 1;
+
+ // ----- Return
+ return $v_result;
+ }
+
+ // ----- Look if the archive exists
+ if (!is_file($this->zipname))
+ {
+
+ // ----- Do a duplicate
+ $v_result = $this->privDuplicate($p_archive_to_add->zipname);
+
+ // ----- Return
+ return $v_result;
+ }
+
+ // ----- Open the zip file
+ if (($v_result=$this->privOpenFd('rb')) != 1)
+ {
+ // ----- Return
+ return $v_result;
+ }
+
+ // ----- Read the central directory informations
+ $v_central_dir = array();
+ if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1)
+ {
+ $this->privCloseFd();
+ return $v_result;
+ }
+
+ // ----- Go to beginning of File
+ @rewind($this->zip_fd);
+
+ // ----- Open the archive_to_add file
+ if (($v_result=$p_archive_to_add->privOpenFd('rb')) != 1)
+ {
+ $this->privCloseFd();
+
+ // ----- Return
+ return $v_result;
+ }
+
+ // ----- Read the central directory informations
+ $v_central_dir_to_add = array();
+ if (($v_result = $p_archive_to_add->privReadEndCentralDir($v_central_dir_to_add)) != 1)
+ {
+ $this->privCloseFd();
+ $p_archive_to_add->privCloseFd();
+
+ return $v_result;
+ }
+
+ // ----- Go to beginning of File
+ @rewind($p_archive_to_add->zip_fd);
+
+ // ----- Creates a temporay file
+ $v_zip_temp_name = PCLZIP_TEMPORARY_DIR.uniqid('pclzip-').'.tmp';
+
+ // ----- Open the temporary file in write mode
+ if (($v_zip_temp_fd = @fopen($v_zip_temp_name, 'wb')) == 0)
+ {
+ $this->privCloseFd();
+ $p_archive_to_add->privCloseFd();
+
+ PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \''.$v_zip_temp_name.'\' in binary write mode');
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Copy the files from the archive to the temporary file
+ // TBC : Here I should better append the file and go back to erase the central dir
+ $v_size = $v_central_dir['offset'];
+ while ($v_size != 0)
+ {
+ $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+ $v_buffer = fread($this->zip_fd, $v_read_size);
+ @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size);
+ $v_size -= $v_read_size;
+ }
+
+ // ----- Copy the files from the archive_to_add into the temporary file
+ $v_size = $v_central_dir_to_add['offset'];
+ while ($v_size != 0)
+ {
+ $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+ $v_buffer = fread($p_archive_to_add->zip_fd, $v_read_size);
+ @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size);
+ $v_size -= $v_read_size;
+ }
+
+ // ----- Store the offset of the central dir
+ $v_offset = @ftell($v_zip_temp_fd);
+
+ // ----- Copy the block of file headers from the old archive
+ $v_size = $v_central_dir['size'];
+ while ($v_size != 0)
+ {
+ $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+ $v_buffer = @fread($this->zip_fd, $v_read_size);
+ @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size);
+ $v_size -= $v_read_size;
+ }
+
+ // ----- Copy the block of file headers from the archive_to_add
+ $v_size = $v_central_dir_to_add['size'];
+ while ($v_size != 0)
+ {
+ $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+ $v_buffer = @fread($p_archive_to_add->zip_fd, $v_read_size);
+ @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size);
+ $v_size -= $v_read_size;
+ }
+
+ // ----- Merge the file comments
+ $v_comment = $v_central_dir['comment'].' '.$v_central_dir_to_add['comment'];
+
+ // ----- Calculate the size of the (new) central header
+ $v_size = @ftell($v_zip_temp_fd)-$v_offset;
+
+ // ----- Swap the file descriptor
+ // Here is a trick : I swap the temporary fd with the zip fd, in order to use
+ // the following methods on the temporary fil and not the real archive fd
+ $v_swap = $this->zip_fd;
+ $this->zip_fd = $v_zip_temp_fd;
+ $v_zip_temp_fd = $v_swap;
+
+ // ----- Create the central dir footer
+ if (($v_result = $this->privWriteCentralHeader($v_central_dir['entries']+$v_central_dir_to_add['entries'], $v_size, $v_offset, $v_comment)) != 1)
+ {
+ $this->privCloseFd();
+ $p_archive_to_add->privCloseFd();
+ @fclose($v_zip_temp_fd);
+ $this->zip_fd = null;
+
+ // ----- Reset the file list
+ unset($v_header_list);
+
+ // ----- Return
+ return $v_result;
+ }
+
+ // ----- Swap back the file descriptor
+ $v_swap = $this->zip_fd;
+ $this->zip_fd = $v_zip_temp_fd;
+ $v_zip_temp_fd = $v_swap;
+
+ // ----- Close
+ $this->privCloseFd();
+ $p_archive_to_add->privCloseFd();
+
+ // ----- Close the temporary file
+ @fclose($v_zip_temp_fd);
+
+ // ----- Delete the zip file
+ // TBC : I should test the result ...
+ @unlink($this->zipname);
+
+ // ----- Rename the temporary file
+ // TBC : I should test the result ...
+ //@rename($v_zip_temp_name, $this->zipname);
+ PclZipUtilRename($v_zip_temp_name, $this->zipname);
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privDuplicate()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privDuplicate($p_archive_filename)
+ {
+ $v_result=1;
+
+ // ----- Look if the $p_archive_filename exists
+ if (!is_file($p_archive_filename))
+ {
+
+ // ----- Nothing to duplicate, so duplicate is a success.
+ $v_result = 1;
+
+ // ----- Return
+ return $v_result;
+ }
+
+ // ----- Open the zip file
+ if (($v_result=$this->privOpenFd('wb')) != 1)
+ {
+ // ----- Return
+ return $v_result;
+ }
+
+ // ----- Open the temporary file in write mode
+ if (($v_zip_temp_fd = @fopen($p_archive_filename, 'rb')) == 0)
+ {
+ $this->privCloseFd();
+
+ PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive file \''.$p_archive_filename.'\' in binary write mode');
+
+ // ----- Return
+ return PclZip::errorCode();
+ }
+
+ // ----- Copy the files from the archive to the temporary file
+ // TBC : Here I should better append the file and go back to erase the central dir
+ $v_size = filesize($p_archive_filename);
+ while ($v_size != 0)
+ {
+ $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+ $v_buffer = fread($v_zip_temp_fd, $v_read_size);
+ @fwrite($this->zip_fd, $v_buffer, $v_read_size);
+ $v_size -= $v_read_size;
+ }
+
+ // ----- Close
+ $this->privCloseFd();
+
+ // ----- Close the temporary file
+ @fclose($v_zip_temp_fd);
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privErrorLog()
+ // Description :
+ // Parameters :
+ // --------------------------------------------------------------------------------
+ function privErrorLog($p_error_code=0, $p_error_string='')
+ {
+ if (PCLZIP_ERROR_EXTERNAL == 1) {
+ PclError($p_error_code, $p_error_string);
+ }
+ else {
+ $this->error_code = $p_error_code;
+ $this->error_string = $p_error_string;
+ }
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privErrorReset()
+ // Description :
+ // Parameters :
+ // --------------------------------------------------------------------------------
+ function privErrorReset()
+ {
+ if (PCLZIP_ERROR_EXTERNAL == 1) {
+ PclErrorReset();
+ }
+ else {
+ $this->error_code = 0;
+ $this->error_string = '';
+ }
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privDisableMagicQuotes()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privDisableMagicQuotes()
+ {
+ $v_result=1;
+
+ // ----- Look if function exists
+ if ( (!function_exists("get_magic_quotes_runtime"))
+ || (!function_exists("set_magic_quotes_runtime"))) {
+ return $v_result;
+ }
+
+ // ----- Look if already done
+ if ($this->magic_quotes_status != -1) {
+ return $v_result;
+ }
+
+ // ----- Get and memorize the magic_quote value
+ $this->magic_quotes_status = @get_magic_quotes_runtime();
+
+ // ----- Disable magic_quotes
+ if ($this->magic_quotes_status == 1) {
+ @set_magic_quotes_runtime(0);
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : privSwapBackMagicQuotes()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function privSwapBackMagicQuotes()
+ {
+ $v_result=1;
+
+ // ----- Look if function exists
+ if ( (!function_exists("get_magic_quotes_runtime"))
+ || (!function_exists("set_magic_quotes_runtime"))) {
+ return $v_result;
+ }
+
+ // ----- Look if something to do
+ if ($this->magic_quotes_status != -1) {
+ return $v_result;
+ }
+
+ // ----- Swap back magic_quotes
+ if ($this->magic_quotes_status == 1) {
+ @set_magic_quotes_runtime($this->magic_quotes_status);
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ }
+ // End of class
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : PclZipUtilPathReduction()
+ // Description :
+ // Parameters :
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function PclZipUtilPathReduction($p_dir)
+ {
+ $v_result = "";
+
+ // ----- Look for not empty path
+ if ($p_dir != "") {
+ // ----- Explode path by directory names
+ $v_list = explode("/", $p_dir);
+
+ // ----- Study directories from last to first
+ $v_skip = 0;
+ for ($i=sizeof($v_list)-1; $i>=0; $i--) {
+ // ----- Look for current path
+ if ($v_list[$i] == ".") {
+ // ----- Ignore this directory
+ // Should be the first $i=0, but no check is done
+ }
+ else if ($v_list[$i] == "..") {
+ $v_skip++;
+ }
+ else if ($v_list[$i] == "") {
+ // ----- First '/' i.e. root slash
+ if ($i == 0) {
+ $v_result = "/".$v_result;
+ if ($v_skip > 0) {
+ // ----- It is an invalid path, so the path is not modified
+ // TBC
+ $v_result = $p_dir;
+ $v_skip = 0;
+ }
+ }
+ // ----- Last '/' i.e. indicates a directory
+ else if ($i == (sizeof($v_list)-1)) {
+ $v_result = $v_list[$i];
+ }
+ // ----- Double '/' inside the path
+ else {
+ // ----- Ignore only the double '//' in path,
+ // but not the first and last '/'
+ }
+ }
+ else {
+ // ----- Look for item to skip
+ if ($v_skip > 0) {
+ $v_skip--;
+ }
+ else {
+ $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?"/".$v_result:"");
+ }
+ }
+ }
+
+ // ----- Look for skip
+ if ($v_skip > 0) {
+ while ($v_skip > 0) {
+ $v_result = '../'.$v_result;
+ $v_skip--;
+ }
+ }
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : PclZipUtilPathInclusion()
+ // Description :
+ // This function indicates if the path $p_path is under the $p_dir tree. Or,
+ // said in an other way, if the file or sub-dir $p_path is inside the dir
+ // $p_dir.
+ // The function indicates also if the path is exactly the same as the dir.
+ // This function supports path with duplicated '/' like '//', but does not
+ // support '.' or '..' statements.
+ // Parameters :
+ // Return Values :
+ // 0 if $p_path is not inside directory $p_dir
+ // 1 if $p_path is inside directory $p_dir
+ // 2 if $p_path is exactly the same as $p_dir
+ // --------------------------------------------------------------------------------
+ function PclZipUtilPathInclusion($p_dir, $p_path)
+ {
+ $v_result = 1;
+
+ // ----- Look for path beginning by ./
+ if ( ($p_dir == '.')
+ || ((strlen($p_dir) >=2) && (substr($p_dir, 0, 2) == './'))) {
+ $p_dir = PclZipUtilTranslateWinPath(getcwd(), FALSE).'/'.substr($p_dir, 1);
+ }
+ if ( ($p_path == '.')
+ || ((strlen($p_path) >=2) && (substr($p_path, 0, 2) == './'))) {
+ $p_path = PclZipUtilTranslateWinPath(getcwd(), FALSE).'/'.substr($p_path, 1);
+ }
+
+ // ----- Explode dir and path by directory separator
+ $v_list_dir = explode("/", $p_dir);
+ $v_list_dir_size = sizeof($v_list_dir);
+ $v_list_path = explode("/", $p_path);
+ $v_list_path_size = sizeof($v_list_path);
+
+ // ----- Study directories paths
+ $i = 0;
+ $j = 0;
+ while (($i < $v_list_dir_size) && ($j < $v_list_path_size) && ($v_result)) {
+
+ // ----- Look for empty dir (path reduction)
+ if ($v_list_dir[$i] == '') {
+ $i++;
+ continue;
+ }
+ if ($v_list_path[$j] == '') {
+ $j++;
+ continue;
+ }
+
+ // ----- Compare the items
+ if (($v_list_dir[$i] != $v_list_path[$j]) && ($v_list_dir[$i] != '') && ( $v_list_path[$j] != '')) {
+ $v_result = 0;
+ }
+
+ // ----- Next items
+ $i++;
+ $j++;
+ }
+
+ // ----- Look if everything seems to be the same
+ if ($v_result) {
+ // ----- Skip all the empty items
+ while (($j < $v_list_path_size) && ($v_list_path[$j] == '')) $j++;
+ while (($i < $v_list_dir_size) && ($v_list_dir[$i] == '')) $i++;
+
+ if (($i >= $v_list_dir_size) && ($j >= $v_list_path_size)) {
+ // ----- There are exactly the same
+ $v_result = 2;
+ }
+ else if ($i < $v_list_dir_size) {
+ // ----- The path is shorter than the dir
+ $v_result = 0;
+ }
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : PclZipUtilCopyBlock()
+ // Description :
+ // Parameters :
+ // $p_mode : read/write compression mode
+ // 0 : src & dest normal
+ // 1 : src gzip, dest normal
+ // 2 : src normal, dest gzip
+ // 3 : src & dest gzip
+ // Return Values :
+ // --------------------------------------------------------------------------------
+ function PclZipUtilCopyBlock($p_src, $p_dest, $p_size, $p_mode=0)
+ {
+ $v_result = 1;
+
+ if ($p_mode==0)
+ {
+ while ($p_size != 0)
+ {
+ $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE);
+ $v_buffer = @fread($p_src, $v_read_size);
+ @fwrite($p_dest, $v_buffer, $v_read_size);
+ $p_size -= $v_read_size;
+ }
+ }
+ else if ($p_mode==1)
+ {
+ while ($p_size != 0)
+ {
+ $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE);
+ $v_buffer = @gzread($p_src, $v_read_size);
+ @fwrite($p_dest, $v_buffer, $v_read_size);
+ $p_size -= $v_read_size;
+ }
+ }
+ else if ($p_mode==2)
+ {
+ while ($p_size != 0)
+ {
+ $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE);
+ $v_buffer = @fread($p_src, $v_read_size);
+ @gzwrite($p_dest, $v_buffer, $v_read_size);
+ $p_size -= $v_read_size;
+ }
+ }
+ else if ($p_mode==3)
+ {
+ while ($p_size != 0)
+ {
+ $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE);
+ $v_buffer = @gzread($p_src, $v_read_size);
+ @gzwrite($p_dest, $v_buffer, $v_read_size);
+ $p_size -= $v_read_size;
+ }
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : PclZipUtilRename()
+ // Description :
+ // This function tries to do a simple rename() function. If it fails, it
+ // tries to copy the $p_src file in a new $p_dest file and then unlink the
+ // first one.
+ // Parameters :
+ // $p_src : Old filename
+ // $p_dest : New filename
+ // Return Values :
+ // 1 on success, 0 on failure.
+ // --------------------------------------------------------------------------------
+ function PclZipUtilRename($p_src, $p_dest)
+ {
+ $v_result = 1;
+
+ // ----- Try to rename the files
+ if (!@rename($p_src, $p_dest)) {
+
+ // ----- Try to copy & unlink the src
+ if (!@copy($p_src, $p_dest)) {
+ $v_result = 0;
+ }
+ else if (!@unlink($p_src)) {
+ $v_result = 0;
+ }
+ }
+
+ // ----- Return
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : PclZipUtilOptionText()
+ // Description :
+ // Translate option value in text. Mainly for debug purpose.
+ // Parameters :
+ // $p_option : the option value.
+ // Return Values :
+ // The option text value.
+ // --------------------------------------------------------------------------------
+ function PclZipUtilOptionText($p_option)
+ {
+
+ $v_list = get_defined_constants();
+ for (reset($v_list); $v_key = key($v_list); next($v_list)) {
+ $v_prefix = substr($v_key, 0, 10);
+ if (( ($v_prefix == 'PCLZIP_OPT')
+ || ($v_prefix == 'PCLZIP_CB_')
+ || ($v_prefix == 'PCLZIP_ATT'))
+ && ($v_list[$v_key] == $p_option)) {
+ return $v_key;
+ }
+ }
+
+ $v_result = 'Unknown';
+
+ return $v_result;
+ }
+ // --------------------------------------------------------------------------------
+
+ // --------------------------------------------------------------------------------
+ // Function : PclZipUtilTranslateWinPath()
+ // Description :
+ // Translate windows path by replacing '\' by '/' and optionally removing
+ // drive letter.
+ // Parameters :
+ // $p_path : path to translate.
+ // $p_remove_disk_letter : true | false
+ // Return Values :
+ // The path translated.
+ // --------------------------------------------------------------------------------
+ function PclZipUtilTranslateWinPath($p_path, $p_remove_disk_letter=true)
+ {
+ if (stristr(php_uname(), 'windows')) {
+ // ----- Look for potential disk letter
+ if (($p_remove_disk_letter) && (($v_position = strpos($p_path, ':')) != false)) {
+ $p_path = substr($p_path, $v_position+1);
+ }
+ // ----- Change potential windows directory separator
+ if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) {
+ $p_path = strtr($p_path, '\\', '/');
+ }
+ }
+ return $p_path;
+ }
+ // --------------------------------------------------------------------------------
+
+
+?>
Index: v1.1/trunk/scripts/bibliotheque/Script.php
===================================================================
--- v1.1/trunk/scripts/bibliotheque/Script.php (revision 0)
+++ v1.1/trunk/scripts/bibliotheque/Script.php (revision 287)
@@ -0,0 +1,71 @@
+<?php
+// Encodage : UTF-8
+// +-------------------------------------------------------------------------------------------------------------------+
+/**
+* Script
+*
+* Description : Fabrique permettant de charger les scripts
+*
+//Auteur original :
+* @author Jean-Pascal MILCENT <jpm@tela-botanica.org>
+* @copyright Tela-Botanica 1999-2008
+* @licence GPL v3 & CeCILL v2
+* @version $Id$
+*/
+// +-------------------------------------------------------------------------------------------------------------------+
+
+class Script {
+ /** Niveau de message de type LOG */
+ const LOG = 0;
+ /** Niveau de message de type ERREUR */
+ const ERREUR = 1;
+ /** Niveau de message de type AVERTISSEMENT */
+ const AVERTISSEMENT = 2;
+ /** Niveau de message de type INFORMATION */
+ const INFO = 3;
+
+ public static function getCode($niveau) {
+ $txt_niveaux = array('LOG', 'ERREUR','AVERTISSEMENT', 'INFO');
+ return $txt_niveaux[$niveau];
+ }
+
+ public static function charger($commande_nom) {
+ $classe_nom = implode('', array_map('ucfirst', explode('_', strtolower($commande_nom))));
+ $fichier_script = ES_CHEMIN_MODULE.$commande_nom.DS.$classe_nom.'.php';
+ if (!file_exists($fichier_script)){
+ trigger_error("Erreur : script '$fichier_script' inconnu!\n", E_USER_ERROR);
+ }
+ require_once $fichier_script;
+ if (!class_exists( $classe_nom)) {
+ trigger_error("Erreur: impossible de trouver la classe de la commande : $classe_nom\n", E_USER_ERROR);
+ }
+ $Script = new $classe_nom($commande_nom);
+ return $Script;
+ }
+
+ public static function getParametres($argv) {
+ $parametres = array();
+ // Récupération des options
+ while(count($argv)) {
+ if (isset($argv[1]) && $argv[1]{0} != '-') {
+ $param = array_shift($argv);
+ $parametres[$param] = array_shift($argv);
+ } elseif (!isset($argv[1]) || $argv[1]{0} == '-') {
+ $parametres[array_shift($argv)] = null;
+ } else {
+ trigger_error("Erreur: valeur manquante pour le paramêtre '".$argv[0]."' \n", E_USER_ERROR);
+ }
+ }
+ return $parametres;
+ }
+
+ public static function setAutoloadChemin($chemin) {
+ if (is_array($chemin)) {
+ $GLOBALS['chemins_autoload'] = array_unique(array_merge($GLOBALS['chemins_autoload'], $chemin));
+ } else {
+ $GLOBALS['chemins_autoload'][] = $chemin;
+ }
+ }
+
+}
+?>
\ No newline at end of file
Index: v1.1/trunk/scripts/bibliotheque/pclzip-readme.txt
===================================================================
--- v1.1/trunk/scripts/bibliotheque/pclzip-readme.txt (revision 0)
+++ v1.1/trunk/scripts/bibliotheque/pclzip-readme.txt (revision 287)
@@ -0,0 +1,421 @@
+// --------------------------------------------------------------------------------
+// PclZip 2.8.2 - readme.txt
+// --------------------------------------------------------------------------------
+// License GNU/LGPL - August 2009
+// Vincent Blavet - vincent@phpconcept.net
+// http://www.phpconcept.net
+// --------------------------------------------------------------------------------
+// $Id: readme.txt,v 1.60 2009/09/30 20:35:21 vblavet Exp $
+// --------------------------------------------------------------------------------
+
+
+
+0 - Sommaire
+============
+ 1 - Introduction
+ 2 - What's new
+ 3 - Corrected bugs
+ 4 - Known bugs or limitations
+ 5 - License
+ 6 - Warning
+ 7 - Documentation
+ 8 - Author
+ 9 - Contribute
+
+1 - Introduction
+================
+
+ PclZip is a library that allow you to manage a Zip archive.
+
+ Full documentation about PclZip can be found here : http://www.phpconcept.net/pclzip
+
+2 - What's new
+==============
+
+ Version 2.8.2 :
+ - PCLZIP_CB_PRE_EXTRACT and PCLZIP_CB_POST_EXTRACT are now supported with
+ extraction as a string (PCLZIP_OPT_EXTRACT_AS_STRING). The string
+ can also be modified in the post-extract call back.
+ **Bugs correction :
+ - PCLZIP_OPT_REMOVE_ALL_PATH was not working correctly
+ - Remove use of eval() and do direct call to callback functions
+ - Correct support of 64bits systems (Thanks to WordPress team)
+
+ Version 2.8.1 :
+ - Move option PCLZIP_OPT_BY_EREG to PCLZIP_OPT_BY_PREG because ereg() is
+ deprecated in PHP 5.3. When using option PCLZIP_OPT_BY_EREG, PclZip will
+ automatically replace it by PCLZIP_OPT_BY_PREG.
+
+ Version 2.8 :
+ - Improve extraction of zip archive for large files by using temporary files
+ This feature is working like the one defined in r2.7.
+ Options are renamed : PCLZIP_OPT_TEMP_FILE_ON, PCLZIP_OPT_TEMP_FILE_OFF,
+ PCLZIP_OPT_TEMP_FILE_THRESHOLD
+ - Add a ratio constant PCLZIP_TEMPORARY_FILE_RATIO to configure the auto
+ sense of temporary file use.
+ - Bug correction : Reduce filepath in returned file list to remove ennoying
+ './/' preambule in file path.
+
+ Version 2.7 :
+ - Improve creation of zip archive for large files :
+ PclZip will now autosense the configured memory and use temporary files
+ when large file is suspected.
+ This feature can also ne triggered by manual options in create() and add()
+ methods. 'PCLZIP_OPT_ADD_TEMP_FILE_ON' force the use of temporary files,
+ 'PCLZIP_OPT_ADD_TEMP_FILE_OFF' disable the autosense technic,
+ 'PCLZIP_OPT_ADD_TEMP_FILE_THRESHOLD' allow for configuration of a size
+ threshold to use temporary files.
+ Using "temporary files" rather than "memory" might take more time, but
+ might give the ability to zip very large files :
+ Tested on my win laptop with a 88Mo file :
+ Zip "in-memory" : 18sec (max_execution_time=30, memory_limit=180Mo)
+ Zip "tmporary-files" : 23sec (max_execution_time=30, memory_limit=30Mo)
+ - Replace use of mktime() by time() to limit the E_STRICT error messages.
+ - Bug correction : When adding files with full windows path (drive letter)
+ PclZip is now working. Before, if the drive letter is not the default
+ path, PclZip was not able to add the file.
+
+ Version 2.6 :
+ - Code optimisation
+ - New attributes PCLZIP_ATT_FILE_COMMENT gives the ability to
+ add a comment for a specific file. (Don't really know if this is usefull)
+ - New attribute PCLZIP_ATT_FILE_CONTENT gives the ability to add a string
+ as a file.
+ - New attribute PCLZIP_ATT_FILE_MTIME modify the timestamp associated with
+ a file.
+ - Correct a bug. Files archived with a timestamp with 0h0m0s were extracted
+ with current time
+ - Add CRC value in the informations returned back for each file after an
+ action.
+ - Add missing closedir() statement.
+ - When adding a folder, and removing the path of this folder, files were
+ incorrectly added with a '/' at the beginning. Which means files are
+ related to root in unix systems. Corrected.
+ - Add conditional if before constant definition. This will allow users
+ to redefine constants without changing the file, and then improve
+ upgrade of pclzip code for new versions.
+
+ Version 2.5 :
+ - Introduce the ability to add file/folder with individual properties (file descriptor).
+ This gives for example the ability to change the filename of a zipped file.
+ . Able to add files individually
+ . Able to change full name
+ . Able to change short name
+ . Compatible with global options
+ - New attributes : PCLZIP_ATT_FILE_NAME, PCLZIP_ATT_FILE_NEW_SHORT_NAME, PCLZIP_ATT_FILE_NEW_FULL_NAME
+ - New error code : PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE
+ - Add a security control feature. PclZip can extract any file in any folder
+ of a system. People may use this to upload a zip file and try to override
+ a system file. The PCLZIP_OPT_EXTRACT_DIR_RESTRICTION will give the
+ ability to forgive any directory transversal behavior.
+ - New PCLZIP_OPT_EXTRACT_DIR_RESTRICTION : check extraction path
+ - New error code : PCLZIP_ERR_DIRECTORY_RESTRICTION
+ - Modification in PclZipUtilPathInclusion() : dir and path beginning with ./ will be prepend
+ by current path (getcwd())
+
+ Version 2.4 :
+ - Code improvment : try to speed up the code by removing unusefull call to pack()
+ - Correct bug in delete() : delete() should be called with no argument. This was not
+ the case in 2.3. This is corrected in 2.4.
+ - Correct a bug in path_inclusion function. When the path has several '../../', the
+ result was bad.
+ - Add a check for magic_quotes_runtime configuration. If enabled, PclZip will
+ disable it while working and det it back to its original value.
+ This resolve a lots of bad formated archive errors.
+ - Bug correction : PclZip now correctly unzip file in some specific situation,
+ when compressed content has same size as uncompressed content.
+ - Bug correction : When selecting option 'PCLZIP_OPT_REMOVE_ALL_PATH',
+ directories are not any more created.
+ - Code improvment : correct unclosed opendir(), better handling of . and .. in
+ loops.
+
+
+ Version 2.3 :
+ - Correct a bug with PHP5 : affecting the value 0xFE49FFE0 to a variable does not
+ give the same result in PHP4 and PHP5 ....
+
+ Version 2.2 :
+ - Try development of PCLZIP_OPT_CRYPT .....
+ However this becomes to a stop. To crypt/decrypt I need to multiply 2 long integers,
+ the result (greater than a long) is not supported by PHP. Even the use of bcmath
+ functions does not help. I did not find yet a solution ...;
+ - Add missing '/' at end of directory entries
+ - Check is a file is encrypted or not. Returns status 'unsupported_encryption' and/or
+ error code PCLZIP_ERR_UNSUPPORTED_ENCRYPTION.
+ - Corrected : Bad "version need to extract" field in local file header
+ - Add private method privCheckFileHeaders() in order to check local and central
+ file headers. PclZip is now supporting purpose bit flag bit 3. Purpose bit flag bit 3 gives
+ the ability to have a local file header without size, compressed size and crc filled.
+ - Add a generic status 'error' for file status
+ - Add control of compression type. PclZip only support deflate compression method.
+ Before v2.2, PclZip does not check the compression method used in an archive while
+ extracting. With v2.2 PclZip returns a new error status for a file using an unsupported
+ compression method. New status is "unsupported_compression". New error code is
+ PCLZIP_ERR_UNSUPPORTED_COMPRESSION.
+ - Add optional attribute PCLZIP_OPT_STOP_ON_ERROR. This will stop the extract of files
+ when errors like 'a folder with same name exists' or 'a newer file exists' or
+ 'a write protected file' exists, rather than set a status for the concerning file
+ and resume the extract of the zip.
+ - Add optional attribute PCLZIP_OPT_REPLACE_NEWER. This will force, during an extract' the
+ replacement of the file, even if a newer version of the file exists.
+ Note that today if a file with the same name already exists but is older it will be
+ replaced by the extracted one.
+ - Improve PclZipUtilOption()
+ - Support of zip archive with trailing bytes. Before 2.2, PclZip checks that the central
+ directory structure is the last data in the archive. Crypt encryption/decryption of
+ zip archive put trailing 0 bytes after decryption. PclZip is now supporting this.
+
+ Version 2.1 :
+ - Add the ability to abort the extraction by using a user callback function.
+ The user can now return the value '2' in its callback which indicates to stop the
+ extraction. For a pre call-back extract is stopped before the extration of the current
+ file. For a post call back, the extraction is stopped after.
+ - Add the ability to extract a file (or several files) directly in the standard output.
+ This is done by the new parameter PCLZIP_OPT_EXTRACT_IN_OUTPUT with method extract().
+ - Add support for parameters PCLZIP_OPT_COMMENT, PCLZIP_OPT_ADD_COMMENT,
+ PCLZIP_OPT_PREPEND_COMMENT. This will create, replace, add, or prepend comments
+ in the zip archive.
+ - When merging two archives, the comments are not any more lost, but merged, with a
+ blank space separator.
+ - Corrected bug : Files are not deleted when all files are asked to be deleted.
+ - Corrected bug : Folders with name '0' made PclZip to abort the create or add feature.
+
+
+ Version 2.0 :
+ ***** Warning : Some new features may break the backward compatibility for your scripts.
+ Please carefully read the readme file.
+ - Add the ability to delete by Index, name and regular expression. This feature is
+ performed by the method delete(), which uses the optional parameters
+ PCLZIP_OPT_BY_INDEX, PCLZIP_OPT_BY_NAME, PCLZIP_OPT_BY_EREG or PCLZIP_OPT_BY_PREG.
+ - Add the ability to extract by regular expression. To extract by regexp you must use the method
+ extract(), with the option PCLZIP_OPT_BY_EREG or PCLZIP_OPT_BY_PREG
+ (depending if you want to use ereg() or preg_match() syntax) followed by the
+ regular expression pattern.
+ - Add the ability to extract by index, directly with the extract() method. This is a
+ code improvment of the extractByIndex() method.
+ - Add the ability to extract by name. To extract by name you must use the method
+ extract(), with the option PCLZIP_OPT_BY_NAME followed by the filename to
+ extract or an array of filenames to extract. To extract all a folder, use the folder
+ name rather than the filename with a '/' at the end.
+ - Add the ability to add files without compression. This is done with a new attribute
+ which is PCLZIP_OPT_NO_COMPRESSION.
+ - Add the attribute PCLZIP_OPT_EXTRACT_AS_STRING, which allow to extract a file directly
+ in a string without using any file (or temporary file).
+ - Add constant PCLZIP_SEPARATOR for static configuration of filename separators in a single string.
+ The default separator is now a comma (,) and not any more a blank space.
+ THIS BREAK THE BACKWARD COMPATIBILITY : Please check if this may have an impact with
+ your script.
+ - Improve algorythm performance by removing the use of temporary files when adding or
+ extracting files in an archive.
+ - Add (correct) detection of empty filename zipping. This can occurs when the removed
+ path is the same
+ as a zipped dir. The dir is not zipped (['status'] = filtered), only its content.
+ - Add better support for windows paths (thanks for help from manus@manusfreedom.com).
+ - Corrected bug : When the archive file already exists with size=0, the add() method
+ fails. Corrected in 2.0.
+ - Remove the use of OS_WINDOWS constant. Use php_uname() function rather.
+ - Control the order of index ranges in extract by index feature.
+ - Change the internal management of folders (better handling of internal flag).
+
+
+ Version 1.3 :
+ - Removing the double include check. This is now done by include_once() and require_once()
+ PHP directives.
+ - Changing the error handling mecanism : Remove the use of an external error library.
+ The former PclError...() functions are replaced by internal equivalent methods.
+ By changing the environment variable PCLZIP_ERROR_EXTERNAL you can still use the former library.
+ Introducing the use of constants for error codes rather than integer values. This will help
+ in futur improvment.
+ Introduction of error handling functions like errorCode(), errorName() and errorInfo().
+ - Remove the deprecated use of calling function with arguments passed by reference.
+ - Add the calling of extract(), extractByIndex(), create() and add() functions
+ with variable options rather than fixed arguments.
+ - Add the ability to remove all the file path while extracting or adding,
+ without any need to specify the path to remove.
+ This is available for extract(), extractByIndex(), create() and add() functionS by using
+ the new variable options parameters :
+ - PCLZIP_OPT_REMOVE_ALL_PATH : by indicating this option while calling the fct.
+ - Ability to change the mode of a file after the extraction (chmod()).
+ This is available for extract() and extractByIndex() functionS by using
+ the new variable options parameters.
+ - PCLZIP_OPT_SET_CHMOD : by setting the value of this option.
+ - Ability to definition call-back options. These call-back will be called during the adding,
+ or the extracting of file (extract(), extractByIndex(), create() and add() functions) :
+ - PCLZIP_CB_PRE_EXTRACT : will be called before each extraction of a file. The user
+ can trigerred the change the filename of the extracted file. The user can triggered the
+ skip of the extraction. This is adding a 'skipped' status in the file list result value.
+ - PCLZIP_CB_POST_EXTRACT : will be called after each extraction of a file.
+ Nothing can be triggered from that point.
+ - PCLZIP_CB_PRE_ADD : will be called before each add of a file. The user
+ can trigerred the change the stored filename of the added file. The user can triggered the
+ skip of the add. This is adding a 'skipped' status in the file list result value.
+ - PCLZIP_CB_POST_ADD : will be called after each add of a file.
+ Nothing can be triggered from that point.
+ - Two status are added in the file list returned as function result : skipped & filename_too_long
+ 'skipped' is used when a call-back function ask for skipping the file.
+ 'filename_too_long' is used while adding a file with a too long filename to archive (the file is
+ not added)
+ - Adding the function PclZipUtilPathInclusion(), that check the inclusion of a path into
+ a directory.
+ - Add a check of the presence of the archive file before some actions (like list, ...)
+ - Add the initialisation of field "index" in header array. This means that by
+ default index will be -1 when not explicitly set by the methods.
+
+ Version 1.2 :
+ - Adding a duplicate function.
+ - Adding a merge function. The merge function is a "quick merge" function,
+ it just append the content of an archive at the end of the first one. There
+ is no check for duplicate files or more recent files.
+ - Improve the search of the central directory end.
+
+ Version 1.1.2 :
+
+ - Changing the license of PclZip. PclZip is now released under the GNU / LGPL license
+ (see License section).
+ - Adding the optional support of a static temporary directory. You will need to configure
+ the constant PCLZIP_TEMPORARY_DIR if you want to use this feature.
+ - Improving the rename() function. In some cases rename() does not work (different
+ Filesystems), so it will be replaced by a copy() + unlink() functions.
+
+ Version 1.1.1 :
+
+ - Maintenance release, no new feature.
+
+ Version 1.1 :
+
+ - New method Add() : adding files in the archive
+ - New method ExtractByIndex() : partial extract of the archive, files are identified by
+ their index in the archive
+ - New method DeleteByIndex() : delete some files/folder entries from the archive,
+ files are identified by their index in the archive.
+ - Adding a test of the zlib extension presence. If not present abort the script.
+
+ Version 1.0.1 :
+
+ - No new feature
+
+
+3 - Corrected bugs
+==================
+
+ Corrected in Version 2.0 :
+ - Corrected : During an extraction, if a call-back fucntion is used and try to skip
+ a file, all the extraction process is stopped.
+
+ Corrected in Version 1.3 :
+ - Corrected : Support of static synopsis for method extract() is broken.
+ - Corrected : invalid size of archive content field (0xFF) should be (0xFFFF).
+ - Corrected : When an extract is done with a remove_path parameter, the entry for
+ the directory with exactly the same path is not skipped/filtered.
+ - Corrected : extractByIndex() and deleteByIndex() were not managing index in the
+ right way. For example indexes '1,3-5,11' will only extract files 1 and 11. This
+ is due to a sort of the index resulting table that puts 11 before 3-5 (sort on
+ string and not interger). The sort is temporarilly removed, this means that
+ you must provide a sorted list of index ranges.
+
+ Corrected in Version 1.2 :
+
+ - Nothing.
+
+ Corrected in Version 1.1.2 :
+
+ - Corrected : Winzip is unable to delete or add new files in a PclZip created archives.
+
+ Corrected in Version 1.1.1 :
+
+ - Corrected : When archived file is not compressed (0% compression), the
+ extract method fails.
+
+ Corrected in Version 1.1 :
+
+ - Corrected : Adding a complete tree of folder may result in a bad archive
+ creation.
+
+ Corrected in Version 1.0.1 :
+
+ - Corrected : Error while compressing files greater than PCLZIP_READ_BLOCK_SIZE (default=1024).
+
+
+4 - Known bugs or limitations
+=============================
+
+ Please publish bugs reports in SourceForge :
+ http://sourceforge.net/tracker/?group_id=40254&atid=427564
+
+ In Version 2.x :
+ - PclZip does only support file uncompressed or compressed with deflate (compression method 8)
+ - PclZip does not support password protected zip archive
+ - Some concern were seen when changing mtime of a file while archiving.
+ Seems to be linked to Daylight Saving Time (PclTest_changing_mtime).
+
+ In Version 1.2 :
+
+ - merge() methods does not check for duplicate files or last date of modifications.
+
+ In Version 1.1 :
+
+ - Limitation : Using 'extract' fields in the file header in the zip archive is not supported.
+ - WinZip is unable to delete a single file in a PclZip created archive. It is also unable to
+ add a file in a PclZip created archive. (Corrected in v.1.2)
+
+ In Version 1.0.1 :
+
+ - Adding a complete tree of folder may result in a bad archive
+ creation. (Corrected in V.1.1).
+ - Path given to methods must be in the unix format (/) and not the Windows format (\).
+ Workaround : Use only / directory separators.
+ - PclZip is using temporary files that are sometime the name of the file with a .tmp or .gz
+ added suffix. Files with these names may already exist and may be overwritten.
+ Workaround : none.
+ - PclZip does not check if the zlib extension is present. If it is absent, the zip
+ file is not created and the lib abort without warning.
+ Workaround : enable the zlib extension on the php install
+
+ In Version 1.0 :
+
+ - Error while compressing files greater than PCLZIP_READ_BLOCK_SIZE (default=1024).
+ (Corrected in v.1.0.1)
+ - Limitation : Multi-disk zip archive are not supported.
+
+
+5 - License
+===========
+
+ Since version 1.1.2, PclZip Library is released under GNU/LGPL license.
+ This library is free, so you can use it at no cost.
+
+ HOWEVER, if you release a script, an application, a library or any kind of
+ code using PclZip library (or a part of it), YOU MUST :
+ - Indicate in the documentation (or a readme file), that your work
+ uses PclZip Library, and make a reference to the author and the web site
+ http://www.phpconcept.net
+ - Gives the ability to the final user to update the PclZip libary.
+
+ I will also appreciate that you send me a mail (vincent@phpconcept.net), just to
+ be aware that someone is using PclZip.
+
+ For more information about GNU/LGPL license : http://www.gnu.org
+
+6 - Warning
+=================
+
+ This library and the associated files are non commercial, non professional work.
+ It should not have unexpected results. However if any damage is caused by this software
+ the author can not be responsible.
+ The use of this software is at the risk of the user.
+
+7 - Documentation
+=================
+ PclZip User Manuel is available in English on PhpConcept : http://www.phpconcept.net/pclzip/man/en/index.php
+ A Russian translation was done by Feskov Kuzma : http://php.russofile.ru/ru/authors/unsort/zip/
+
+8 - Author
+==========
+
+ This software was written by Vincent Blavet (vincent@phpconcept.net) on its leasure time.
+
+9 - Contribute
+==============
+ If you want to contribute to the development of PclZip, please contact vincent@phpconcept.net.
+ If you can help in financing PhpConcept hosting service, please go to
+ http://www.phpconcept.net/soutien.php
/v1.1/trunk/scripts/bibliotheque/pclzip-readme.txt
Property changes:
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: v1.1/trunk/scripts/bibliotheque/ScriptCommande.php
===================================================================
--- v1.1/trunk/scripts/bibliotheque/ScriptCommande.php (revision 0)
+++ v1.1/trunk/scripts/bibliotheque/ScriptCommande.php (revision 287)
@@ -0,0 +1,559 @@
+<?php
+// Encodage : UTF-8
+// +-------------------------------------------------------------------------------------------------------------------+
+/**
+* ScriptCommande
+*
+* Description : classe abstraite des scripts
+* Fichier d'origine jelix-scripts par Jouanneau Laurent
+* copyright 2005-2007 Jouanneau laurent
+* link http://www.jelix.org
+*
+//Auteur original :
+* @author Jean-Pascal MILCENT <jpm@tela-botanica.org>
+* @copyright Tela-Botanica 1999-2008
+* @licence GPL v3 & CeCILL v2
+* @version $Id: ScriptCommande.class.php 1948 2009-09-03 14:12:02Z Jean-Pascal MILCENT $
+*/
+// +-------------------------------------------------------------------------------------------------------------------+
+// TODO : supprimer les classe getStaticIni(), getStaticParam() et getStaticNom(). Utiliser une seule méthode qui gère les deux méthodes d'appel.
+/**
+* classe representant une commande
+*/
+abstract class ScriptCommande extends Controleur {
+
+ public $nom;
+ public $parametres;
+
+
+ /**
+ * Paramêtres disponible pour la ligne de commande
+ * le tableau se construit de la forme suivnate :
+ * - clé = nom du paramêtre '-foo'
+ * - value = contient un nouveau tableau composé de cette façaon :
+ * - booléen: true si le paramêtre est obligatoire
+ * - booléen ou var : true si le paramêtre nécessite un valeur à sa suite ou la valeur par défaut
+ * - string: description du contenu du paramêtre
+ * Les paramêtres optionels devraient être déclaré à la fin du tableau.
+ * Le dernier parametre du tableau peut avoir la valeur '...',
+ * il contiendra alors l'ensemble des paramêtres suivant trouvés sur la ligne de commande.
+ * @var array
+ */
+ private $_parametres_autorises = array( '-a' => array(true, true, 'Action à réaliser'),
+ '-v' => array(false, '1', 'Mode verbeux : 1 ou 2'),
+ '-t' => array(false, '', 'Test sur un nombre de ligne...'));
+ /**
+ * Contient les valeurs des paramêtres récupérés de la ligne de commande :
+ * le tableau se construit de la forme suivnate :
+ * - clé = nom du paramêtre '-foo'
+ * - valeur = la valeur récupérée sur la ligne de commande
+ * @var array
+ */
+ private $_parametres;
+
+ private static $_static_nom;
+ private static $_static_parametres;
+ private static $_static_ini;
+ private static $log = '';
+ private static $log_fichier;
+ private static $log_resource;
+ private static $log_fichier_ecraser = false;
+
+ public $syntaxhelp = '';
+ public $help = 'Aucune aide pour cette commande';
+
+ function __construct($commande_nom) {
+ $this->setNom($commande_nom);
+ parent::__construct();
+ }
+
+ public function __destruct() {
+ if (isset(self::$log_resource)) {
+ if (fclose(self::$log_resource)) {
+ self::$log_resource = null;
+ }
+ }
+ }
+
+ public function getNom() {
+ return $this->nom;
+ }
+
+ public static function getStaticNom() {
+ return self::$_static_nom;
+ }
+
+ private function setNom($script_nom) {
+ $this->nom = $script_nom;
+ self::$_static_nom = $script_nom;
+ }
+
+ public static function getLog() {
+ return self::$log;
+ }
+
+ public static function setLog($l) {
+ self::$log .= $l;
+ }
+
+ public function initialiser($plc) {
+ // Récupération des paramêtres autorisés par le script
+ $this->setParamAutorises($this->parametres);
+
+ // Vérification et récupération des paramêtres de la ligne de commande
+ if ($parametres = $this->verifierParametres($plc, $this->getParamAutorises())) {
+ $this->setParam($parametres);
+ }
+
+
+ $tab_fichiers_ini = array( ES_CHEMIN_CONFIG.'bdd.ini', // Paramêtres de la base de données
+ ES_CHEMIN_CONFIG.'commun.ini', // Paramêtres communs aux différents projets
+ $this->getModuleChemin().DS.'configurations'.DS.'config.ini');
+
+ // Chargement des fichiers ini généraux
+ for ($i = 0; $i < 2 ; $i++) {
+ if (!$this->parserFichierIni($tab_fichiers_ini[$i])) {
+ $e = "Le fichier $tab_fichiers_ini[$i] est introuvable\n";
+ trigger_error($e, E_USER_WARNING);
+ }
+ }
+ }
+
+ abstract public function executer();
+
+ protected function getModuleChemin($shouldexist = true) {
+ $chemin = ES_CHEMIN_MODULE.$this->getNom().DS;
+ if (!file_exists($chemin) && $shouldexist) {
+ trigger_error("Erreur: le module '".$this->getNom()."' n'existe pas ($chemin)\n", E_USER_ERROR);
+ }
+ return $chemin;
+ }
+
+ private function verifierParametres($p_ligne, $p_autorise) {
+
+ //print_r($p_ligne);
+ // Récupération des paramêtres
+ foreach ($p_autorise as $p_nom => $p_val) {
+ if (count($p_ligne) == 0) {
+ if ($p_val[0]) {
+ trigger_error("Erreur: paramêtre manquant '".$p_nom."' \n", E_USER_WARNING);
+ }
+ }
+ if ($p_nom == '...') {
+ $parametres['...'] = array();
+ foreach($p_ligne as $arg) {
+ $parametres['...'][] = $arg;
+ }
+ $p_ligne = array();
+ break;
+ } else {
+ if (isset($p_ligne[$p_nom])) {
+ // Attribution de la valeur issue de la ligne de commande
+ $parametres[ltrim($p_nom, '-')] = $p_ligne[$p_nom];
+ unset($p_ligne[$p_nom]);
+ } else {
+ // Attribution de la valeur par défaut
+ if ($p_val[1] !== true) {
+ $parametres[ltrim($p_nom, '-')] = $p_val[1];
+ }
+ }
+ }
+ }
+
+ // Gestion de l'excédant de paramêtres
+ if (count($p_ligne)) {
+ trigger_error("Erreur: trop de paramêtres\n", E_USER_ERROR);
+ }
+
+ return $parametres;
+ }
+
+ protected function setParamAutorises($param) {
+ if (!is_null($param)) {
+ foreach ($param as $c => $v) {
+ if (isset($this->_parametres_autorises[$c])) {
+ trigger_error("Erreur: le module '".$this->getNom()."' ne peut définir le paramêtre '$c' car il existe déjà\n", E_USER_ERROR);
+ } else {
+ $this->_parametres_autorises[$c] = $v;
+ }
+ }
+ }
+ }
+
+ protected function getParamAutorises($param = null) {
+
+ if (!is_null($param)) {
+ if (isset($this->_parametres_autorises['-'.$param])) {
+ return $this->_parametres_autorises['-'.$param];
+ } else if (isset($this->_parametres_autorises[$param])) {
+ return $this->_parametres_autorises[$param];
+ } else {
+ trigger_error("Erreur: le module '".$this->getNom()."' n'a pas défini le paramêtre '$param'\n", E_USER_WARNING);
+ return false;
+ }
+ } else {
+ return $this->_parametres_autorises;
+ }
+ }
+
+ protected function setParam($params = array(), $val = null) {
+
+ if (is_array($params)) {
+ $this->_parametres = $params;
+ self::$_static_parametres = $params;
+ } else if (!is_array($params) && !is_null($val)) {
+ $this->_parametres[$params] = $val;
+ self::$_static_parametres[$params] = $val;
+ } else {
+ return false;
+ }
+ }
+
+ protected function getParam($param = null) {
+
+ if (!is_null($param)) {
+ if (isset($this->_parametres['-'.$param])) {
+ return $this->_parametres['-'.$param];
+ } else if (isset($this->_parametres[$param])) {
+ return $this->_parametres[$param];
+ } else {
+ trigger_error("Erreur: la ligne de commande ne contenait pas le paramêtre '$param'\n", E_USER_WARNING);
+ return false;
+ }
+ } else {
+ return $this->_parametres;
+ }
+ }
+
+ protected static function getStaticParam($param = null) {
+
+ if (!is_null($param)) {
+ if (isset(self::$_static_parametres['-'.$param])) {
+ return self::$_static_parametres['-'.$param];
+ } else if (isset(self::$_static_parametres[$param])) {
+ return self::$_static_parametres[$param];
+ } else {
+ trigger_error("Erreur: la ligne de commande ne contenait pas le paramêtre '$param'\n", E_USER_WARNING);
+ return false;
+ }
+ } else {
+ return self::$_static_parametres;
+ }
+ }
+
+ protected function getIni($nom) {
+ if (isset($this->_ini[$nom])) {
+ return $this->_ini[$nom];
+ } else {
+ return false;
+ }
+ }
+
+ protected static function getStaticIni($nom) {
+ if (isset(self::$_static_ini[$nom])) {
+ return self::$_static_ini[$nom];
+ } else {
+ return false;
+ }
+ }
+
+ protected function parserFichierIni($fichier_ini) {
+ if (file_exists($fichier_ini)) {
+ $aso_ini = parse_ini_file($fichier_ini);
+ foreach ($aso_ini as $cle => $val) {
+ if (preg_match('/^php:(.+)$/', $val, $correspondances)) {
+ eval('$this->$cle = '.$correspondances[1].';');
+ eval('$this->_ini[$cle] = '.$correspondances[1].';');
+ } else if (preg_match('/^php-static:(.+)$/', $val, $correspondances)) {
+ eval('self::$'.$cle.' = '.$correspondances[1].';');
+ eval('$this->_ini[$cle] = '.$correspondances[1].';');
+ } else {
+ // Ancienne forme : compatibilité avec les anciens scripts...
+ $this->$cle = $val;
+ // Nouvelle forme : utilisation de la méthode getInit().
+ $this->_ini[$cle] = $val;
+ }
+ }
+ self::$_static_ini = $this->_ini;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ // Log Resource
+ /**
+ * Lit la valeur de l'attribut Log Resource.
+ * Utilise le motif de conception (= design pattern) Singleton.
+ *
+ * @access public
+ * @param string le préfixe du nom de fichier à créer.
+ * @return string retourne le Log Resource.
+ */
+ public static function getLogResource() {
+ if (!isset(self::$log_resource)) {
+ if (file_exists(self::getLogFichier()) && !self::$log_fichier_ecraser) {
+ // Ouvre en écriture seule ; place le pointeur de fichier à la fin du fichier. Si le fichier
+ // n'existe pas, on tente de le créer.
+ self::$log_resource = fopen(self::getLogFichier(), 'a');
+ } else {
+ //Ouvre en écriture seule ; place le pointeur de fichier au début du fichier et réduit la taille
+ // du fichier à 0. Si le fichier n'existe pas, on tente de le créer.
+ self::$log_resource = fopen(self::getLogFichier(), 'w');
+ $entete_utf8 = "\xEF\xBB\xBF";
+ if (!fwrite(self::$log_resource, $entete_utf8)) {
+ echo "Erreur écriture dans le fichier de log lors de l'ajout de l'entête UTF8.\n";
+ }
+ }
+ }
+ return self::$log_resource;
+ }
+
+ // Log Fichier
+ /**
+ * Lit la valeur de l'attribut Log Fichier.
+ * Utilise le motif de conception (= design pattern) Singleton.
+ *
+ * @access public
+ * @return string retourne le nom du fichier de log.
+ */
+ public static function getLogFichier() {
+ if (!isset(self::$log_fichier)) {
+ if (self::getStaticIni('projet_nom') && self::getStaticIni('version') && self::getStaticIni('sous_version')) {
+ $fichier = self::getStaticIni('projet_nom').'_'.
+ self::getStaticNom().'_'.
+ self::getStaticParam('a').'_'.
+ 'v'.self::getStaticIni('version').'_'.self::getStaticIni('sous_version');
+ } else {
+ $fichier = self::getStaticNom().'_'.self::getStaticParam('a');
+ }
+
+ if (!self::$log_fichier_ecraser) {
+ $fichier .= '_'.date('Y-m-j_H:i:s', time());
+ }
+ $fichier .= '.log';
+ // Ajout du chemin vers le fichier de log et stockage dans variable static
+ self::$log_fichier = self::getStaticIni('log_chemin').$fichier;
+ }
+ return self::$log_fichier;
+ }
+
+ /**
+ * Retourne un message d'avertissement formaté.
+ *
+ * @param string le message d'erreur avec des %s.
+ * @param array le tableau des paramêtres à insérer dans le message d'erreur.
+ * @param int le niveau de verbosité à dépasser pour afficher les messages.
+ * @return string le message d'erreur formaté.
+ */
+ private function traiterMessage($message, $tab_arguments = array(), $niveau = 0) {
+ // Nous ajoutons dans le texte les infos provenant de la BDD (déjà encodées en UTF-8).
+ $texte = vsprintf($message, $tab_arguments);
+ if ($this->getParam('v') >= $niveau) {
+ $prefixe = '';
+ if ($this->getIni('projet_nom') && $this->getIni('version') && $this->getIni('sous_version')) {
+ $prefixe = $this->getIni('projet_nom').'v'.$this->getIni('version').'.'.$this->getIni('sous_version').'. ';
+ } else {
+ $prefixe = date('Y-m-j_H:i:s', time()).' - '.Script::getCode($niveau).' : ';
+ }
+ $log = $prefixe.$texte."\n";
+ echo $log;
+ self::setLog($log);
+
+ if (!fwrite($this->getLogResource(), $log)) {
+ trigger_error('Erreur écriture dans le fichier de log.'."\n", E_USER_WARNING);
+ }
+ }
+ return "\t".$texte."\n";
+ }
+
+ /**
+ * Retourne un message d'erreur après avoir écrit le message danns le fichier de log.
+ * Si le mode verbeux est inactivé, écrit le message dans le fichier de log.
+ * Si le mode verbeux de niveau 1 ou plus est activé, écrit le message dans le fichier de log et dans la console.
+ *
+ * @param string le message d'erreur avec des %s.
+ * @param array le tableau des paramêtres à insérer dans le message d'erreur.
+ * @return string le message d'erreur formaté.
+ */
+ protected function traiterErreur($message, $tab_arguments = array()) {
+ $niveau = Script::ERREUR;
+ return $this->traiterMessage($message, $tab_arguments, $niveau);
+ }
+
+ /**
+ * Retourne un message d'avertissement formaté.
+ * Si le mode verbeux de niveau 1 est activé, écrit le message dans le fichier de log.
+ * Si le mode verbeux de niveau 2 est activé, écrit le message dans le fichier de log et dans la console.
+ *
+ * @param string le message d'erreur avec des %s.
+ * @param array le tableau des paramêtres à insérer dans le message d'erreur.
+ * @return string le message d'erreur formaté.
+ */
+ protected function traiterAttention($message, $tab_arguments = array()) {
+ $niveau = Script::AVERTISSEMENT;
+ return $this->traiterMessage($message, $tab_arguments, $niveau);
+ }
+
+ /**
+ * Retourne un message d'information formaté.
+ * Si le mode verbeux de niveau 2 est activé, écrit le message dans le fichier de log.
+ * Si le mode verbeux de niveau 3 est activé, écrit le message dans le fichier de log et dans la console.
+ *
+ * @param string le message d'information avec des %s.
+ * @param array le tableau des paramêtres à insérer dans le message d'erreur.
+ * @param int le niveau de verbosité à dépasser pour afficher les messages.
+ * @return string le message d'erreur formaté.
+ */
+ protected function afficher($message, $tab_arguments = array(), $niveau = null) {
+ if (is_null($niveau)) {
+ $niveau = Script::INFO;
+ }
+ $msg = $this->traiterMessage($message, $tab_arguments, $niveau);
+ return $msg ;
+ }
+
+ /**
+ * Méthode prenant en paramètre un chemin de fichier squelette et un tableau associatif de données,
+ * en extrait les variables, charge le squelette et retourne le résultat des deux combinés.
+ *
+ * @param String $fichier le chemin du fichier du squelette
+ * @param Array $donnees un tableau associatif contenant les variables a injecter dans le squelette.
+ *
+ * @return boolean false si le squelette n'existe pas, sinon la chaine résultat.
+ */
+ public static function traiterSquelettePhp($fichier, Array $donnees = array()) {
+ $sortie = false;
+ if (file_exists($fichier)) {
+ // Extraction des variables du tableau de données
+ extract($donnees);
+ // Démarage de la bufferisation de sortie
+ ob_start();
+ // Si les tags courts sont activés
+ if ((bool) @ini_get('short_open_tag') === true) {
+ // Simple inclusion du squelette
+ include $fichier;
+ } else {
+ // Sinon, remplacement des tags courts par la syntaxe classique avec echo
+ $html_et_code_php = self::traiterTagsCourts($fichier);
+ // Pour évaluer du php mélangé dans du html il est nécessaire de fermer la balise php ouverte par eval
+ $html_et_code_php = '?>'.$html_et_code_php;
+ // Interprétation du html et du php dans le buffer
+ echo eval($html_et_code_php);
+ }
+ // Récupèration du contenu du buffer
+ $sortie = ob_get_contents();
+ // Suppression du buffer
+ @ob_end_clean();
+ } else {
+ $msg = "Le fichier du squelette '$fichier' n'existe pas.";
+ trigger_error($msg, E_USER_WARNING);
+ }
+ // Retourne le contenu
+ return $sortie;
+ }
+
+ /**
+ * Fonction chargeant le contenu du squelette et remplaçant les tags court php (<?= ...) par un tag long avec echo.
+ *
+ * @param String $chemin_squelette le chemin du fichier du squelette
+ *
+ * @return string le contenu du fichier du squelette php avec les tags courts remplacés.
+ */
+ private static function traiterTagsCourts($chemin_squelette) {
+ $contenu = file_get_contents($chemin_squelette);
+ // Remplacement de tags courts par un tag long avec echo
+ $contenu = str_replace('<?=', '<?php echo ', $contenu);
+ // Ajout systématique d'un point virgule avant la fermeture php
+ $contenu = preg_replace("/;*\s*\?>/", "; ?>", $contenu);
+ return $contenu;
+ }
+
+ /**
+ * Créer et stocke du contenu dans un fichier.
+ *
+ * @param string le chemin et le nom du fichier.
+ * @param string le contenu à stocker dans le fichier.
+ * @param boolean true pour compresser (gz) le fichier. Par défaut vaut false.
+ * @return string le message d'erreur formaté.
+ */
+ protected function creerFichier($fichier, $contenu, $compression = false) {
+ $e = null;
+ if ($compression) {
+ // Ajout de l'extension gz
+ if (substr($fichier, -3) != '.gz') {
+ $fichier = $fichier.'.gz';
+ }
+ // Début de l'écriture du fichier compressé
+ if ($resource = gzopen($fichier, 'w9')) {
+ if (!gzwrite($resource, $contenu)) {
+ $e = "Le contenu texte n'a pas pu être écrit dans le fichier compressé '$fichier'.";
+ }
+ if (!gzclose($resource)) {
+ $e = "Le fichier compressé '$fichier' n'a pas pu être fermé.";
+ }
+ } else {
+ $e = "Le fichier compressé '$fichier' n'a pas pu être ouvert.";
+ }
+ } else {
+ if ($resource = fopen($fichier, 'w')) {
+ if (!fwrite($resource, $contenu)) {
+ $e = "Le contenu texte n'a pas pu être écrit dans le fichier '$fichier'.";
+ }
+ if (!fclose($resource)) {
+ $e = "Le fichier '$fichier' n'a pas pu être fermé.";
+ }
+ } else {
+ $e = "Le fichier '$fichier' n'a pas pu être ouvert.";
+ }
+ }
+ if (is_null($e)) {
+ return true;
+ } else {
+ trigger_error($e, E_USER_WARNING);
+ return false;
+ }
+ }
+
+ /**
+ * Méthode permettant d'encoder de l'iso-8859-15 vers utf-8 un tableau de variables.
+ *
+ * @param mixed la chaine ou le tableau à encoder en utf-8 depuis l'iso-8859-15.
+ * @param string l'encodage d'origine si ce n'est pas ISO-8859-15.
+ * @return mixed la chaine ou le tableau encodé en utf-8.
+ * @access protected
+ */
+ protected function encoderUtf8( &$val, $encodage = 'ISO-8859-15') {
+ //echo print_r($val, true)."\n";
+ if (is_array($val)) {
+ foreach ($val as $c => $v) {
+ $val[$c] = $this->encoderUtf8($v);
+ }
+ } else {
+ // Nous vérifions si nous avons un bon encodage UTF-8
+ if (!is_numeric($val) && !empty($val) && !$this->detecterUtf8($val)) {
+ // Les nombres, les valeurs vides et ce qui est déjà en UTF-8 ne sont pas encodés.
+ $val = mb_convert_encoding($val, 'UTF-8', $encodage);
+ }
+ }
+ return $val;
+ }
+
+ /**
+ * Méthode permettant de détecter réellement l'encodage utf8.
+ * mb_detect_encoding plante si la chaine de caractère se termine par un caractère accentué.
+ * Provient de PHPDIG.
+ *
+ * @param string la chaine à vérifier.
+ * @return bool true si c'est de l'utf8, sinon false.
+ * @access private
+ */
+ private function detecterUtf8($str) {
+ if ($str === mb_convert_encoding(mb_convert_encoding($str, 'UTF-32', 'UTF-8'), 'UTF-8', 'UTF-32')) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
+?>
Index: v1.1/trunk/scripts/scripts.conf.php
===================================================================
--- v1.1/trunk/scripts/scripts.conf.php (revision 0)
+++ v1.1/trunk/scripts/scripts.conf.php (revision 287)
@@ -0,0 +1,35 @@
+<?php
+// Encodage : UTF-8
+// +-------------------------------------------------------------------------------------------------------------------+
+/**
+* Script configuration
+*
+* Description : permet de configurer le lancement des scripts. Le contenu de ce fichier ne devrait pas nécessité de
+* modification dans la plupart des installations...
+*
+//Auteur original :
+* @author Jean-Pascal MILCENT <jpm@tela-botanica.org>
+* @copyright Tela-Botanica 1999-2008
+* @licence GPL v3 & CeCILL v2
+* @version $Id$
+*/
+// +-------------------------------------------------------------------------------------------------------------------+
+// Gestion de la mémoire maximum allouée aux scripts
+ini_set('memory_limit', 2147483648);//128Mo = 134217728 ; 256Mo = 268435456 ; 512Mo = 536870912 ; 1Go = 1073741824; 2Go = 2147483648
+// Gestion des erreurs
+error_reporting(E_ALL);
+
+// Gestion des chemins
+define('DS', DIRECTORY_SEPARATOR);
+define('ES_CHEMIN_APPLI', dirname(__FILE__).DS);
+define('ES_CHEMIN_CONFIG', ES_CHEMIN_APPLI.'configurations'.DS);
+define('ES_CHEMIN_MODULE', ES_CHEMIN_APPLI.'modules'.DS);
+define('ES_CHEMIN_PEAR', '/opt/lampp/lib/php/');
+
+// Chargement de base pour l'autoload
+$GLOBALS['chemins_autoload'] = array(
+ dirname(realpath(__FILE__)).DS.'bibliotheque'.DS,
+ dirname(realpath(__FILE__)).DS.'..'.DS.'bibliotheque'.DS.'dao'.DS,
+ dirname(realpath(__FILE__)).DS.'..'.DS.'bibliotheque'.DS.'utilitaires'.DS
+ );
+?>
\ No newline at end of file
Index: v1.1/trunk/scripts
===================================================================
--- v1.1/trunk/scripts (revision 0)
+++ v1.1/trunk/scripts (revision 287)
/v1.1/trunk/scripts
Property changes:
Added: svn:ignore
## -0,0 +1,2 ##
+retenu_absent.csv
+superieur_absent.csv
Index: v1.1/trunk/A_LIRE.txt
===================================================================
--- v1.1/trunk/A_LIRE.txt (revision 0)
+++ v1.1/trunk/A_LIRE.txt (revision 287)
@@ -0,0 +1,4 @@
+Cette application nécessite le framework de Tela Botanica pour fonctionner.
+Il est disponible ici : http://svn.tela-botanica.net/websvn/listing.php?repname=Applications.framework
+
+L'ensemble des fichiers de l'application sont dans l'encodage UTF-8.
\ No newline at end of file
/v1.1/trunk/A_LIRE.txt
Property changes:
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: v1.1/trunk/configurations/mtpr_v2_1.pdf
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: v1.1/trunk/configurations/mtpr_v2_1.pdf
===================================================================
--- v1.1/trunk/configurations/mtpr_v2_1.pdf (revision 0)
+++ v1.1/trunk/configurations/mtpr_v2_1.pdf (revision 287)
/v1.1/trunk/configurations/mtpr_v2_1.pdf
Property changes:
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: v1.1/trunk/configurations/mtpr_v4_1.pdf
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: v1.1/trunk/configurations/mtpr_v4_1.pdf
===================================================================
--- v1.1/trunk/configurations/mtpr_v4_1.pdf (revision 0)
+++ v1.1/trunk/configurations/mtpr_v4_1.pdf (revision 287)
/v1.1/trunk/configurations/mtpr_v4_1.pdf
Property changes:
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: v1.1/trunk/configurations/mtpr_v4_2.pdf
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: v1.1/trunk/configurations/mtpr_v4_2.pdf
===================================================================
--- v1.1/trunk/configurations/mtpr_v4_2.pdf (revision 0)
+++ v1.1/trunk/configurations/mtpr_v4_2.pdf (revision 287)
/v1.1/trunk/configurations/mtpr_v4_2.pdf
Property changes:
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: v1.1/trunk/configurations/mtpr_v4_3.pdf
===================================================================
--- v1.1/trunk/configurations/mtpr_v4_3.pdf (revision 0)
+++ v1.1/trunk/configurations/mtpr_v4_3.pdf (revision 287)
@@ -0,0 +1,7673 @@
+%PDF-1.3
+1 0 obj
+<<
+/Kids [ 4 0 R 5 0 R 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R 15 0 R 16 0 R 17 0 R 18 0 R 19 0 R 20 0 R 21 0 R 22 0 R 23 0 R 24 0 R 25 0 R 26 0 R 27 0 R 28 0 R 29 0 R 30 0 R 31 0 R 32 0 R 33 0 R 34 0 R 35 0 R 36 0 R ]
+/Type /Pages
+/Count 33
+>>
+endobj
+2 0 obj
+<<
+/Producer (Python PDF Library \055 http\072\057\057pybrary\056net\057pyPdf\057)
+>>
+endobj
+3 0 obj
+<<
+/Type /Catalog
+/Pages 1 0 R
+>>
+endobj
+4 0 obj
+<<
+/Parent 1 0 R
+/Contents 37 0 R
+/Resources 38 0 R
+/Group <<
+/CS /DeviceRGB
+/S /Transparency
+/I true
+>>
+/MediaBox [ 0 0 595 842 ]
+/Annots [ 63 0 R ]
+/Type /Page
+>>
+endobj
+5 0 obj
+<<
+/Parent 1 0 R
+/Contents 64 0 R
+/Resources 38 0 R
+/Group <<
+/CS /DeviceRGB
+/S /Transparency
+/I true
+>>
+/MediaBox [ 0 0 595 842 ]
+/Annots [ 65 0 R 66 0 R 67 0 R ]
+/Type /Page
+>>
+endobj
+6 0 obj
+<<
+/Parent 1 0 R
+/Contents 68 0 R
+/Resources 38 0 R
+/Group <<
+/CS /DeviceRGB
+/S /Transparency
+/I true
+>>
+/MediaBox [ 0 0 595 842 ]
+/Annots [ 69 0 R 70 0 R 71 0 R 72 0 R 73 0 R 74 0 R ]
+/Type /Page
+>>
+endobj
+7 0 obj
+<<
+/Parent 1 0 R
+/Contents 75 0 R
+/Resources 38 0 R
+/Group <<
+/CS /DeviceRGB
+/S /Transparency
+/I true
+>>
+/MediaBox [ 0 0 595 842 ]
+/Type /Page
+>>
+endobj
+8 0 obj
+<<
+/Parent 1 0 R
+/Contents 76 0 R
+/Resources 38 0 R
+/Group <<
+/CS /DeviceRGB
+/S /Transparency
+/I true
+>>
+/MediaBox [ 0 0 595 842 ]
+/Type /Page
+>>
+endobj
+9 0 obj
+<<
+/Parent 1 0 R
+/Contents 77 0 R
+/Resources 38 0 R
+/Group <<
+/CS /DeviceRGB
+/S /Transparency
+/I true
+>>
+/MediaBox [ 0 0 595 842 ]
+/Annots [ 78 0 R 79 0 R 80 0 R ]
+/Type /Page
+>>
+endobj
+10 0 obj
+<<
+/Parent 1 0 R
+/Contents 81 0 R
+/Resources 38 0 R
+/Group <<
+/CS /DeviceRGB
+/S /Transparency
+/I true
+>>
+/MediaBox [ 0 0 595 842 ]
+/Annots [ 82 0 R 83 0 R 84 0 R ]
+/Type /Page
+>>
+endobj
+11 0 obj
+<<
+/Parent 1 0 R
+/Contents 85 0 R
+/Resources 38 0 R
+/Group <<
+/CS /DeviceRGB
+/S /Transparency
+/I true
+>>
+/MediaBox [ 0 0 595 842 ]
+/Type /Page
+>>
+endobj
+12 0 obj
+<<
+/Parent 1 0 R
+/Contents 86 0 R
+/Resources 38 0 R
+/Group <<
+/CS /DeviceRGB
+/S /Transparency
+/I true
+>>
+/MediaBox [ 0 0 595 842 ]
+/Annots [ 87 0 R ]
+/Type /Page
+>>
+endobj
+13 0 obj
+<<
+/Parent 1 0 R
+/Contents 88 0 R
+/Resources 38 0 R
+/Group <<
+/CS /DeviceRGB
+/S /Transparency
+/I true
+>>
+/MediaBox [ 0 0 595 842 ]
+/Annots [ 89 0 R ]
+/Type /Page
+>>
+endobj
+14 0 obj
+<<
+/Parent 1 0 R
+/Contents 90 0 R
+/Resources 38 0 R
+/Group <<
+/CS /DeviceRGB
+/S /Transparency
+/I true
+>>
+/MediaBox [ 0 0 595 842 ]
+/Annots [ 91 0 R 92 0 R 93 0 R 94 0 R ]
+/Type /Page
+>>
+endobj
+15 0 obj
+<<
+/Parent 1 0 R
+/Contents 95 0 R
+/Resources 38 0 R
+/Group <<
+/CS /DeviceRGB
+/S /Transparency
+/I true
+>>
+/MediaBox [ 0 0 595 842 ]
+/Type /Page
+>>
+endobj
+16 0 obj
+<<
+/Parent 1 0 R
+/Contents 96 0 R
+/Resources 38 0 R
+/Group <<
+/CS /DeviceRGB
+/S /Transparency
+/I true
+>>
+/MediaBox [ 0 0 595 842 ]
+/Annots [ 97 0 R 98 0 R ]
+/Type /Page
+>>
+endobj
+17 0 obj
+<<
+/Parent 1 0 R
+/Contents 99 0 R
+/Resources 38 0 R
+/Group <<
+/CS /DeviceRGB
+/S /Transparency
+/I true
+>>
+/MediaBox [ 0 0 595 842 ]
+/Annots [ 100 0 R ]
+/Type /Page
+>>
+endobj
+18 0 obj
+<<
+/Parent 1 0 R
+/Contents 101 0 R
+/Resources 38 0 R
+/Group <<
+/CS /DeviceRGB
+/S /Transparency
+/I true
+>>
+/MediaBox [ 0 0 595 842 ]
+/Annots [ 102 0 R 103 0 R 104 0 R 105 0 R 106 0 R ]
+/Type /Page
+>>
+endobj
+19 0 obj
+<<
+/Parent 1 0 R
+/Contents 107 0 R
+/Type /Page
+/Resources <<
+/XObject <<
+/Xf1 108 0 R
+>>
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>>
+/MediaBox [ 0 0 612 792 ]
+>>
+endobj
+20 0 obj
+<<
+/Parent 1 0 R
+/Contents 114 0 R
+/Type /Page
+/Resources <<
+/XObject <<
+/Xf2 115 0 R
+>>
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>>
+/MediaBox [ 0 0 612 792 ]
+>>
+endobj
+21 0 obj
+<<
+/Parent 1 0 R
+/Contents 116 0 R
+/Type /Page
+/Resources <<
+/XObject <<
+/Xf3 117 0 R
+>>
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>>
+/MediaBox [ 0 0 612 792 ]
+>>
+endobj
+22 0 obj
+<<
+/Parent 1 0 R
+/Contents 118 0 R
+/Type /Page
+/Resources <<
+/XObject <<
+/Xf4 119 0 R
+>>
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>>
+/MediaBox [ 0 0 612 792 ]
+>>
+endobj
+23 0 obj
+<<
+/Parent 1 0 R
+/Contents 120 0 R
+/Type /Page
+/Resources <<
+/XObject <<
+/Xf5 121 0 R
+>>
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>>
+/MediaBox [ 0 0 612 792 ]
+>>
+endobj
+24 0 obj
+<<
+/Parent 1 0 R
+/Contents 122 0 R
+/Type /Page
+/Resources <<
+/XObject <<
+/Xf6 123 0 R
+>>
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>>
+/MediaBox [ 0 0 612 792 ]
+>>
+endobj
+25 0 obj
+<<
+/Parent 1 0 R
+/Contents 124 0 R
+/Type /Page
+/Resources <<
+/XObject <<
+/Xf7 125 0 R
+>>
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>>
+/MediaBox [ 0 0 612 792 ]
+>>
+endobj
+26 0 obj
+<<
+/Parent 1 0 R
+/Contents 126 0 R
+/Type /Page
+/Resources <<
+/XObject <<
+/Xf8 127 0 R
+>>
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>>
+/MediaBox [ 0 0 612 792 ]
+>>
+endobj
+27 0 obj
+<<
+/Parent 1 0 R
+/Contents 128 0 R
+/Type /Page
+/Resources <<
+/XObject <<
+/Xf9 129 0 R
+>>
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>>
+/MediaBox [ 0 0 612 792 ]
+>>
+endobj
+28 0 obj
+<<
+/Parent 1 0 R
+/Contents 130 0 R
+/Type /Page
+/Resources <<
+/XObject <<
+/Xf10 131 0 R
+>>
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>>
+/MediaBox [ 0 0 612 792 ]
+>>
+endobj
+29 0 obj
+<<
+/Parent 1 0 R
+/Contents 132 0 R
+/Type /Page
+/Resources <<
+/XObject <<
+/Xf11 133 0 R
+>>
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>>
+/MediaBox [ 0 0 612 792 ]
+>>
+endobj
+30 0 obj
+<<
+/Parent 1 0 R
+/Contents 134 0 R
+/Type /Page
+/Resources <<
+/XObject <<
+/Xf12 135 0 R
+>>
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>>
+/MediaBox [ 0 0 612 792 ]
+>>
+endobj
+31 0 obj
+<<
+/Parent 1 0 R
+/Contents 136 0 R
+/Type /Page
+/Resources <<
+/XObject <<
+/Xf13 137 0 R
+>>
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>>
+/MediaBox [ 0 0 612 792 ]
+>>
+endobj
+32 0 obj
+<<
+/Parent 1 0 R
+/Contents 138 0 R
+/Type /Page
+/Resources <<
+/XObject <<
+/Xf14 139 0 R
+>>
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>>
+/MediaBox [ 0 0 612 792 ]
+>>
+endobj
+33 0 obj
+<<
+/Parent 1 0 R
+/Contents 140 0 R
+/Type /Page
+/Resources <<
+/XObject <<
+/Xf15 141 0 R
+>>
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>>
+/MediaBox [ 0 0 612 792 ]
+>>
+endobj
+34 0 obj
+<<
+/Parent 1 0 R
+/Contents 142 0 R
+/Type /Page
+/Resources <<
+/XObject <<
+/Xf16 143 0 R
+>>
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>>
+/MediaBox [ 0 0 612 792 ]
+>>
+endobj
+35 0 obj
+<<
+/Parent 1 0 R
+/Contents 144 0 R
+/Type /Page
+/Resources <<
+/XObject <<
+/Xf17 145 0 R
+>>
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>>
+/MediaBox [ 0 0 612 792 ]
+>>
+endobj
+36 0 obj
+<<
+/Parent 1 0 R
+/Contents 146 0 R
+/Type /Page
+/Resources <<
+/XObject <<
+/Xf18 147 0 R
+>>
+/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>>
+/MediaBox [ 0 0 612 792 ]
+>>
+endobj
+37 0 obj
+<<
+/Length 1017
+/Filter /FlateDecode
+>>
+stream
+xœ•WKÛ8 ¾ûWè\À.I½, 0`'Îb÷6»z(zê¾°è´h/ýûKR~%‘ƒ ðHE~üHQ
+4h~Vß +ðŸO¾q¦ud~üe>¼3_'9˜ÿTÃ¥BM4>¶ +™ËŸæý +¹üýñØᨣخæ¯Ó¯
+¼|BWãü¢Û®
+Iæ}W»iHº¤Zƒ?Ài•Žª{î>]~«ÆKõr ³…Æ¹Ù#ˆD$´ t##êyÍcàÕڊŒWãfÂë„-ïD/ Póò>ãlۄeA|{°çˆj + “BpS¸*Ə0À O‹b»ë ‘ãu›¨iNŽì´lƒ W”³=¹f,xË˾ q&œƒj Ø; å
+Ï,&¬0‚Á`›”‘Áµ\4ÓV*sÅX)–Z«%(vΊARA/@†+õ£¬æáIÅcgçýgYÓ¹îàüë§â}Ù6îE`mb>K1 òväPêiw¿·-s  2ij¯lÌÌЃê"®p6çWû +¶|üÔ¬Ô9¬ÆçB—ú]|ú.0¨€&^£žV¥1¬YXÁöz2„?íÛCi•ë禱¢l_#½‡ MN ÚÕxåæQ5ý’Ü<¼Š’®J)Î1ë£Í¡¥ÉCxTÇ.
+ÓM{C~ £ÏË0# ©8B’æ–ÇiªT²¬MÊkþræ¢HEà÷\Ná.ñÀ]`©
+»‚ô¤{ùò_qG”îRrIq-¡q"cÏmÛ¾ÍiÂKNQ³F» ³PfhߗÇw[1Àì«ßõ…¾ˆò/,ÓÿQ¸÷ã¢TDùÀ—%Ö-ÆÕïÄÕxÀ–ã ì1?Êýki‚Ó±ËG­Ô×nÛXš.˜Í±•ÅÜiè('ç´½ â}šï®ö8+ŽÚ߯¨±ñ¸Æóùµ’‡ÂÏ«¨ÿ¥SWÏ+7-Çj:ùbþÈMj·ÿ3Y|.Ѹ–ÜP +ËiÛ3—€s³Ò³t q{çÕqé¶áªÁ1Iu˜öÛ.¿¥Ï‹BnHùu´Ú¡ÄNïî`:/-ñ¦=䎂ÜcwäI‡ÝTöCÖÊ.˜hŸ7á€o«{Þ`–«€aé&OSâb(Q2]f&°çí%yøŸÎñ¨À>m€Ç嘬¾Ì©_oR> +az$h+˜.ÎMMí¹’C¼Â#ÿÒa¯•wŽ³­“/f™™àõHþ­†wՋùnøg’7Zy×蘇ëo¦,”®¢ un9ï}uæôͼ¨©½Ÿ]œ©;wî`È&,$Ìm‹mølCœE6,ª­Ü*69öe½0ûú6£÷¨
+endstream
+endobj
+38 0 obj
+<<
+/XObject <<
+/Im6 39 0 R
+/Im5 40 0 R
+/Im4 41 0 R
+>>
+/Font 42 0 R
+/ProcSet [ /PDF /Text /ImageC /ImageI /ImageB ]
+>>
+endobj
+39 0 obj
+<<
+/Filter /DCTDecode
+/Length 5012
+/ColorSpace /DeviceRGB
+/Type /XObject
+/BitsPerComponent 8
+/Height 130
+/Width 118
+/Subtype /Image
+>>
+stream
+ÿØÿàJFIFÿÛC
+
+
+ + +  ÿÛC   + +ÿ‚v"ÿÄÿÄÿÚ è( O¡îiö¸/¡+õð|ÙI=RYŒ™äŽYÜ ¾¥„G“¡³Œl¦ Õ + ä Á1£@,ucr³ç»N~¦¶]†šob`ˆš€ÌZkã×6ôÔdÆ` CÏÐ3ç:*ÄãcMËP7“¢­Žö__reÉ°}¿… Œäv”ÿn÷¥×§œw¨Úþçü~G7ŽW˜4Ñý6¢½77­x„ +` Ԁ_ºÕ(+öÇærx·7&ÂÇén"ÉÖÝ´€s@WX™/S¿u#¤mRÏZû5›yc#¶=}~ï.én—õ‡ÛÅgah­žœŽ±5Ýfeëme³çôx‚åˆ €ÀÿÄ+ 6012@!#ÿÚ6NÈȽpæ½pÞ½t֔ù”ׯ×¹
+wýÀ_^á+¯p–Ó²BlÒd„Üžã)¯q”л³Þ®r¬“=a<’6 ‡¸êf ™´xn€ûL}÷V‰Ò!¨ G’Kˆ^ åKx^ÖWt§|L{Îy ¨Žj' ûDo4mۅb ÍV7[êglU Ç+€‚œ]O©nœ7௉‹¨OëõL™®‘íÁžûnDF`õq"¦Üé_HàÚ© ½ÇŠÅËwëÑ­•äUɳÉ)’sÓIÐQ‘ÈÙY­Ñü;jW×íšÜÖ3¸Ç-Ý$«Êë¼¼c¹U sA;,ź?‡SØè׫nþ;º?=8ðÏT&g¨f¶WŒ»³Ó·Ý/·N iÕÎZ)R’eY#²½;wñÍÒûu#•®Uåzvïã™P
+¦›Ü²Ø0avmÍ·£j¹v֓#¥‚ +¾ëª²UVA·£¬ºM¹£Ónh:ö֜Ñ×ÛÁ÷4¸Ôv42"÷bLk䪒“Lf2Œö8ԃÀŸ.ç_ë ½TN…K“{‡\{")8¯†ëf9hÛɲ§±hÒ,sŽ{&"ð‹ÙÂ'gu8î&•¨íqÊ"q¤DMpŸÿÄ-!1AQ‘Ñ 034q¡"ñÿÚ?”¼;Q?¯âºN.ä:+äâîC¢º@H$òhþ'WÈ7žC¢íÄò
+=m̯ª’Z {ßsÆGÆccŽdxkêål†&ê ˜œakµË¿T\vÅ)#ZCÆeR+ËZ8„÷¢Åp +‡¸všz'T0½ªHݬvÝ4¾ƒ>!¼ÓÇfY©$t®/vÝ4¾ƒ>áŠI¿&æ„]`]ޟ6‹6¡MO{šY±
+zsö´iiÚr³vhÁNƒíG©€'ÿÄ@PÿÚ? XÿÄC  !1"2AQq #6BRa“¡Á0s‚‘”@r±35CSbƒ„²ÑâðÿÚ?ðøEĂlÙ¥åìã_Ôõ+^oŸÔ­y¾ß¨Z×`0ÿPµæûüqY?a6W”-ëÍù~0¯7æøË^oÍñ…s¶ «ß:ÕÆÀ˜ŽÑ2כøŠµæþ#â-'Â> ó[væþúÂH¸^[lÙà$kq`u |½„·*®–?õ­ZqÌked½ï×JYæŒOlÅ×Tí#ßò¨¬åP&ŒÈxeáíé|©¼¦!ÎHòêœ}+üê\VumàË®šÛÛQ0†%”±ºô{í§šQ‰Ì‘Òó[ó®kLaÞ!Î2jºðùTQÏ’bF$¸-k‹^Æ·;™£R+"e· +k*r„°P„ìêù^ŸÉâDfn ¿úôyvóœÿKn\>Ă.;?|›êYb\Ä/ɍò¸vrL‡¤‚·¸g½ºJx¯Úç\_%*ݤgÿ‚\F"è؋Z.ÁíûV‡–&â­^G
+¨üDœX} ›žßݎ#}Šƒ‹Š<| fkó¯ÙÝH›B3·<}úlneä¬zgª™S#Åu>k¨÷VLy8˜ éúkM.Ä,Üɶþ*8‰æXá㚕ÐæVU¯¯e$;<«,'ʨsÙPgÁ}í›ý´¸þP£ }#ǺԮŒ[PGƒgþ?§… 21… *—ÐâD’Hα ¨ èŠÜe\F÷ÈþuIÞ´2¹þÌÚ݂¯×àæê¢Låðy¹ÐÒKÅÁ³ÿÓdz©Sí?ùÍ[;ñý<hbxåh/å BöÉ$j²åʳ[U¥ÊÙðòß!ëÃã½jÙߏéã(ˆàG[6óÒöwÑßb#Bòæç~T0ÑéýOŒ>õ«g~?§˜7mã½jö/É;€nïßÝE¢Úo*Ž%2šxhÈHµšégî ³mclùEoѐGkç!mj•bÚfF"ÜÃqëiÕY&Û"õd* +gEÊZù²‹VXv¾õ¸Ù2šÈûP£‘|¬ß +¨L_ÞYmùÖtÚléë(Sn6¾û/ÞSj‘NÙ£éŽo7¿ZXpøŠ‹1a µJ$¶r黸¾·ÿ‹Ôó¼X‹H­†‹v
+Û¬R:>ܽ·qnNôƒÃ)U±yhŒ¢ç2çLÜËi}mÄ©LyZ˜p‹«JÀŒøw˜Âë‘0äHŸÄÝu„ ‘>+pë–Hó¿ßÆÕêÌðÅ ”k›Õ#¿J£¬)Ëpº4g7:÷ìÒýžÊÂO:'(i‘s7Ø<ïçX¨Ñ#L²¯£¯*`Ût3¬CS¯?ë[-ö$y"Š¦2‹[@ÞÛÖ×ÅáÂÈÐåI¢ ÒF\¯ù5«g(\¾Axg†á{¼<<¶•ÀV¢õjÓJÒ¸x?ÿÄ(!1AQaq ‘ð¡0±ÁáñÑÿÚ?!H C¼š®}#ø˜þ§Äû÷ñ46Ó?ò•}ÿÄ[¦§´úñ3ÖN?ª}7ø”ÙfŠ–ìù87ºG0ˆÚQ³î 0ªÁ ?¼·ïÍ/†‡˜½ž$5Ìэ*àåÇ}:NÏï‰}C³¥,ñ%•¨l!^¥—ˆG`4uK.õxüCÚzäS^ð‚D¼˜*x?„”@<•^Y+;ï1£1¨MœEf3hՊ“µ¹;1R½U:;4wG­±‰ +h»QNé¬{£N›ýD¸Qc€À¡´Û JèzŸZHàv Úõ´€Ö¦Î–ûvýދ ³­dv}ÄYUÚì¯#–áê:úJì,L(û+"6ˆ 4«ôëF݃PGYWð8›ÌÙãÙþ¥¼+G3SÆf2\.AÚ ¾•››” Ôãdí1â‡áØÿ¢ep¹ö€DE¬Gx2JKm’ ÿ§&ñM Ïíßí:_qò_h<ÊÄéù=‰íOÉètÐ&VYt|ÃB‚¸¶¹JÄsÖ~ÄFM¬«ÑÏLÂpJAém)Õ8wÿaSœŽ‘ü³¹}N˜|×U5ЕԚ]#ù:a‡\=êçkB÷”$3\Æñ--Éö79ôï +}=:,êt\3ƒÁÚÔxÊÚÔÄ­eZ£¯q¯CÓìûE™PÒL 2$F\¯«ëûKhV„éˆ1pªiîCèÕj­µYÄ8 Etç,ªóh뙐°¶ ˽L ›ÄÞ"RœÝÅ4"סÎ:œ(5qN¢ŽkuMI¯îeą‚s«O¼Zªò +¹1 ”Æ1­{=—,ŠÙ=’Í<=+TôY`q¬JÑÉ-]ÎÅÒ²rrm® ~æ3]ގý¥œ»-ŽÎ­ì3Y²>G—CțBá¼x&¢OåX7y˜ú¡^ ù"ÿú
+1¾ègQ€x±4ZGQùƒ(ڔe
+üÄj̬VÐrDÖæ%fà•Àì©J91¤Ò""À' +pKZñ5°Î³IÿÚ $õĤ~é>–ùwÎú¸w²í‡Nïά½#`™ +íö~}~,TN"wÕ)³ð€ÿÄ%!1AQa q‘¡±ðÁáÿÚ?Cµ€ùºûÓµ z–¥ ¶ +Ó·äíJ´PƒMÏÉÅ)ɘÖϚŸG¦:˜t†wö’¦\?ož¦MPž–^Æo|éG +‹¹çzS'ÙsZÙE8÷ãhíÍiY6ó‡Š\L\@‡íw¨RŒ\ßW·ÉY¹B|_TÓOäF]ÿoL†¯ÄWÒú¤¦™Ó3¿í12½3GØS Ô
+Ë?|Se †X»Þ°i +Ýã%4Jòéæ˜]'‡<ÔàTV.žj5KÆ`Úd¾šù¢‚ moHëÿÄ !01@ÿÚ?ÁBòº±ŽÇfj§­Uxu:J ÎrqñÈøÿÄ%!1AQaq‘± ¡ÁáðÿÚ?AŠeµè¤ ±,ýd†ßǃdzÁ!]éÏ9ú²åíš7<àþȍ‰{§¹á†J5oè›{¬ uÄ"`!Šþ³Wÿwõ,ÁÝ
+Ò*“íV‘+¶cÝ|ƒAL¦¦n¤†žØÒ¦°!9Û6&͍¢;e£ôE .›š`¨tP«`£\UÈ$Ä Ȭ%XUÇðˆn+œÙà$ìeb÷Ì`iÝYÈrU1- €À1°D½ouTœ²KÇN_ FÙ›-è›9ˆ2ü+öñK7á¬Ô7›ôl…´êîIiíQ觕úõ1ýæäÀ†;|0Bj(„z`^pà2o[óòòa×Àø[¦Må.1‰¬úÍxîBütúÀ$ǎœ6àc¿0}@ž þøîÄHîÈTÓ¹¬Óz•¸0])„gþ~4¯¼»ù¦°wrÕã6f°Þ±ð^¥uÚô¢yNáQ€uÃ]‰E Îr¾ÌzçÂìð`âä>0Ž>óñ¼îL}äHnáFÒ;.Ò`\¯B Î<mÈz@[lñ}aíïƱzeX¥Þðoœsºe1ÁÊEÈ=ÅLŸõÿ k›!§ –)(oâ« ÞA‰WT¥ÒY÷K‘Öþ«JySÖ
+ƒTôU8 +E Ä<à©{͛*smŽŠü¤:˜ZSZÿu“x*‡³T’Vé
+î _$:#rÃ6)Ðcý`øDH‰³mAF´lát¢g¨÷ʓè|àÒYI>azóÍáæ(å¶"`µ}ÿõ†zøuÚdz…‡ýûrOx/æ ±ÊUý›xÚßáÀÀN¨ºRÝ=s ea€#Pz×Î(¦Pª½s“]õŠCmž¡ÁasU‚K¢6(H +(ëlrõWôä4wü±Ë­˜Q0u¦'qǬãXe<^°£‰±Å×5›`¿óqoxÝû"e¤yÿ UYܦ-hÎù͏·;*eH4Õ_qäqtû×ÞCÅqŠ8–ëƳÖ'œIσþ¹¶g¿Œ‚ɼ︷ëܳµÖtKŠœ›±eD’Xã$ û:¼ +öàk÷/Ömr«ë8ã½ÿ÷á5¼^Þ2®“*<þ`æ¾#Ä׬lRG!æå™6¯\51}s/ÔûĦ¹“yÛïýq+>¡)¦(¿X¡jåEdf±€Â±ð‘w7ÒCY (—ÎÀ´Ø|É6á›B¥f +—Hèî0±Œ~°‘I›$ÝäÂO -B…eBýânEPd*K«–7t’¥ÐŽ»Ýf…ºå½ÕÊQ +}8—LÀ@ƒb(:E=âÍ&р-QGs]"8;®¸yÍ?iæ +ºMìÚ-Ãr±‡za%i˜àc»Ž›Jf'n›TTmó|âI¾mTz˜~€k§¨›K==ÆSM¼<o/RYô`%.íRÌ
+œ¦À•}³Ë¡ç"ƒUX{Qdzq‰`‚¬=ŽûqRÍ7HkQºÐÕ0‚wåâäN…a•*ùU¡>…È Iã PHý3"à ‚=¼kØ…¨_x•*ÚÄwyû€‘ A£ëÎIŸÞ<IҊX<§ èX•÷›ΐßî p3ÿÙ
+endstream
+endobj
+40 0 obj
+<<
+/Filter /DCTDecode
+/Length 20738
+/ColorSpace /DeviceRGB
+/Type /XObject
+/BitsPerComponent 8
+/Height 236
+/Width 200
+/Subtype /Image
+>>
+stream
+ÿØÿàJFIFÿÛC
+
+
+ + +  ÿÛC   + +ÿÂìÈ"ÿÄÿÄÿÚ Õ N¢q§ Ÿ"iò‰´`Äö«*‰K«G#ŽôØÍÞª–9×úÆ0Ë×BT©Ì“©I<ƒÛlüìh%ٟ.ÌÜt¿ÈDnub¥Kµ eûãøk 0xdÆÚìî…»2Hòðœ¨XjG|]¸WYk`1VOlsQ¢îVc¸GWê.Ë!Õ`VVÖHÉ ²Zó곑Üe
+LTv‰R5L¬‰ +S|Ç,ƒjðùSߕé_ÆöðÚ)KÈKÖyR’Àë"V’ýÜǜcŸy^ܵ%Ì?g§…ÑiÛå|\l‘àø ǚ±ºÌöžÓyY–—+Ԁ\Ôy ²ê1NŠÖJ”°©}fËpaùƒã&>£å„^Ó·Èé:Ý륳BQwÎH$%ã•ËƵ¶ªST/ZdÒÒ¡néBµ‚ƒ}lêê$xæÂ?ÎP8|䢏ŸZ6È?R¢xŽŸÕ/ê^ä§ +Re=YŸÆtïA
+É«-¥Š¶É[W1GçÔµ0k˜úÞt[ædI ¼½V䢩Дn©%©‹]ÑÀ#ßᘭj&É!–\ ^oсjS”Õy]’´. w,³Ž¾KÇÏBc¸²Žß+k¬]©ÊÎë¢õˆyþÆÍd—Ì„±K¶-]E}©±{ pUSªäOu뛍@» ’{HBýuûTP~P¶€Ã££ä3ބü(U.F"¼èð“"¹t¥`T²þ…©LIJÝë<£týKèX5—yÖø©€|™®þLÿÁÍGô¶,0ü³Ô¼g¡è΋Úqw8. +èì:<òè›C'Fì?“(KÝ b¤3ŸiTùé#*èFp?ÿÄ+ 0#!"@P$ÿÚÿ„Åâõ;—kâdŽp\dó‹œ!è>cE>ٕ9ÿFÑȪë=md¸_7‹ iµØzÑl@ŇRuÇ3…tzŸŽS1›ånt«<Ž¦Éûfš1¢²òÖCñÊô™Œ# ÔË('k Å­lÁ㤭ÚX—bÍ^É®ØßV+iÖ–ôå6>•\P‹^Äf¢~’Ë„›ºgÊNT)œjº\6Œu1Û$ÑXRiV )¹£ÃªÆž­a­Yg†§µ¬¿M“A̞¾Óæ›æQˆ†qÙqu÷âYþ‹S’ù ¼ˆÌ׃ÞȜ9ØÊz[%V)ý²ú‘ͱvçä£m¼äíÆ2­“´ëÉ4QVñTº! ïDéàm‰ø¶À °²òåkVʗ"U¥¼ÌM&5ÍRc;ÉýF²ÀÒµgæ.[!å¢]þpàd‹ŒDìîp )˜Ók(ç{£k³¨:¿ÊuÄB-Áûâq±.«ê³ØÞï·p´©Ñgº>ØÓ^BX›mI®>;òřlΕ
+4a ¼t¼ÍTQÉb±­÷6v4ÚÎ=»QÔ\‘C]|• BCÚÁsWµŽ·@³}ž½øs+¼Âº¼‡E7©{æ鮡hŽØqŠ%`¨»¶èD¨ ³ìšìÄM5¡b&;ހ‹–…Ã:Û·½ïYQ,×BC®š6•*M÷`@&í“å
+:é¯:7$~ç[Ÿ+÷³éuç!Ù.û 2ŠëuÀ nõ´Œ1w-Â֛åb_“ĀŝQºZ~D®$‘ÎB˜þø¼…£ì– °š.­AH(ܕV°É}Ï!û†"R~­UøX™|B.40VO``Qlj>2žÀ*òxZlÁ]dQÎCՑt„jïy7ȓǷ!ט<ϲ´Ê餀``ÃV:7iÁ$Áòx7ÇÏa¢
+Ú¬*³¾¹Għ÷”ÿ_;µ¨®Ûà)rI#ïåû
+‹xb^½/1 \6fU”›ƒ êÑZ.Á­àÌ#`Ѹ¦'=“S[ÏjJKSVLêÍloVų©fx}
+|Æz6µ¸:nUƒ„çÚ=¼9ÀŸál ¶CãF@®c9ƄÙ*d‹Ï]ñ$ÔeŸl¦6;U(ö¨N.˜p±ŠÔΌmÔ(à` +x²¥Ø­§íúŽÀ²i¤Û]#H•k,!üE¹Ë¬4<]·Ã^?ÇaÉ>½¯uhÑ£©°s ;¿¼Þû¢=1ŠŒhl*Y$áÂs¦òJñÈãÓÊv5Å»07íC㣠‰eñàÒ²N‘4è(6­–ÃIH°Ñ`iÃL‡>Ç2ør'òrS o;€MîÙ!t=x)ß.dº´Dlú™xÄC Í/Á¯Î»/bÌV¦bcÊÀó˜ÀI¢X1dµ9&>[¤ÇÙ÷†4–%å [Þ¨ÄDZ±üª?¿4ørv{{ë¡pKšb<‹°Kåbä7Ôb>¹º7‚ØüZ°ÕJРžÌãKl2(Ôy1×q$ÌÖXößHQ§|°s¥ÃoޝRÏwË~ÕÐË”Ð9 :È òݏ› Ft¯jà°ÌR½r¨2M;qbTw+­ oêt“ÆØbl ¤F êµuö}¥½t}d,ÉϜ‚œqX‘nm¿Òs¯›‰¿öÚ|9 Ozµ]+R5o#–QbÊBÎnñ|á—ÃcžÒ|jÏ´[Ê¿‰á&H7ƒŽØHBë]5Ña(™F#ݒ P§ö¤41‡Y/hBŠ:M©¦`s¸L,ƛÂô¿-*\|Ãߦ›ÄØ\'Ú½&ÆöسZIcŒ{bu&s¬vòs©„q3Þ \qÑ°A0-ÖYGE¦×0IǶÞ#Ìèõ‡£gÌØ /¦Ø›V#ˆ½É–ÂP¨Šò¢§áœyâ›À^6B춦2ŸØîd"?Dq™3? V0NÖqN+u¼Ó­<@AƒÔœÆ§˜Ú6 Ì„Ž t „:î˱ÔBÃ,…n •|rçi«v\}¿’¿Z_2Ö{m¬Úk‰M’±°ÂÁˆ¥´ËǚO=^5çÇ£\ ±n6‰€‡¤ìF~^ÈcõvY—©Ën_yŒnF}¿(1¹ÓG¸ˆÜvÕî!S¼þÔÂõU½…œhæ‚Ãg“;f<º²YlÜŠv€f‹\
+Äó 3mÞ¦¨r*$fFbU^€5c¸VXñŒ“hû
+ÊùK.“ °Éõ⟿¹ù¼r¼Vøò…bI5دŠ2DŽJk¤£)ø‚U㬻«@…„ðµÃFvìxm±^#ÙÕulø–ª'QdŸdÙmiYE] wP Û £ÓŒ*|cXÝh–qçՑ!œpñâÁ¯jP90Ž…ïÐ4ImÑÎàQ”læԋÒm+r +<4¶BhƎÁ†#¨ }„sÞ²  ”íc­Ö¢¯ÁXRO!?ýÁP·¯4ãF¢l +×FI"÷B õ…½k¹kÁÇe¬ÈÆ3x9Ym F×g¦•½vс Ù´ ì´÷F,b¨™NKb8ñ‰éÛ\o­›ŒH 9*)±õ(ÉIS „ÊV2 |姵.ÖiBƖB7›aX=‡ë=€Æß½Ö¸·yŠ×\G¯ì}UYdˆþ%h¢l^­ÕlÅÉuÆ;}Ššf¸.“ë! +&äÀÖçp´t»‰3!muvô3Œmƒ)¨ØtGWfë<4‹=Ä5èvš‘nq,ÛÿÄpÿÚ?CÿÄpÿÚ?CÿÄP
+!1"AQaq2B‘¡#b±Á3CRr‚’Ñð $04S¢²sÂÒá%@DPTtâ5cƒ“³ÿÚ?ÿȯY[7„Žø`ˆÞz³ÿڏo°x\¯þòP¿qÇÿG[î?øàq2™õf¿ÝÆZšcõ£¸ù`t:øgcìë|9ÿÜZ"Ý.°~b#Ëõf
+eÑ5!ö¡ê/¾C÷`KŸçkù¸½cï;Ÿ†?«ä5Õç±çýò¿f“Ѽ¶’5ö§•ü'SSåÖڙ%¨QïD¶8°¦M˜EËÕd÷vÛ¦úWÊeò¡û
+Ÿ–4åY¤¹}hߢW.—aûqýq1 _iþ‘múÜÇ¿ ¾‡V11çä{µyeuŽ4fc`?z2’[„fòýãˆß56ͬ(ãÝŸ¶šQCN6Ô|ýïÿ-±WIC”ÉS\‘’%A}mmL¬ç¸Xûñ +m=ø2ò,-áŒÞª¡#ª¬¥–H)éæH‚ZÞ-ïÜb*îCÈéA`ÞªËÕ"ß?-±`ƒKRtT‹Õeì`{HòÀe:”îíÁŽ¦ž:n¯A·•ñÒÎÈ­¢Ëׅ¥wÝ/êøÛq„xSñe0ÕçO?êŸäøa2ÿH ’z+Ùfæ@ú­í GSI2Ïî²’¢¢E†Ʀvä02ܵZ ± +É=ߦÿpÃÓeeQ‚Uæ’.®·-#ô›ÃÅaFòO—¥IDCˆõcb\Ÿ1 +u}¨ƒ"³+é'Ùñ8–œeÓ;Ë$übã@Ôd'sÏe½Ø‰)²É^¢îTó©˜Émõ¬ïØmŒ¨TÐEl‹Òj´Iµ¬Ÿ[­áى¢i¢)kƒÃ ïìÁVŒTG-ïÄ·[¿õ½ûþ®)ø’¤\j[ËÕn©Û¬z·µ¶6óÄ<$I$ +¼e-Æ¢7ê¿; +Áì8¬ÍKRñcšRr:íÚÂÃRó6n0ÙUN—¤u›‹In¨n£ê_ —±òÅ~YVϜeT®b™fÖi{›ë/?<G˜e“þ0ȪH<î§Ïǹ°•”t;2žh{ö)’e,zM×e­ÍπìÃä¹$(ö«®Ó֑ûTx÷žÎX\§‡M ¨¦‰âºÆü6¡Ÿg9Ži'D¡ŠsÇÄ7¶¡¥z·n®ý½Ûàf™”Zf;ÓS7þ?ç=§ÝŠZ9ã@ÕÎbŠPڀkî[ݾ2êìɪik¨‹· +eê¨ü­Ø1QžtY%Z…(¼I8qF€õGÛÊødoH©âÐn#Ë g:»µÞØX…li'0%‹…sÞ7°>X̲‰$TÅ'L‘‡Y†÷Û|ñ(XeZ]bAGEM*²žøݖɿg,MMéFO'⊃~;Y¸múLÕ¿}ûñ–VÓÔµVSZ¥-«A+Õ[û…±×Q±épƕ«dÐþužì5tô¿GëS‰SIkðÃ~qÚ=ø†²•ÍfC[¸ ßRÿوª©äÁ*êVÓL®‘M¬Øéæ±ÿ×—Ç ©3£ãVK»Eb¯Öì7=˜ §¡CUC +žj(&Ðά:¤ïïߞ+Vh¤¢¥¥£ãE’k=/Pm犪èòù+cZÉ%‚ +’5b}wcÍ­`-{c1Ìs9/$¢‚­¡àéî"ÚÍ·Þ،ÁÏèØ )¥yDs(#mWçϟ<f³ÕÁ=GíO¯H;sïÅ-.Šˆ(c„F²pu£‘òøßÖ+Ð~‰éz–bØtLÊJ‰ª¬xëû¡[8Ý~ªð”d+f[l{ùá³,«4©Í)"}UYlUL®ƒêˆêroHä
+љ8—ÒÇ·0IÝmÛ¾*($¥lºIF±GÕH7Ó7}÷)ۏFý$K–ôcô rɱ_–£Š¯JíËD›…÷Œ~*©M9mljßû:‚̽^å,§ßç‰ýÍLLö… ݖðoéMU;h†.ÇÇ¥9þ«Fu$}íì(òÛßlPÒIKUU˜<Ë[Rô}~±²‘ᵁçlVft/Qø¾ZÈɧú¤u¿+öu{<qœEM—ËÁ’u‚ª]j¶…äÁï'Uñ.^ôÓIL·X)´H¶óS·¿I˜åëO˜™8!+#ݘv_ÕnÌX ì>_ á­I ,¿ ¾þÌEO
+è†%«Ü 4iâÅFÐÆé$.´Ó¨wùbŸ6GÂßZä[ïøâ*¸rޙR›k‚n àx7o‘Äõ´‰RG>c¯¼•m;wó܃|EKžÃ‹ Â'ƒX~¡ÜvÜ2ßcánxšˆTœ“=_ ¨c}zђ|û{oŠJùNšÈ$F)Ûō®ãୌ×/Yâ©nŽ;=ip¹”l$Ìò¾¤ÒGùØù‡ë7¼â)dkÕCôSy÷ûÿ£K”Â~–©µºÑ¾'ìÆ[”µ"
+º¯­)õ~w?²¸Ž§-àÑÌÎZ¥›]¥ÛµT‹ó¾+¢âÆ[,c0—óQ€ImÈXmáˆè"Š®†š;ĒÔ@UZÜί㈩šSIGQ-7ŸûÍJȗ¹þWðÆO2#ÔåÒOåc­¦¶‚ûïe`yât¤éꙑc‘ÄÀcÑ¿ëÅ$Ô…bºæ ½þGߌ’¥êfj©'ëHò·[¬Ã +†3:j¾›AÁN_ŠOôƒéûþxʜÚ^‘˜Ã Ç-K6´Ñryò7ùb²zåŽjyR§H‘¬Ê©©–ÝÛbIµÉÑ&ËÄÔñl­7·ÇLùe+Ë#jfh$⊠=¤­Ë*FÂˆ ߐùbJjz—›Ñéú€¿årÙ{/õoðòÆu”Êy™Ó[Â"ÀT!³{šÊ|› +=COR]––ç:iùuoú‡K®ô¦5˪/ȋ}>ý¿oSmG[´~ýÓþ_èË3õ¨è‰>SaþmñS7MèÒÏIŠ5PÏ3µÄIk~Š®ß[ÑÔ=o@©‹‡#U¥´ÔÖ·pŽ+ÏÆ©®Íº$`ÿ„²q%>]ø¯Ž|Ùæ0먮·à‡Ýùzßuñ$1¿ +àqUEPª5,N6µýÜ[zŠItÅ55hyÍúÑ|~ÜVQt©cž„¬Ó$igMÇ1¸åËJ«M +D-]4cas»“ñ厉ærS 3ÇDcꪹӨx\÷â.$µµk9èP†ÚI›•n|9âªzǯg£–5~#5+/©o +ùöâ?¤¨¨Õ ©ã³õ$Fw÷vbƒÒ
+xêxn‚Š"‡Õ^A
+ß¼`mo…‚_¢FZ«ÿNþÃy_o~w3X“Fk@7Z”äZÞ<ñ‘f°¾Ê땹ÉN÷Q«ëFÝSîÆq˜©5:ÊÝÊì7?~8ô‚Ž¬pàu^Ž–õÞÇì8ÈóáÔ­…ú<Äv:ÿòVø⒱yOÉñ†¾³¶Y—ϳçŒâ·óõr-mâvû_冬…©"¥¥¨5K·Öí¨Â© q™ÕVKôÔ1»ô˜PÚ¢Yì <“³´ã§G^s\–w~†”±]G·¿yd”S%kÓib@ŠI¥n»]›Ù¹åŒµâ®3TÒÆ"5Ø0ïø3 ß+±Ìhg‘]ó°•çóÅ&sJD­–Ï B5Fâ2éëàn~¨©_ª¬šz·Õ{|1æ‚ž’¦¥' +ãz¯nïWæEIË璞œu [×>C•ñ˜ed»Q²¬trðÙÂÆïÖÝÛã2Ê4ÓGeD +;i*MØ/–$‘#–4WÐ ¨P·f=N=>âx-~$doïÀ¤†s$ñÑæÕýâœõ¢¿}˜hý¼IG]jÊ ~f¢×1øj0;±é~fùZzh¿kE›æN$U¾WO1uÿ‡™PZæ ÅbxjU¹±dÝ©¤h¾ñöþŠûÔL±ÿÅ÷cÑèÕÍ35@к¬ÄøjtÂ[(‡ñ´É<¿At¬·CTúÃá¾2<¥ã]*°Á<PòQõøcñUYKM,d^˜J5!mÞNœQTTä½/¤=9j=`O­~GIû1[Ç .‚IxÂ¬¥–üÁ·ÛŒÛ.†XR’Åx½kö6ø—>¤Ã
+@>†EÕi¸šXy[ÐÓÖÇ$ +@jÄ[‡É*ëgè‘WqxµqC¨FA² c&¶eC˜Ì±ÞÃ(XÞoŒÃ/é¼92äHŒê€ñf"åÇ«áGSŠÇš¨ÕAëF9©8£§ZÁ<­S,sÁÂUÓ
+ûwïÂ$õò3¬ó©‹€¡4ÛÖ·;‘ø(3+H°AP¥Ú¼<2à±#š¯ÝåŠÚd"(¦šŒßԟTL?yÆ3¨a]Ö×҃°³\¯»rKÌ`DáeuúÄütâEž×Í2…w·X}Œâ‰½‚oÁû¿SO~lïo†-¡™)ž<¨&§[3GÁ1Ræ‘~0JVá-5‡Ð‹ ¾×:¶·mÈÇ¢o@Æ
+ù**P’?'#7[ásŠz (M\*†j©#‹T̶[˜»aÓ6£üeĀ4sFxŽ#Õê›öú¢ãcWI½`Üdzﵱ™ÖÁSK•p¬J­rÑÛÚ·nÑêÕvä“sÏQ$xâ,ÆL΁M=R4fNÞÓñìÄ99®¢ªDŠHÞ ½«A­á¾2gL”Ùc†Y%kñXxßÙÆY_J"ª@µΨÃ/'¸?,e‹³ZiNX$—Mô…ýfk±O5&m ÓÑÕ½JÍjY[–4ÒgPÕS¥KO*Ã÷>έ[ ,)_Nò³h$Õ݇/mu¯ÝŠlë.‘'Ì~‚Më7ªGÕ${±.Wm(ù¼Jǖ.À|vÃõLqËÃÃóp€à¹ÿöcÑúÉ«Uô¤ŸQXkQðAŒþ“°ñmîÓÓ~’ñ{c5•ÈHãŠîÇ`,Çþ,Pæ1d”ã/ÔY%ŽN·åuè~ñåƒG–RÅWÑæ“0V’M1Ž*.÷ó¹÷âXikíZj¥§_£ÐÇÚ¿-Ï?NðTðj ôy5Ս%|,×&ý c+¥ž;@ò%8+·T6ãÎ×ÅF]˜§õìº ¨ÿ5u[áŽ<Ë"Sçp“{X”õmûø▗£¡§—-“RÎïsƒqëe[xý&)t…²Õ©]¶ü©Ç¥ÜP"Ì8’tˆ—dIÒWÏgZf/E¦m ³wÿ.&…åA4™„áRûžß»¤/2,²fÓª&­Ûù¶36’²Τúwc²÷ÛðgqNLޏô΋SOþ¶ë òÇ£­,Úê©ê㦚@.ž¼R{Åý÷Ãå嘚iø];ñ*7=OØ\œz6Ó¸y…mœ¯.´o°ðÆbƒÛâ}€þ‘?Üý*á‰iE\sðå6B‰^çö‡¿ÊðTÁœI7žž;•ë¥»[’OÖÆ}X/-}Ed”Ôp¨ÔÌuÂøŠ–¥Æ_BP$”®æ ‹Ü’-s}ÿk¿eSe”OF±®žÌšoÕWõ»yœG©Ž2
+' ¶å‰$xQ¤’>5·)݈£„ˆ?%ÕüŸê÷`UxÍHå6ž·ÇÕ4ÑÎÉê—]Æ9iâ–4õQÐ0äÒÂK®–<1ÖÇ¥54È
+ +(ªÌ;F‘ÒAƒ`Ë`G, +Z«®ÃL|5ïfìíñÃBՐ¥Rn±ÐS-ÓɘxóÄ| Ó7§•˜[¦*ì?stù‘Y£ÍôÇ-J~OˆNŸ ýøÉR¦M3åUéE)oj"KBÿê5ßsêÙ¸onHÛÝ·e³(ÿÒØ­ý¿ô~ŽÂCŠ-$Õt‹Rt¦ÝÈ09{7û±Ó¤«J†áp#Ž;}
+ÞåI³rßÍRC®¶ ÐÃ9 ©–-'W߄Zh\¹£L“GP8¢Öýë|qG%mtï]MVËi[÷›»°µT¬mɑ½hÛ¸éTV<o*BšÊÇÎاÍ`˜¼´â9B·RX­´€{'IÜb¶½"fÎÔñ üû^˧̜+TRe°ÖIgž\ѕ¤w·vö{y ?ÚJDß ÒáÏnì7ý¶kØúÑðZ÷òÕݨx_WC“f:ÕX-ú<îq[ŒMáÒ²:Iba1때¬±1ñÓ¨bº¨]åÔëLAöô‰ÿ=øômG·UÇ÷,L~ügÚú8¿h_ÓV/f¡"(Už*ÑJÒèÿSê÷l¿ÇMIšµLñTÅLJp¢PtãìüN'ºLRÔ2 )–׊Kvj;àeJʏ¥x†®‰ö¿[¸ag£‰«iæ!Úu‘›Qý>|ÏnãϳÙí$i!1k„¨§Onãc·¿ eK6Vçmm֎þx—1YE,hdÕÕp;° æ I+¼:wÆ牕ÕÁ +׉,¥F€mÖµ÷Ž. +Á횢¦ý5¼–][yc9›)^ui„¾¤ÔÖ?¢vÇ£ª(eU!«dHt(yòÔÛyb£MSNÐÙÀø­û]¼‡Ëè_#»¾æ8®[Ôç¸ùwâ(èsz] uÛO u[{7Äÿ# +HËëf^#ëT´QX}bؗBJ§…, ê»˱÷â¤éÒْÕ;+sc*?Ò1“Gÿ§¢–O/QŽ3êëÜÿ™ï÷~yÇæjüˆ?ôÇ£ÕCÿ—˜Oë.“÷6£Y'hD‚Vm7ߖÀ÷b¦J“-#]Ȭ„•{µ¬w¶„ô±ë%Œ“#$oÒn·3‰² ¯Eq«2‹Ø ÿ‡úÝãÇ·dôѨ¥¤`KÇ°WWË^håâ£)ÕÊ¡×Û÷P/<I TÓU0]|6ÑM`ѹ|ü1%t ô‰×‘Gm½¿?»Ä -D‰Ä]½EÕkŽÐ;#ïú˜ž•˜¼4¥ þ¿ +—PSå{`Èôfx_šƒqÕnë‹ïၡº
+fN­þˆ0åçŒÂS_¢’Š ©CbìFb7Û´aÄy+ÊBŽ¢¾¶Ú='}ðŸöo£ám{†Ý +¾|ÃÃQK•ñ‘úéHå\÷ÿ?Ã~+Ѽ¨N*»]¾Ûì¼» À¦âjzQMÇ·Y{ˆÛÏö–––EhsÎf÷¶ß³H³ʊa_;?ê\WÕÏL~Èÿåøs8u? ZßîÄ&ÿI•Ö†ý†çòføb®–±8ÏOëj–ÇÃÏ Ò •Ô¯«ƒ(Ê´¶âûÖĒELã@ÖnÀoÏü<E-%1£¨„2ô„ší¤ÚëkoÌ}¸1,v(㈘‘s„ü⑾üIhDzyA(Úmiߟǖ!yIU…ÞßáŒÎ:dU¥­‘É0O»­ŒÒF‘a§0ÓɪF°°û±S-Du‘£•GÝŠTÍèZ9($6(1L:ÈWø,féã¬bMW BÂö۞×ÑüÒF ÅSQ ¶öÛðÄ+£ð¿÷–m‰ßÚýoäáõäP'>“wÔ»žvn;8zlÔe +øR2O+aÍdPuŒƒ¥'aÒ~֓h[GÒ£ݹ8ÔzµÕIœ©{ò„Ë a¦NNjo÷þXͽ¨Úž­Z!~ÞÕÿ)8h¥žŸ¢Ã5ÿLqOà ÅU"š] 6;,Ëö[ÀcñúÆ\LÌê« +Ë܎åøàœL )ïqlC#6‹Â “þí>æÄUñHrð$‰ê–-`j±ê÷ï SÒBñÔÑSʶã>™–Ûé-êûŽ+/£ˆ–âAceþ<±ž‡Ž9ªe*Ó­¬K­Ç–(º;˜§ÅTuԐ#çüÝ£W/¨¥¬³i[£#]DvC~íñ[;Í[×&§…oЦǸmM6a7â3–ÒGªÖþ|qÄÿi«˜‘{ÅRo.^xªZ決¼SA +©ln}Üñx«©fÕlò ›Y—Ý¿ïâXª „¯ÑÇj]}]¼ôã"ÊPÙ뢎7=ÑO–ßµŠ,¥?¹ÑlàrÛ¬ÿpþŽ[é.Ír;Ý~ñŒŸ<§ RWèIsÚ>d¯Ã Õ.ºÉ"
+¢ä–5¬jš–Šª#d‚Åw[öóܜ>VîÒEÒHe ~îìh¤‘éçÞíæ¦ÖåõqûCV‘h<8éLq¶›%Ù½¯ÉòðÂp´ð­ÕÑÊØËeàZy'´¯*Í
+©vÜýQ‚cáÇÃëÀÑ%¸wîø;Þ)éØÒÔ5ÄW¹í¶Üù[Ïd5²- +'dé-ºõN«{Ïo/ègËŒ¢Fi¢gM¾í¼N3Yrȑ•ë.x¤þN>ÿ~ ¯ªèi2L&`µ[µ®E¾®$“ñVI5ì1íߗó¤âL‡-àÉg¥^÷nÍ»nÃ݁Ä˧£$QÊ,µ…‘ÃÏ +OIfI&qPw³u;=®,?¤uÐÒÑ%%"·´@½ì?gÙåOZz·*Œ{¯v>óöF¦†É̶¿qì8¯ôC6ºÜžü›ÃýC‘ÕÛ¥Ã\œFÁµñÈ#Ì[*dHäeFMGžè?Ž"«£æ*GÒ"†Øªû6ߝ¾ÞÌp§¥IP{tÆÌ<4Ÿ5åúClWSÇÖ¢ž£©Å‚êž©þ~8JŒ¶w¢¾Â’]¿w–öîæ#Š¾¶‰E4ÑÂü>\€zݛª·ÀàÔ¬#×^Õïþ~õچ*ˆ¸‰ 3ZNk©‰÷sÅ\ó1jf× É{X§³sõºÞcjTWSA$p¬le†3y¿‚Et²«SÇØîíV9Åg Ǩ²èP,GÕú£å߈×פŽl’qïü­½Ø)MWW
+=~•±ö½R6ç€ÐæÉÑ9M¨µíËPí—n))€üg?OM¸áŽßpÛo¡ùORššÜR9 +»|‡Ìâ
+HL0¨Eҏ>ËÃ
+Ê[qts*=¯1ŠLΔ/ã¼¹ÕÌw¶¢=Ÿ&ÞÝب¬®§ŒUq4$r~jײïÊÛÿ™»ôËô
+Ѷ„iN®ÞîËxmæìUä©o¤*èm¬öx +íï ©ïëk¨¨ªÉîÄô2VË 3 *‰Gê—Q±îÄ3ÒTôÓpÜÐZݛ1ÄH2ö§eÜÉÌ5¹çî–‘éj¨ë)©tD“Çn1 a¤öâ:&ŸT…Dlªlnfç³ÄùĈÍM5.¥ŠTê”,6>a¦ÿô8SǨҌ•Žß-ûHŽ¢
+
+Y«ØhDfOÒÛ_k¹¯xޝ-†™Îé¿SSräwmþü=-E9­2›ÁPí¨’.¿mû/办S5$‘¶—p{-×û°ÕÕ ÿmW +1ÄÞLJ»™Ãæµ œÆ¿®KóUçóçý€Ï2«.ÚX{þÉðîîÁÎrI_¤~z”_•Á¥°ó°ÃG>i,èZò +1úÖíëü± usë§èí!
+º5x +'Ìû±OQ,4ôzÒL­&çïóÀU«éU‘N¼6ԋk›YQzÛß·R¹ˆ0MeŸ‰dëX]y“‰)Ú·*‘ã;ÅÅhۗñÄt²!†¦D*±ÉPøÜ-’T ܶQsìêW!ɍ鸝·Ó ?8ñ©
+$ÔõtmÏìJßkX|퇥¬Ñ,ñ"¬Š
+÷X½ö¶8*«†Ûpçvt¼‡í/¤²î›ÓÀþµû +»û†¤ªéËâkSӞFݞ]ýçû†dDãK#r#6ÉËO–ô‘ôÑo5~I[&[™šU§õ€ÿV!5òfPȁÄFYaõˆµÁ/¾8+"ZÈd
+É8ºúÀ R3<Œâ£‰ÑSéǤCÚþ8…¨f–ŠHÓB´Pöz®w÷ây'•ÌҝM3ž¶¯ .ÞX&ŠD¯¡`8”ÕjÅ_~{­± (©–LÀðùví¿Ž* EÕù:¹F#Îç (LÛôxäÕëÚcŒ;»qžÿXX¦¨–F Þ£ÇÌmËY|t5KX«°ˆOI#™µ^Ü-N{˜K˜V7Z»XÒßæp™®rL9Z£€lw/‡ŽQb‰•EdU€e;qøÏÑ© ”êèÁ­oÔ?v,ô¯/!תÒèÜìóâdÔuô·¿­D–?¯¿Ì`ŠÜŽ“-B4ká†Â÷ ¨m|F GÃÇq÷~%Ž9\ݵiˆ_nˑÏâ|°A—°±_=¿ëÛúC–,baë^=I cTÏÀƒXRVfây +ì=÷>H֍ªÓV­/«ÚÛqá€sÜÜÓÒ Ž•¿vÿõÂe^‹SK,§ª'{¼ñûð3/Hå5u-Öèåµoõo–¨
+£`göºk©•Øz²®Î¾ütŒ‡2bE›†ÿ±Ç 5¢éjxùþÚí…lÇ"áIÏ\j­ü1ŒÂ®Œ/%Òß±¹ñ<±,«›:¼—Ô×kò·jöoï8uij+/§Õ~¯eöƬ»Ñ´’@,dUþ8áå4]W§‹oßm°*sìɉ<Õ[ŸÚ8áÐÓ,WõŸ›·™ÿ¸Ø‹Œ6WLIíTÒ~XêÁ4?îæ?}ñùJÁÿäòâíDþKü-€iòÊuaə5‰Å‡þyÿÄ*!1AQaq‘¡± Áð0Ñ@PáñÿÚ?!ÿ¢í¨’=là—Çõ<$áü}apïüŽJ‡ÜEò×í€ð '×þð[_…¯ô?? úCä/ˆzÍìlò¾>ɘGý bòµ]ò§æÈßÆ£÷a¶µD£ÊI¼&NùçÎ! !¬Tz|¦<–Ìú_ë¼cœ]ŸÓûår ‹ÕËõÃçù¸}r=·Zyf$íÌuzÎp-p‰ÚWCq/bP«~GÖ:Òä·Ñ2¦"QÄÚNý<áè©c€ì‚ª\?á:pQÅei¸q¹Hš@¦c!5²m$ÓSàŽÙ´¶o}÷ž5€eäôôøÄÿμiäu-ýý͇P2NF!¾å¥×Óí–â5•cT&Ȁ· |²#¥“E¤ì‘z €‰WH…Þó!ILUwS3i@4:iÊ/GämC’G}󠌃dÁµc¨HÃÜô±%;1ôö Ž ½9 +c€ÑúÒN± ’§)}f¿(ꦁº#D4oíŸuu*]ÿ`–Œ¹àrŠ>Âsòañyu
+ОƒV¢ å™ÕÞÂÉ># xi¡qÀ‰¥JX’œÙ©>N +"ædöZ—Íl=½à»+4aóbÖÙ¤6%%f±›ȓw0é1Øúba@¢œÄK’b$DÈi0o©„·½Y~y͆Öo4(ˆ›”sz4FLjã#­”Û¤;äóˆތ¥''äXÉC’ߨ™\Ÿ86¯G÷ÁDü8ǗLBîýì±} Š&­D“(]¯ †ÄÊEöˆ{bªå2ëVcɒÅ÷Ù° )¨ÊO[„bº}Œs iZlµYh’…Hîäõ–Ô@`pÐõš +½Úê]ú™Ä)¥{‹¬)äÆ!â$óÄú6Sß©’LÄdÐê`½b ¢¹'îz›á;Ĝdrv×t8”¡Pǀ&Šj'z“+öùŽßÝâö& •jéû­ú‹¶F2·´Mœ¸K–ȹkԔdh‰¥êʕ9ð˜ÊdXÜ@„]#œ¥?OM -ق@Q’Łã”÷ã#^%ÀƒÃçDòãDð’ í±äÂ*‹sã)o|¾ž¨v‘™•N­‰Âž Œ•8@ ’>ˆgó8€PèðËž­1í¡ç%À¡í™¥©g½ã2… øs딕ñCîjay*µ1‘ÙˆïÅîG÷  9Vä<ä¾OÉôøn–Œ•0EÔn ²°¶ð&°œQ3À¼lèƜ!0°à +<°˜wÌ|à«þ›Bi2۔(öàj²kãÐÂ̦G„4®¬p^Ûð³ ͉ ØD­2œÕ´oJX¿N;,?–ÖLƒÍï@\Œ
+[¼J'·7Ø#½£js@[ãŸ/ì¸
+`0eæ +ÚÏYK“IëO)˜úYø¿—í‚™ò_OÜǾq_’´¡A ÊrC©Y¨£áqìoM¿Ãñ®¢®oO)Ë`½E¦+ªÅoëð +P‚#E¡ÉR²°_ñÕàp„䄸±õׅ°7bPLâEjÐÃb¬k8·Ì2‚ú©ÒIbø”'HK xR¾ø­¨…ZB>ç8Õ)§ÎDŸÜöýKÅò€ÂÛd‚X±ìµ– 4ùòH_8±’Å뷇‹8b„IË$ýë.o<J‘“P8OÖ-B`ñ¯Ùš
+†Ü¤þƒŠ|÷hçÁU¢'|e‡QI¤‡YéÅڂAT‰]cëëKìÄ`ö(Š Hš:ïôb­$‚˜±ë–4 +Ð,;‰Ã†#`CÎ"KFp0ÜPž3ÀŒV8¬ŠSSÁ¼²•Î½¼c~rºócĺÏ8žÁ`´_Y¦,; Ҙæœ/}û€ó{]’Àr°ß§`ÄÕ¼Z7Ñd{D
+§Øa6N=OŸªåüÍØOâûX¯¯Ç%]Hh€¡Êõ–æf%€¥ É-ÌC‰×Üø"ºÆQLmÜ2‡Êç Gä·YLî<‚‡ï–t¶¡ÆdfN<I¿Ä` LŽˆÀ± µQ¸ŽcFßÅ06­µ^"‹M¹âKkÒcJwTðˆ7€× ±ïÏdŒ4êåãW^&
+Õ=×èÿŽ\¹ €¶Û$¦1 ±’Š¿G·"õèЌþâЪD­}O(ŸÒkõK'èòá¬g*Bi‰Rb±’dM#Ñ ÀÄ9e³)ÿàÈŃ¥ÏÕý§ÈèȂÉùžó›ÐÏ;^g¼š®Ç1\^A7xbNQ¯õœÒQiÙ¨¥0m ŒÖeNHdÂ÷Â!!–¦0àº0TAµ¾Ì™Qšu)Öº…J¹²f'©îW¸•ÄMC“£ÃߌjÃU­.r|™°VTíô1£ÄÛøAë/‘„U}â\¹ÓáÁ#¹À¬ö·ê)y”?³ŒTJ *fu‰Œá°,à @V’C”è¬ äA@nˆ¥­E8™‡4gx$Ãy–$ 1ÆËë`>XÒ¨á3oúær`Ù©`–ÐÎ6&¨»•[ÞŠIÂؤ>„)}C“Ì ‚žè+-®3¡¥ܝè“S¸X‰“Ì«ÒrùRÓG¥~1i™Y‰–ú,ӑÃf+d¡0? +`cLj1´ëío¸(‚9Ce*Xœd-" /ÃÑà3EBŸÍ?Q=d “pN/ űMB1U‚q½ rj>”!á'ÀxÅêc ¶J„œ +(]ÄÙ +|á¬ò8QÇY4P}’Íêß® (ÁO)ú`¢(A£hñ“‹˜Æ]Ãç:éÀPV:Aˆ¢Â
+zÅÄA´A¥L`:<t!•pÙ'’n¯UÀááQy¶OL‚9pGŒ!ɽå<E,‡0 ,#¬Oô@ø€‡ÁXmX“é}HëA–6P!6úX™³"2֒%Ñ
+’âUœñËó6[ È$Ë
+"Âÿ³S +QHK0åDn)‡„¡z’Pù€äÏ^MñŸÜÃ4±¸ô_Æ!)»S?ÀÁN˜ÖÏã/¯¸_@UŒ&±"A|5 zõ•$—!‹bȃ 6–Z
+âføëÛª`ÜsóŽyEu´Æƒ3Oè¤a×/QúáCO òåÑEÝË…xÀIˆ¸Â"£/Š¡Îù†‰Œðïf*Õ/?1Ö5‚!°R‚FnA>8™ ¢WlBcכ I~1Q$'9F \nÀwÖQbBÊ$'®f$I" +0XóP*wã%ï(3HMÃã!‚r$A‡1n•–b8"›q€ngáÂGƒ.Ä1¤ŽBæT•]䐇¢™0ç¡$‰£$²b“ւ›(s­äb µ\ø¿†Î–òRÁt@žæýu,ÇC~q¨äߌ/8(ºŸ !Ã+-ÄìÂ57ÑGŸ'œ‡~
+ŒT¸6«rÇT+ˆ®Ô]ª:ŽòÞœËPø[,™\Çi•8p"àŸuÊiZ‘Æ&¸Àã‹c„~tÙÌfŒ™Qh‚ATÁÞ+ãaYäzkX}ìpÓÓʍƵ;ZœôY‚˜áTÃ,Ê‚÷o¬ö +h GŒ4ÏɄ~7–ÉhåÞðS‰DOR8Õ.Ãù4|Ýä_OÞ½ãYÏÕ}?[@R¨ó¦FM Ö0%‰æJ‚Z"†œˆÍ“)í¶X¾&!ƒŽà†.-&ÌÖD%`ÐA“t€Æ­xð"þ¹`AëOðÌOšÃ¥¥r}JËg@¯Ñ¶1H“Qí(8ø1táíÈæj iDޜº! q˜kàCx.kßc "¾zÀÉt[ G‰«;$j4†Ô>;F¬]Ô©-&L˜do„4¥q‹ÛdäŠ)6ÂDÇÀOƒ"äÔHGʹ:]N£…ñòY"s°c꟨0J!0ÛFÞ_çê1Ë2I<©=™‰ç½û³¢—…ý9_݌A,NÒD0f NĞHpM_²ß·ë4ÄÕvKsŒŒØu=’ÝW›ßˆk¤¦¸‡œm7 :1D·ë¼1q" þ¨GÃM1“SDBŒ‡Pݝ9ئœ¼•d"jb'i 
+¡Šc¾ÙkÄàÁ°ìþxäm-™w%˜2:§ØÇð,¹p«W\b[¶²ùK°Ã¨è|8û +gœÚ™÷ö –þW4Ÿ•_8ڝ }+.«É!‰Eÿ8Ù!´*e°Œ+󬜢¶%²ÁHÜ:œ;´Êv);D¬P~(ŠŒd$–² Áx1êâ1~3¼Ü—¬5 9’| 6ÝÌtŸàFŸ“yL«4¨Fñ®
+N~ü„hwƒ +§»@&›Þyd©*ò±óöbÔo&B䉂Q¨ÉÔu,D|F:¨b„l>Y;TàHGâôäãD]´£ûbº¯py~0èw#óvyÍøã×åq=8ÁÌ @wäú2Q§È(€¥:Ò¸&0Tšò!òC]¨7™°.ßeb(ò1Eæbdä\ !b²èØ +”á°Ü¹_ +¡õ¯6ٗ(ԀYØvMWŽs€4ìy!<#—–$"#Í­<Hñ’§èø¨‡‘ñÑ1OÈbǃž{«9´¸缅a”TM6„>X%€J²
+ÉiÈ4s¥Oäß=™ée$ŸÝ<FyHÖ=aöð‹ñXìûÄQ&V„„ixï}’I|¬‚Yȁ[¨Äžx +H¦@øX·¿÷Jða™VÜ­X ðùÏ7÷£2#©9Èþꆈ€G<à¶FüS–†7†ÈòLߟ|¬´FDæŽù'™³6Ô@â›$@„ÔFфCjb$²Ì¶(Øb—’pj¹K”É$á.Kùb" +-ç‚ëoÁÂtÄYNÅâ^Ä3±Ç%`>U¾:ýà$v8€úW«óyrø’#!TÆB5À@3V¤½"¾2§ƒ„ÆMJ»¦#[4I9„mV4aº”]wBdÐäÝ²c1 V";xÌ:ú°h–½P¡zu-åi¸#ñ +¡rzÈ$I y“tp}1ÒW÷ÇøâA +v"V@KXc˜Œb’‡÷Œç¹ï§iþŽØŠY›}^EµüŸáU±ÍꄺyéÇò3,T‡ÈxîÌﲤØGJ‹¬·A2dƁ˜Ÿ¶ + ©
+T„#rUt8ˆ!$š×xÐXž)eÒ3á’Ó
+
+ œ,ûãÁ¢g¯`žŽ˜ (mñ©$k7z–MGŒ,5’1#žG‚X*ëJËRF7B”§1§[Ž3qì +֞³‚sk~þ¨û·xðÏ
+-þ#ò0R'NHyºþ—ÿÌRƒ­ËÿŽCÏ)…/‚Ìñ8¿pç9W—Ö! LÙIvn;qi³‡ˆ°rõ€AdA@_6ËËñ…10ƒµ¸ˆ;¹´Sßy¥ôb0)ÔóšOXÑ^DA%@ñv›,ò³¤èÄÊ3Œþµ+Þñ€pùaÝ£è1¿†±™ƒM3¯ÌFIÕd<X÷yqW‘’[¤“‘lìˆKØwԏË`+XÙE²'³‚&èÛb
+¢Ûvä R +|få4ˆ=ÐøŒ¾³?ã4’÷ˆû¥·ñÿؒ‘$q͟¾bâ«"Î]{ŒÐ$vCíÇ^ê¸ûÏÿÚ óÏ<ãÎ8óÏ<óÏ8âKҌ<óÏ8ÓÉ bcÏ<Ã$àÈЍ<ðÎp‰8C
+8òÀÁH(â,ËñŒãsÏ<ƒË,ñ„1Â<±Ê ‰òŠ$¡MâÏÏ ±ÆðLÃÏ<ñ8‹8#Ï<óÊ0 1G<óÏ<²É4qÏ<óÏ<óÏ<óÏ<óÏ?ÿÄpÿÚ?CÿÄpÿÚ?CÿÄ(!1AQaq‘ 0¡±Ñ@PðÁÿÚ?ÿÑ1Ö ‡ö?À9©֏ÿ" m’gí?æcZÃìL©¢ +DûùJé?1)
+œ_Û)ßþμ¸üòm§Qñ|„¬Š¤ŒÝd¡&0:.Š»šÏõô®™9TŸ¦×>˜Ÿ*ï5Mâ%½†BKEvO¾”ý`5ûx,•r? ƒc8¢~÷0jã/š¾³où@L.¨hׁwV"LÞƒãŽ™µvhéz‹)Ü„Œþ…Ïl/`9±ª€(ag²Š¦@2aM˃U'¼2Â(–µA»s£¡t‚ä11¨ÚöñÒ¹<`TDG +44ìú`€ÚS$ú,ð‘§.JZœ=ÕÞlaΑ¡út”,Q4 +iò #¤ñM >«ümPpʎ㘨íêü´Bc¨`ccN¹&dšXî-¨Ë¡´ù¨F€Ã*5֞±,µƒØՋ”ÞŠQ_®Þ•Tç NE?˜‰€C® |p ñ¦ÂÔ +.1{$—B!¯I»5<ö,v­Þ§õ>ŒƒBY ÍqL§Ði¾W«4D™HPxêŽc`ºJXٝkàȉÜõÕ\ \úDEÿ2UxcW0z¤s´žˆ›‚±3OFµ¯4ë銬úP6ÓÊ.NÎ
+TԐ-Åó–ÅJ@`ªGJYÀÆXº:ÊéAi3ʁ\¹õ–z4ò‚tºbfbÖQR€»‡Âï+€šBµ%U‡Ôיvud‚!­F"3Ñе©Ù±0N’Ç!éb…¨Ð
+A-[ŽY§q#–@Sè䌙&õ‰Bd×6ŒðHþ¤oÿXFˆìDby‚4mRÈl²„F`ÌQËÖ |$¶¢b³¬û¯:­¦™õ»}Å6 +„M@þÄßX*‚‰!l™'I  lËO@4Õ!„(ÍT¯ǀ†AÀ|àŒ¼ŒÁ•Q[?ƒ <Å €/ƒaë$ÈqÀÑH¤ûÕöGNtm–¤i 0Q
+N‘cAR ÈÖ Þ¨ QWJàt>6Ó/OŽËÓE}<5MÅ43?òL43ÕkÉ­\ÁÁLÒVõIãƒãÿp9ICՐ=P÷ +§!¸z=òh¤  +æC*¸sÝsFzU %˜ ÒÌTÛD¤ +#L øˆ ½p¨s\Ï2]xÒ)6a~TÐ8Z( •²¡j†Çƒ—ñïFßq÷©Àƒj‡¼~ +) æT ƒ`Â$;!ßÃãv†žâ¡ ó+M³š–¿¨Vo¨§Ì-p_↋Ÿk’¢åڂªoŒÆ‘‹ :˜Ë’Šm<V†Vq •²hÞ cƒ`;GÂüNhLU„˜áÒ ùeY}ô¡ðÁ™ç€×;2ƒ4# +)Ò!rÖdZ[Ó-Pþ$NóÊÂåõ0¬
+
+Ôüè´'-A0îàˆèCè¾TP4?ìvùlLe·§óËàÅüˆó͟ö‘0ÖbU}¹yÀˆ*·’ZÛ¸.#F2d‹Çr¤€{´¢Ç7S¡‹)*»~]÷$L[ú¦i#£M2î2¹ÚA)@BhAÓO|pnb¬·Ñ×µL>J7ZhùŒ Ò.èÜSù—ö¢r­­¡uY‹ï%¶†B’E¹Sé;]äŒ[{@ÁSî
+úr›[ý,oèduŽ´²¶“u¡çvfD2%–T-ÇÀÐl‚'œ’De; +0Áí<О“ +S•ñhð*¾±ìù¸ë›5ä£ÖõX€»¡×”=Ð Á +„^Þ¦EMkªÁOE•g¥9¥>ñ*ƒ8HÝ°H‘À¢[EÛx]ø%•0<ÑDŠ+BÕ¡Y€|••¯k}Æ9£´‹µ‚O“'Ô×Ýáød„
+j?Ø%?_×dØ7?b?¼&Šsº)ûúdÚ¾ëH¥lò°FŠn&¨Þ€Zùތ7B*U¸²“ÒwBÃ^ހóÝ'†]ñ镺þ 8H*Éódh|8é &ô¨Û¤Æè¸DàpÝöšÅ“ñçhlX BË*ê.š¢+• ºÏtaEbš4 Т°ò‚8uê.+Չi„i©…ÇêÐG$ïª"èLÙ[W$ÎÍ öÊY5Gæú‰õ<ú¸‰øÙpt–vš/í|™®Ôëa—â!ÿOël¤Æ+þ…/ÞZB+§&ÇGí 㗚dà\X“š3£†îôñ¬¤kýâlmM.ð
+«§ã<²R3r ìסK® ¨p¿6­ñ¼†l·f£ÁW¦°fÉâúb…ÕJKI©i4ËÏÖ§ulj dmÆâM.Px¨çS-q¢‡ž ø Çx¢«¹}0œ£>Ä4›øÌ¢F˜ô( +7:ßàìhÄ¥”ª|Õ) +‰ù1£ñüqæ©,éZÀÝ
+‘ú)Õ-cGO'Þ#eàùÞՐ~5"Ïؗ”5M~ƚ!üûlÚ
+U$ ÿ íµ¬®á‘Ähò`Á`„,i˜=‡"Hè¶ðøIÛÒ³`Åù3Ççø¤-] › cQ†Úm¤HGJm3øUZ Ø˜;PQԘ/KDÍVn|&Mݱ!!õÙ$šˆ— +;æ⺯=Ž*Üd 9| 
+ܽn_RQТ9§楂Þ_–aÖw))cÒ +Ç¡Žom¡$t§ˆ`ÖïâŒ|°}˜¬2ÈK3'>Râ*Ä[²%„™jBDhÿŸÕ±—@ü}ÿÇž[Av¾L7'I 54ù)ÇuÂs xn‡O&ˆÝÊÉPˆ$˜#b<[n¬Â²}½‘™‹¼õ«]Áx +Er§rÀ(šÂ»ì«³lÃ}žât\O¨ú1€3#qu®ÞhjŸ±ëXWÌ%ÒÈE’nt—?ÃR;…®C»0ú~Ã&¾²Ý$MÃϖH:5!ˆh؟ 9¬ }ÚXekŠ*!NŠܚMÃxÉX½®e&Gý—
+(EKn¦· 1¯ÿ'õ›#”o¼ø}Ù»
+P±`«žÄ7N¸IËÀcΒ}Ê+CU-œ«
+@’³«bÂM@øÚtðI“ÄÅñ +¶Ò}¾qöQRgsñ<æ™ ! BèͺÍb¼ëàz,}0³œ%t\†„4b +‡C61”øÈܸȴP^W˜Áò¿¤B šAæ_íÒ"…qւĹ#+v†9+®…Û}‰RŽ‰˜(¤M—²o•á¸÷­Ó°ÀŸdQÔ¸>DÓöS.&oœ#©íûþ¨õ¦<û†¡ÄqStÑ!ƍ`ç³=dܘÎĂû‡®NŠ¼D†©h¬z$X“¯ãP,+ ˜cÜs?á(÷¶°¡Ñ_ Œàh;Š +X$QÈÊéI@{a'RÑò ÞÚ…4€õQGK⁌‚ +”½˜nw¡ˆ[4²ÂbbD–I&Á¯ÄµÂ¨Aì¯Û “µä”ïðx2cbÆcû)ûþ®TgÎ;ÿwë>Ý°P¨·æ§¸ØQ¦f$œB‘ˆN£îB’2è> W¬:§lÄVڞƒ¡+·&C¯Ìg/ ‘‰„>R
+-„jF@Y"â‡ÀIMë4þì5ã•À¤¸!êðÐé]èg¸aˆÜ]ÐRވ„WD)t9·ˆ’I¸CÁãbaTö¥Ö»[`,9ŽÐ.L€ôê9©îAÝà½xL*CfT²Fð”2s_â3ìz¢¶ 1‰ìØø p©z³ñI?x$ƒFØgøÿsú´~Š넵iz@Sò­ÇF¨Îòœ7òÔ¨ ="SAl¡LM¸ê‰1 ¸7«oŽ1«Ðb¡ÝíØ&âPZHX, »û·eèî@
+« [ÔÜUoŃ¾Ù`p@ã0=°^÷‘¥·lLþ'[s¼¤ä.ÈÿãFtCÑMDî Ý·ÖæH EX«ž¶Žëék]‘kٔM-ÕÌ?°¢Ž"!Z4ë¦ :ZÊSÏbÀAû3d¬šêiŒÈÇcñáYøF¯µÑ•Ì 't‡ìÔ¢-Q“)ó“ØaÐWÇáÿL}°x&†­P "3(WƆhԄÁ]þR‘Š”~ý^-Òî–d­ÐFŸJ„ÒÔ¶º·/÷Âν%ûÀ(Ð¥v{AT\~Dˆ2cˆåpî÷Aí]Úìbõ
+_3°®ÜäATé8ßVQ÷\…% Ã=‹õ³ùh1£˜†}² +‡BãW‚B¾E¸J' ô)Ê؀"AƒPŽÄïõ¶ðRöږZkxôˆÝEåR~UÏ-èK+ؐû+´Â}‰Pü4Ž#Ó(öé$êù˜âŸ}1±Tiá"µ1¡U:]4^ê (|@Cò± Þ0›`¬ÖUÓ¬`T<¦Œ˜®u^7ÿÆeŠÁj¤ˆìÁ…5TBö
+ݏ£&ªØ¬m¦4† ]°d_åv6žOÅM4µ1Œi^8<ëÃV +ðCMp Òח‰¢ + +¢!)Æ"ôzW±á¸?CIÈkŠ¼6 +߃| y«è¦ÛD/÷á61¨Ri§ÖŸÒ/ÎQVŸ%AO¢Œ <þÁ§"ºÇ‚‹à{”´ñŠ>H|8ŠÎøÀBAw *°fδ1B&œCP e3ªîÁ0;˜£[|4
+s³+è¹!(Œ +Ÿɬ\€]×2m°ã%Ò:* üÉä\!ˆlÔYªÚrí‹F4¶%dåä’Xœt‰ Bl†
+1+“ÙíHø"y¢ÑBÝÇT“õÕY۞kf
+/!КßÃáÆ
+Ó҈·Å¸:¨¼L{»Žj8šY8œ>¿¶–Zšþ1ûÃ\Ñç‘Ñ^‰±ˆjbBz‹4`ƒ/mE û¬—D¨%àAõúeæød
+ÝPQé $Š66Ô$QSÏ#€Øµ^\€²bš/0…-[¡ŒMÍfqëjB Ê𛈷DÒÇDÂØʆ"i$öԌ'BF“ŒVHN„èƒ[‡Cˆêvm¥Ê'BFxUW¥›ŒX2P¬bTÆD2²^–Λm^Îc£%é "g¯Iªúº¾ªûýÞGܞŒmY_nèä,¹íu8i+d¹*E&Ú£ä-À,UÇ^œ­G†bJb
+æ´_,ƒÁÞmÔkÿø¨§"t£Â×+¸"Í3œÒ“¡è©Ú +ˆ¼K²=z5–ÀEDH¶Ø  ¨‚‰8üÇÁFé1Ž3ÀS¢ˆ°Y¥Â\«„X9ýŽYþ Ü€D”Lq„=>5Ýԙ‹wਕŠ­vj²Ã/ ŠÔo“'fÜëu'+{){ðØïÞ`¯ïÆ@Q>ŸÛڀKâïÄ0ݼÿ¬ ŒZIØ`9‰ÿ +Ç?I@Á1•Á¢¤DI1e }… *Ñ,˜£
+ÚÒ^ŊòG
+¯¡8HàÀ–äË̃ÜÐ_Y¼ìÁ)Þ@EäÇ­ ´Uºr‰.W úŽ±3ȔE#VUâ–>0R!”vÍ lz8)_¹Óׂ¥ BÃJ=Êö"M; l›0ùð*ŽºµrÈ äcvQ6ÌBOÉàð›æB苛Pó!ZM ó©c›é† @Fˆin¿ +Ò" +ø*`¶Í u+)6 ßϵŽ`, mâ·Ô bÄU×t] +…y×tèžÐ•)úø¸ ‹¥f‰N엠;‡«¢,ÜGAôص B@¹Êèb— ùâWNÂE¡Þ°pKr ¹Ù +ÀÊË4²j¦Š¯¬Ÿ‘" ñ$bŠXˆ:Db9ꔊÅUyXš3ó5È~Ôxƒ¹ªÇß"6h<5åÝ,7s D0,iêp5×™q*píUÊ~¡Fª¬d )ÌNòD,Ù9ï+¯Ð@K.ÀEˆÌ¸]ºõ +C{–p–+‚÷$Ä=êbдr‰…Pä‡Ñu×øraÐ{& +ùMÑmHüŸ!Ã4F˨Ùôý9¶‹ý5'^¿<hexæ`*†œÙ¬`MÀ+5. ޗˆÅI™ˆ«‰¸´)ˆu™+£!<ÀœGÂÊ·èa¾PŸxªSï+Ûý#ñ1A’óCÛ6nXð?ðLgÃáçû©'—󖐐¾/ûů}¯à)CTì¼ÙöDü_Î €€|þóÿÙ
+endstream
+endobj
+41 0 obj
+<<
+/Filter /FlateDecode
+/Length 16106
+/ColorSpace /DeviceRGB
+/Type /XObject
+/BitsPerComponent 8
+/Height 503
+/Width 695
+/Subtype /Image
+>>
+stream
+xœíÝoˆ]Gšßñ¼ó›ìšÀ®ßˆ@ð ƒcܗËìhX± -¬Ål"F™z!F„4kˆ2¡3 Ü^Zàx%,“ÜYµ™E#ázC¬iyñ( ‚Éf:ýFùƒÔL’"‘(CB:Ïè¬Ï\ß{ϹõÔ©ª§ªÎ÷“"xw[÷Ï9uNýnúsxìîƒ{·ön_Þ½ºygKþÛúãK{ûk;맮œž|ç7fʱ‹ÇoÞ߶þ€ ©‡I68qéä|6˜)äFâîƒ{ç·V–fƒé²w°oý©@D7ïo»t̗SWN[v…w<ࡵš"/býU@wÜ;³qvx<h “"(ÝÃǏ´C]ʅ«Öß xzòìéåÝ«ÁãASŽ]<nýý€[{·ƒ ?è)òÖß(<|ü(ìðž8PxÏxâ@¡î>¸ûùÂ|aõEröäÙÓµõÄñ )›w¶¬¿=XìÖÞíc›$)ç·V¬˜õäÙÓ ¨
+ÃȍmÂtaX™xòìé…«æÙ -lö@L¦0ôVKÀœÕ†þrêÊiëÀxíìK[lžºŠõá`¤6ïlEj܏¯}åk—~køë0v€Äž<{u†?øãß ò:Œ] ¥»îEäx|í+ç¾w"ÈK]Þ½j}´‹cÿðæï~黿ä¥Îlœµ>`ÔïáãG Æ(J<êÕN\:i}ب\²uWÿäkÇ×¾ð­5K¶ —¾û›ßûק¾&ˆ!Í#†én„PCÛr÷Á=ë£@moÕô¥ïþæöð„ „%mk²xДsß;±ú'_ þ²k;ëÖÇ€JÄ^(©«üèÏ¿vÈbS˜ @{û)1´åÜ÷N²Ø¶y`¸›÷·Óǃ¦üèÏ¿qæýߎôâÖÇ€‚=yöôU«„ ñ@BB¼×—og}€(’´¡¶Û=ÿöïDzØfAàÁjB[¾þÞWòÓo…Ú¬aa¹µwÛú0PÃAmùþíßÿ£þ^Ô·`©T !´åk—~ë'?ýÖ×ßû*!€˜BhËýð÷¢YlÊù­ëC@ööO\:i¤_ûÊO~ú­?øã߉ýF¬§ÀR‰·cè)'ÖßØüô¥­ÿµ¨Cÿê½.´>ðdmóΖy6hË7?xC‚ä„4og}ìÈWÃùÓ~ý÷?yYB¹_'$`Åjæžòæ{¿èF"i!Í;îì[Ÿòòðñ£L&2´¥íFxçãW“…]`šùjŠ Kۍ ÿ‘ìM ´ò™È0]N¬¿ñÎǯ6C“u#HÙ¼³e}BÈBë-w…„¦á›¤K]à9iÍÃ@WBhº¶~üBÊnB‡™Muœ M7‚D…ÄoÍỀ1{òìiÎ AÊêõ£‰g>¶…•™£•ÏžM]åy7 ’6vH#ÿ„ 孏^3²Ø9>Ög €ÔŠHm7µÏ^LߍÐë@RùìûÜ_Ún„ÕëG­>ƒõ¹ <Tœ/í®Ð&C €±)%!L>ßZÊûŸ¼lž<{j}҈® „0ݍ`2d±-lߨ^A a2Սx³BÌ8³qVn†_ï«øÃùïó[+—w¯J‘Û£´,֟”•$lìiBÂ[½fûa FnióqâÒI nÞß&6çɳ§EÌehK»+´íEBˆö~øÍ~±“ŽÜ–—Þ™%3¬í¬Kfxøø‘õÇGŸ"ÖC˜.R÷Þÿäåv³óÀnÑF®½žûðõ¶×ýæ,¿R/ÜX%0d¨¸„0ùb7‚ü·ùça#Hc¶w°ÿËæ~êYp3ïLû;® ·ön3q,™ïÜ´ þ¬¿ñîÎ+™ Y$$ÀÝ÷¦o‰Ó¿ãšÙgÞ7êó[+›w¶è^°"­›y ëÚºwîÃ×Í?!ÀÈ̈́¹K·ká¶÷ê?èN]9MZHlæ´Q¤š½óñ«ŸW¼rèF $¹ùÖdzäXûh8È»I <‰ˆMŽpA§+^[å$-˜ž¦¬í¬[ŸO0#­vÇíú…éœögÝù­•›÷·­¿zµä𚷭ÚòÅnû™m9³qÖú|€™®'×í’wñF‘ɯ]ù™Æcˆ°J|Ð0ùb7‚íf +3…`̺B‚Ü¥W¯Î ñ&­Ë}˜Ž…PÊZ7©-Ó#al7k˜¯œÖ§ÌôŒŸÞd'Ô Æžrìâqù0t, Q⌆Éo]ûìÅ|º&„ãÖ߬Ì̈L³¾Í…«¬…ë¡ÐñŠ“/v#¬^?jþy¦ !À˜õ‡„ù‘»GÒüГ›3QA¥Ün„kŸ½˜áÅ¶ZŸX0³¶³¾ôÞ.ÃØþÖKv'?qé$Ã\”ۍ0=DöݝW fŸèþbòÐaº–*·azÜK›5̉^Öç̸„„™9ìV£Ë$*ÜÚ»m}ÀrTG7B&›5ÌëÓ f\BÂä‹ÓØm{†«0¯Žn„L6k˜/Ö§̸‡„™ÎÛùìò±™,Ù*´aæ1VžÝB€s “E æ»ð¬í¬³ÄÍûÛæͨG™Ù"$ÞR]Ëõ3ª0ߙ`>]~DoÞÙ²>Š–N]9mތz”Rº&„#æ&‹;²XDWʽƒ}ëci Ü¦»²Ú¬a¾XŸd0£ + ó æÚry÷ªõáLíUóÃîQfºrș=Åú$€UH˜tt&˜?thË©+§Ç3 Q¾©ù÷(3Q3·Íæ‹õy3!af¡æÜ~ »x|$Ë)”;óqºæHu2ÿHýÅú<€mH˜Ì-¶ŸÛC‡¦Œa4c‰»BÏ?±ÊªÚ,,ÖçÌøý^(/ÇM¹pcÕúèFtkï¶ùö(3ݹՙ…ÅúT€¿03:=ÇM9³q¶Ö…Îo­˜^2Ӎáf +óÅúT€ïçÚó»>eøÐaò|(c}9A¾‘ùõ(Ï»^ȳ†™b}¶ÀŒwH蘙ãÒyõå„Í;[æGÕ£Ì yÍ­ß©«XŸm03d„üÂéyö!W–J\eq¦!ÃN§®b}¶ÀÌÀ°p:dž3ß«É {ûæÓ£ÌT•ÕëGÍ?’c±>á`fà ù™Ý~§›€ sBóÖvÖ͏¤G=™™6›aõè*Ö'Ì _üátÈ<:LªÈ %n =SIŠ˜ùØëf†‡„®é»GòlnÞ߶>êþJ\a¾»)ÏÙU¬Ï9˜ ²`×Æ·>z-Ϝ ßÚúÀ{*qy„™n„Rf>¶Åúœ€¥áwÑ®ŒÙ>z>vñx‰û@•¸<Â|7¹_7ÿTªb}ÚÀR¨¶à‹Üþª¼ÿÉËyæ„SWN[xµ›÷·Í›¶Ì +Y)fæc[¬O;XŠÖä¾fÎÚκõ±×)ny„ùñ*ï|üªù§ÒëÓ–<6‚tlòÿñXЦÒ?2?\Ú2¿vw¶5¡§XŸy°*$,l²]«¹)Ç./e…¥â–bžÙ>{ê)%>–€€.ÜX +Ø.¬^?Zв “ç;EZŸ''.4?Vª2Ÿ³­TˆdÈÊÌó¥kcÎßäGºõIX¢¸¥˜ç»r®=…`䂏™ïÁ˜í² ùψ,n)æù¥3²¿Ú_ F.ÈzJ3 +DÇÆ|Ç­eÞ”õ¬aÑ6âù^-ºb@l1†Íw`ÌyèZ¶Ë5Oq±Ë|7BA{>ÎBÄh&ºF0fÛíœíL‡€#K”EÝùö --ìÅX¥§kcÎ=Ïy.¯TÖ¶óÝeíù8S.ï^µ>ÿ`,àR Óå܇¯—µlÂ$¿½ŸŠÛöq¾©Ä™m!$@ØYm‘$°±{„e†(kÛÇùî£lw w,„ˆ·sP×ƜgÍç3‚±¸mç7ÍvJq•¬Ä[«gá0¶Ì—M8qéd&#ËÚöq¾áÚg/æyŠÝKnŸÀDʶ#ÿAï™t2G+©Ìw#ÈÿÆüS +,„8Œ¼ +q×Æl—MÈa +Ʋ¶}\ó<¹ªb^ + Q'ã÷Œ`Ìö™µùù²¶}œÏ9Ïaq/¶u2ûñw×Ɯ[ÛþŠ,h)f9ƒ›Ÿ¾T_7„ÏÅÞg°gãêõ£y6(† emû8¿¥W¶’T%·ù°`(öÊ~=#³]6ÁjÜZAÛ>.ìFÈö„ª
+!Z Öí)n£U3QÐRÌóÝ9/‚¡*™Lr€$)WâÆô -ż°!ÛS©-¬¤­4ÏÁ»F0f»ñSú΄‚–b®¸aÂ" ðE z¹{v‘ÎvÒ\ÊÆ¢ åv#T°€R[’t(BÔÕ¦—²F0¦ìL(hy„EB™ve~Ò ɞ†ÏwS7%ÛM“u&”²<‚œ¦÷?y¹ânIkiÎ8”"Ù¶ƒ ›˜¦œûðuób¾¤ù])QÄü›:–…cKò x~…™`^²Qsō`LЙæqÏð²0ãe;¤Ä£ÈUû\@‰’mO\ÜÆ؝ ɺq†—ê»níݎz® PÒT%[ɧ¸ŒQ;.ï^5ÿ‚Žgm¾áݝWª '.Œw– t);½»F0æ9Ý>jgB)Cv#äêü
+C GÊás=#ßúèµ sB¤Î„dOybœ¯lWÕö(Ç.òìiŒS ÕHù«¶{cŽ¹#u&È˚5ï“US7û5ÀR)—ôéÁ˜ç“îà ¥Ì|¤p˜vø⤴ŒÁ;J™ùH7 ‘x°}Y#v&”²YÝ€Vú΄‚F0ìL !“B7¨$îL(kcÎº2)t#€Vú΄‚F0éL !“B7xHܙPÖƁ u#lì¡0/ñJ€`¸~ok#È1ë£×ŠÈlޅnð–xY#oÞß.â¨9ó'"ϸ<¿à¥“t#Àk;ëæ +Sž#ýzªåŸ±SCW`Ëí, )Þ1ÐHܨuuqç9‚Q”öx–²áãÂG?«×æv
+¼ >@é:\ûìÅ*G0õ a~é Õ$í‰ôHüС¬ŒŽ
+zÐðÎǯ.bþÙB•¨ÀØ°cO¹pcÕå1£aÒ±0B†ñlHÙ;؏}ÉÀ¨ÜÚ»ò6^ÐƉøR–N’c+y`þ˜óƒìŽ¹wq u•ó[+)[«‚F0NzsBA aá1¯lõ¤‡¥¼j`$Ò?t(hãdQNØ;Ø?uå´ùs?àm¿ÂêIO⇍`lÊÚÎúÝ÷ä·ª¨R:šÒ5„Õ“îRÀëÁxîÃ×͝šJW«&!LX= ⓟɉ:tŒ`¬jÚ¾méz²“á\ï´GHcóÎVÊö«kcM=ᆥ{Ÿîªb«'@2‰:,œ—W٘:«²pa¦=¼íìçАmì©é×núÒ5ê#Û¡¡…i^ʽŠ¤ÁzwçF0/rôªï¢aÚ#˜H¼A$##ÒùœªZ=‰ÝÀJÊm +Áüx.ÜÈ©²i·ön[_%0^)— bcÀÒ5Ì£¦ÄÅ´G°•x­æ®¦­¦ò¥;nUõì†ñŠ`.ñ² ]#kš²ûv=¸‘ÿ½ùÇ UÖvÖ­¯ À/$^6Œ1`eÓÙ¦2‘rلž«׏VÓÌÅ;zcèŠa›ÈÊÚÎzʖ®k#!¡¿tmäTÓ ŽSWN[_ +€/xòìiÊeÁèQº6rªlzÛ4€ŸŸýý¿Ñ”/~kïvÊö®kšMÝæaØš`›ðÐƃH ¡‘|ã‚åÇr5M^ÀÒÕ÷RÓ3Æ+€JšlÐzøøQÊF¡këùÉ\MätmäTÙ±b›p1 +ÒăV⌻Gêþu¤tW¬©×…m`)ÃxÐÈd +Æww^©¦ùÚzv<—©lüã ËL60‰­›÷·S¶€]#k±?äø¬^?ZýLó[+†²•U<hå0‚±¦%½KÏxÅjBÔ±‹ÇÙ¦¦å™ +Z)w‘žt`”ÿ½yfXzÆlÔ4í‘ñŠÐÊ<´ï"ÝÑŽzC‡®ìTÓÀΗN2íˉ‡å0‚±¦ŸÌºÖ³{¼bMÓoíݶ®é`l>dž—w¯¦lÁ8}4º6rªiÚ㙍³ÖuÌ,ÌEăFâ +º~;×4Œß±ôŒW¬iÚ#ãŒSéñ •r:ä¤û)|M-ãÒÒ³KæÆî‘jòãŒP5ñ uêÊé”íãÈG0öläTӓ¶i06õŃFâé]=í5Öë)ϟ¹,N5­Byóþ¶u½€DºâA ¡‘xm¥®ŒÕ4‘=ß½k#§š¾>ãŒDõñ ‘¸3a´#»6rª¬#eï`ߺF@\#‰­”k+MºG0VóP~¾H¸öً!¡ž!R‘¬ë2Ä5¶„pø|m¥Ä-æÂŒµnèг‘SM“;¯ n#Œ­Ä =#Í»d_¶²i›w¶¬k1D1æxÐHߙÐ1‚±ž¾÷ö›öŒW¬æ ˉK'­«0„×Ɠ‰;ºF0V¶¡CÏxŚ¦=Þ}pϺþ@`$„i‰;&#Áس‘Ó [ă…Òw&,ÁXÇtȞE!*›öÈ6 +ªÑƜ-:ºõU0æ¿g¼bMC/ئ@5ˆK¥\€qÒýs»ôM“»7ª¨$5…iê@‚£Ä 0Nºw4(·C^>v×p‹Ê¦=²M€
+TN\:™¸IíÚ±ÐÆ´g#§ª†e2í@áˆäçaúVuóӗê˜$(X>6Ó s$?Ož=Mßât +ó+îwwïxÅRûFæ Ó”ki< !ô;¿µ’¸Ñéú^ֆ]]"¥²˜/L{P(ÂpéŸ8LºåŸûðuó6Ññów +®¨lÚ#»=( !“'ݍlÍkÿxE¦=€!âAX§®œ6ig¯}öâ| »zýhæ9¡#§š¦=²z€â‚“¶À¤ +êÚ)óF¶g#§‡_vº‡„ÃÞÁ¾I3Ôõ“<ç +ú7rªiÚ#«'( !ùÙhÒ•µ¡ƒ€ÕëGÇ0í‘Õ“Ä%†H¼)ät³»pC‡<§Cö/ŒPÓ´Gº”‚„À­½ÛVíQW¾´¹æmåÌçì¯Xʼ §oJ7€B’±zâ ¥ˆé=9eû|įЍ $„”¬ž8Lº—xçãW3É ýãkšöH7€"³}â°pdB&3
+{>^>2T¡@þ!!,Ã'] 9L‡ì¯XÓ´Gº䏄`emgݬyêž]hû¸_>ØÆî‘1L{”²yg˺@‚!«U•ÚæxásÿkŸ½hÕ/ÛÈ©ªi,± s$s&û8´¥«E¶j‹û7rÊmþÅÀ†rFBÈÁæ-Ãvªk×'“^}yÇww^É´G){ûÖµsO„„¨LvŽž.]Û'¥ŸÙ¿‘SMÓ' Y1BV L˜<ÿý¾ùéKæ3 +{ú4ê›ö(emgݺÞÀ$„ÜØ_œtÿ„O6réxŚ¦=6åòîUëz³Hy:³qÖ°ÁêÙ%!Í0€þ…*›öØF-È!!O7ïoÛ¶Y]Ít‚éË6rªjÚãt¡3@VH9;qé¤aƒÕ3³ vÝ?^Ñpц‡ýÒI–eUB $¤gޙгFA¼fº#§ú¦=.>—N^Þ½úðñ#ë:`¤HE0ïLèÚV)ÒtÈ¥9少DÊr~kE²"Ë0HŒ„Pù9iÛHõt&ʁ¸t¼beÓ˱‹Ç/ÜX½µwÛº>º
+bޙÐ51øú¥9¥_Í)·BZ ¡,yŒLX<H àð€¥ #T9íÑ»DBH(Žmg¤{קÍO_
+Õp/ÛÈ©Úi i@@$„I`Ûõ,Ô,mw×ïßÈ©îiAJ“î>¸g][”J› ù°]€qÒ·vA€ÍšûFÉ´ÇPåÄ¥“k;ël( @‹P®‡7=Ý£
+Ž'\º‘Óئ=†*§®œÞ¼³Åb \Jg>²g~¢w#î2^qœÓ–f±ëú k„„Ò=yöÔ|:dז
+Þ?ö—.ŒÀ´ÇPåØÅã<†° ¡wܳmhzÚtßûK7rbÚcŒrêÊi:L#$TÃö¡CϲÉÓ!Ï}ø:Ó­JÓ±Àˆ 3ù%hظô,h šƒ°t#'¦=¦)ç·V˜8 Œ!¡2òëO~Zµ)Òp¯^?:p:ä҅˜ö˜¸°E50Z$„úØNèép|@°t¼"ÓMŠ„ÏË»WÙtºje»§Cϼťû҅˜öh[š¨Àp`  »pcÕªééL蟴è²0Ó3)RÁˆ
+@Ý u3Ì =k)÷ô,}ÐdgJÀBTjEB«œÐ³ëS׈—…‚ìE ^«Ô‡0V‹'¨:$!,]!àÞӔà…a@e ãqóþvúy‘==óÍ}Ï L{,¨H5c²$P¿„@H(×ÞÁ~úu–zÆL·ø. #0í± râÒI–`ŠFH§Ë»WSv)¸t&¸<h`Úc‰åÌÆYÆ4…"$Œ–Ü·SŽfìyŽÐ¬­ä0£i*%"$ŒœÜ·åî`wéž]Ÿ¶~ü‚KB`Úc酧@q hܼ¿}fãlÜ6ÂaPbOaÚcåüÖ
+]
+@) ˜&?ôä©upYG±«0í±¦ÂÜ „̋7\aéÖÏL{Oa@#9ï„@HƒHQÁ£3iµ–cßÚ»m]Ó,FHÀRÁ£BÏBÍ]…„Pwa”'BIT¸´BÏBÍóec÷ˆ„rB݅‰@† PiæK
+ ×>{ñ_•tA`¨¸Hí²®ã~iHH 'ŒV¨à¶0Bç…s¾NZ¨²œÙ8Ë£ „ qóþ¶÷*L.ûA»Ì‹\½~”´PY‘ü¹w°o]»€÷Ò
+C:xQ}a!À!¡<yötóΖãþ’ÒŽ?»è³`#ÆS.ÜXµ®×À¨ÜÃǏ$-œßZYø$Bî ×ÿÞ¿ü³# Û÷7ß{£Y•±Y¡ÙÒû©ÄÆîF”^$v2D°20$ÐOnïwÜkK󠹧M_ؚ7­¼ä‡w>~U;'búaD“@ ÅI› Q¬XWS.¿ú—·ƒ»ÞÝy…‡Å†2V H©§ùVíÎ0¼{‡Å†2éñÄÉô·ÚލõÀîFTÈ @b„$ÓÛX¿0¼žî^¦Ÿ‡U–µuëŠ Œ!i,k _۔4 +ý[½¶±»x&#Ê-L’È Xji£üþ'/GjPš†þ›xv/l~úRû0¼q¤´…œ$CH@l†!aº ë^xáݝWXÔ1ŸBNÒ 3Q¹4ÁiBB[v/HƐ¤AZ0/ä Bâqlv¥±¶jhš¶~õúQîF˜r9‘8¶¶ÿèڗ̛›¶{áݝWôÝ <Œ0+ä 6BbpodÿáÖ5ÓeH÷‚üæQ&.ä ª !œ€iî­ªüñÅí`ÞÐ,,ÓÝ Úm+7?}Ib#Òr!a©BÂåÝ«æ­ÌÒÒ¬Ô$í¾v!èkŸ½ÈÈ…œÄCgR%„ÃBBB[څ =ºxµ°n3 !¡hÂai!aºxw/ð0"R!'‘0œª¡lÿÕÝ÷Ì—Å{Ÿ©f‡)F,ì+ +Ä*$ÆÌ#!¹«›·,¡Êm¬%`ð0bx9vñ89ˆ€!üBüe‰Q¼»š‡¤ïrêÊé'Ϟ¦¿€ºÑ™oC‚8³qÖ¼e‰W¼‚æa„w‘•øƀœ~C¦•;vQ[¼÷™âa„¶0)Ž­á ᰊ±‹Úâݽ 邇Ž…É@pä¸ ’Ç.7oS¬ŠßBÐÍÈf%aaa#9ŽB%±¶³nÞ¦˜ï}¦äïy±ø^:É F ¬€!œP±€ á°®‰aZ·Ï»TS)y1_Îo­Ä¾€±!' _؄Ш{Žƒwñ[zúa„ùW0/›w¶â] À8‘Ð%àP„i#¾¨-^ A¿ÀSVbB È Õˆ”ç·VÌ[“"ŠßJM»GÞúèµq¦…SWNǸ€1#$`FԄ >~4æiÅo!hùË>ŒXÛY~E#GN@+vBhlÞÙ2oM
+-^+5ëaÄÝ÷^xè€Fš„иpcÕ¼5)ºø­Ô$Ñ¢úy”̈‚#' eBr?uå´yƒRGñX©©ÙaªÖ‡<t‚#'ŒYâ„Ð '/+5ɟUù0‚™@pÁsQ¡& ¡!9•"©”5=Œ`¦9al B‹åš£–/®Ô¤xQzZ`y% rÂx䐷ön3/2vñ˜JÙ,êXèéQŒ`b 'ŒA> ¡!÷sº’©”ïòrq#.ÜXMPu"'Ô-·„Ðzøø³#S©”eí0ÅF rB­²M-¢‚IÑN¥œÞa*ÛÀpfã¬IÆ FN *ØÊ?!´ˆ
+Vez*¥ã®”ò—Ù>Œ` +F rB5TñÀ<!´$*¬í¬3¬Ñªh§Rf¸ÃÓ!¨È (4!´ž<{zy÷ê‰K'Í[œÑí®”Yí0uóþ¶ujFN(Zé ašÜíY§Ñ¶èÇ:Úï0%ñҺ敋”ˆ
+±Õ”ZwÜ;¿µbÞ\R´c +u¤3ˆœPœúâÁ4†+äS´cÓï0ÅÈ x9¨V•]x‘UQuL¹ÃÓ€È ùUBhíì_¸±JÇB>¥ëèø0"ö¢Ž¬™$CTÈÖ8Bëɳ§t,äV´cãí0õðñ#ë
+Œ9!C#OÓöö±aiÇ::>Œ»¨£T ëŠ ŒHԜ@TP!t¹y›©íÖAudkH =r‚9ÂR?Ú¼³ÅrL–é=¬]F ÜaŠ¹@zt)XÑƃq&„iÍcÒBžEµð‚ߢŽ _LÄÎ D…y$„!níÝf6D¶Eù0B·¨#Ã+ä„dˆ¡HZ`ÐB¶ÅãaÄÒ¡ ›w¶¬+]F¸ý"1ºb£!†fî$i!碚Ñ3’Õ܁aˆ¨ !6ÒBþEµ%åÂy”#âÀ9HFUQ‰‰IZ`ÜBæezÏ—‡íЅ÷Þ±®_6¸ý"7D… H¶š´Àœˆœ‹jèÂÕý¶u2Àíy"' A<ÈÊÞÁþåÝ«,ûœsqº°±û·­«RRÜ~‘?¢‚B¶>~Ô ]àaD¶¥'-¼óñ«’÷¬+Q"Ü{Qª«#âAAî>¸·¶³N÷B¶¥M [?~¡¹XÞ|,½˜æ–[͍9 Æöóˆ$„L4Ý Œu̶HTxë£×š™#Y-!eH(÷®‹ Qo"Tcï`_š!žGäYF²ÓSâ„PÖÍE ö¶è@¨ØÝ÷.ï^=³qÖ¼q¤4EΈu¥ˆ.}B(âN‹¼FEš'z ‹vF#Œð6‹
+Œ°Fnï`¿ÃÀ
+ iÊ©+§G2©Á0!äsƒE}FU“I˜Ö,ðØ<• “!x‘C*ÇÖú$§cÌﮨ[õõ™x€¥š‰k;ëŒdÆ0L±ež H ÊZM<€É m?Ï&Ëù­•‘ ?˜až H¦šºM<@@ò»¸Ùt5ðxb&ȑó>æ œ€ôÊ­á~ñ€„•664½ +c[þQ¾²|ñ1Llta °RV='À–üšn“ƒü¾®¦ÏA¾…|—µõ›÷·G2[Áy6 $À\þžx€œIÃ*áAZX MÏC¶ò©ä³]¸±*ŸS>°|ìQ +Aô`ž + ÈDž5Ÿx€Ò5O.šÒ¤ˆ¦4}mñÓ/"E^¶}‹Í;[ÍûÒ?àÍ<•|.âsæـ€<Y]OÞÿÆ¿Ùþ;Ä90Ï„äÌäZøé?þ•Ï>øõ?ýÓ_âCæـ€üY]ÿÎ!-X53Ï„$‡´ð£¿uïæßµ>FÁ<P.#a~»ã¾‡r™_5\8b3¿Ëq¯CéÌ/® a™ßӸš2æ—€!Ìo_ÜÖP½ÿý/ßÿçó'ÿìWÿâŸüu®,ù뿁˜ßǸ•¡ó“wþÕ¯ínýúýï’dGu»àøqYéHÒŸý‹_û·ßù•Ò—0f~wnY€;ï]š¾…¿ø§¿jž¸ú€ñrà6ôóÎ ×H4\‰@ł_ïܗ€y¡‚AÏúÉæـ ¨CìK›{0xl¬`ž +¸H²¤¼Š¹ù`œÂl0Ï<pÙy²½Z¹ÕÀQÀ1±à‘ êq0\ŀ¹¬®Jî-p”¸¹ô)˜|SóHÀu +¤‘ùÕǝZ¶MjÔ$g2Ï\é@@Å]bÜ7à-e“=žTÐÅ< pí*¸Ž¸K óV¾¦HÐÏ<pCªøJán€à̓@y`)óÀc6¶kaT_†ˆ‘˜·þÜ=P+j{klߨ’y»?ò)ŠF}v4¶ï ÔʼÑçN‹<QW`šy[Ï +éQ ÀƒyÏ­Q» *ó–;<zPa æ­9Â¨˜Ÿw* a~÷έXŸ˜Ÿ#N"X1¿Ã—[¬OŽùáça€Ê˜·J[¬¯Àr捥úb]ǁ™·,”âŠu3o‰(æźŠdÞ~QBëªó†Ò뺀ó´èb}öȎyëLs¨Ãä;¿!åÌÆY—òå‹Çåï>¸gý©@tMHPB€üýüÿþü¿<ý¯?{ò³¿üŸÿýɳ§ÿ×ãGÿãà?ü·ÿøŸþò?ÿïÿósëO” >ÿOþ€ÁN¬ŸÔ†„‡Yjݙ³Ú`ý‘@
+„°!Pº»î-l°çîE- ëßÚ»m}̜Rjö›—O«ê˜ü½ê5ÿêòîU©‡Œ!P½'ϞzŒÃ7,¥Ü™ )yí ErìÛ;ë{ûÖb1o÷«lI )]øÁªmµ”ÓÍ VU2o÷«lI/ï^Õ¶2Ö¹`Ú£/*”ÒÓŽÌo­îåÄúIë£åŠR&!Aʗ/¿yÛúxãɳ§rîä Iý—ÿ¶þ80¿¯ åšBNXªíA³þD3¿£ªŠõÑrEHHÉ;$´ÓÞÞY—‘ÿ¿ùÉ ñÌǃ².m`lÌÛ}B!a ®™¼ýEÎQÏkÞÚ»=p<$³ftŃÉóÇ4֟Àª)'ÖOº,eàÒ .Ü+Ù¥”20ŒRŒÐúæݱðæåÓñ¿zzâõÈY{wmV¡ùöµ¹–¥Ü¼¿-ÿ')ý¿†ünÎK?RSšO"¿æ¦{€KYL‰R¼ÐðîRࡃ¤¬·w֗þ
+ þy8¢8FHèGOæÅ Bò³Ç[Œ¹3A~_¸‡+ê?P¥ô!¡òRuLäזõG.X‚ qÚý¡ØtçÈmߋê\(!¡‹öÈp“¯j×Hܼ³åñ.ò¯"}ëœirPÿ*yܜG2Œ™X‚àי0ΎtB€C¯ý¡FrÏ$$$– $z`,h°€ „„… ‰¥ Ú¡&M‰ðus÷öκê1 +¨!a!BBbiBÂÞÁ>!Á…vv{hµ"$,DHH,MHð{£Ðßµ„ +BÂB„„Ä Y!$h"$$–,$x,Eú»€ AHXˆX²ðæåӄ„¥ „„… ‰y„¿5ɵï2Ε™ „„… ‰y„„4otá«A¿h …ùåØì/™`—(BBbiB‚ÇH“}K¥†7U}ànÞ´ J€ZešÍ¦åíz"ŸX?) üNžCH–BîÕoï¬Ëwìn×lÞl^Ê› ¥ Ú¶ïˏ'h¦]ªº|ùƒ ?Xݼ³•`Ï)mýýyê»äÊ©üöµ•…_G¾‹üŸÒœk[͙]Zÿ›“+@ŽIšDZ_•ó¦½3Ç r´ývõ•"ÿ0TZ0 r[šé1¿©´r•X]=¾¬Gó­]–9곆!U]2ƒ|¶x½™„„|®…öeåØSڏÚsCó®ZõZ^sø‚–mµéÿüÓ_!j[,UW*°ß™mŠ\;rX‚gø|ª\>´Ç!RHs-—•ßf¾1®©ô!AŽ€T°!WÍt‘ °¬ºêqê=nbÚw‰q VõÉóÛ¾¼š÷­r¦ž·ˆvHÿßk?^†×‚öMÞäh{)ù‡C2¡Ç;Æ Måuf›ä·a†U.Ú¯#$ÈÙ uϜþœCN“ü[ÕÛ +‰%a[¶È ´Í±vG¡‰þ&¦}ÖãRŒ=ü6éÖ÷ϓíµ0pÇ+ù^›‹Íù‘›æó«Îš 9Ú +ATÅû·a¶U.Úï6$H[ìQ{ÝOӐ¶[õ^Þ©;R«Ñ–Rç' ªŸ òÓ8lOæ_‘ŽE^_{L„÷9¤9_ CBÂÞÁ¾Çêa¿…mHˆñ3paÑF…œ«\>´ß:`HkÇýÉû6CVníݖDêþ½s‚ê°x„ù'o¥WÔØ!A5§OjWØcªwÿöµùûfvƒÔ^9}ªû˜ª$“ž„ü¯mýlo;ª»\ŒSì÷ù]Κ‹¨?»ŠËӇü«\>´_9THpïû•Kl¾9_yür‚ê°hC‚vúÀâÝK™LԐ zÐ<!¸÷3Ë]«ë­U¿ÅÜïK9„„"®í¦rÆHÓ¯ïóËOr£4C>¤öë7cÿf®ùåéئ»|ò"ª\>´ß7HH¤çøvýwl÷œà1²EuXܯY‰7飵ßH)^H/î~§
+›ä\»ÿZYÚ²«Zǜ*$4sÁš2ÿ!»Ft-x¬.%ß:^W¶¶×~þá÷yí —‘™.×rÿ‹Tåò¡ý²Ã+ê^×ßÈw|)ù3í8FÕaq îC"§o¼ ェä§A<‘B‚êÞ(<ì˜d÷+éò‚1rÂBÚðà7~£ kAÛÈJ£»7[ûð]õâïóڄà>¨?x/=³U¹|h¿ìÀÊ#§Ø}ø–Ë]ν6j?¹ê°¨zÿzꛜ·wÖ»^Mjø‘6Ãg†Æ<$hïÒÁç2¸wðª–lÚ¼³åþ¥¼ÇW'['¡”kAÛÈvÕ½é¦gà@VÕ®")C‚{GqûET!³§q9­¥T¹|h¿éÀ šãò³Î½3a¢ìóQUHXøS¬ãÇkæìxÔҜ÷*šU»›q­ªÛoŒIͪ_RÚ»‡êÛù==IJ¹>Å*ggÈÔH÷“›,$h‡aø-jÚU?]^ª”*—í7Tw÷7r¿ÊT}>ªÃ2d¤½Ç̵C}‡ÞDyWI,ýƒÂfÑÂkž¨F{ôCªN½ß­I{ôx‹…o”íµà÷í)èo¼TÃf¦‹{÷—öóû=«R +ÂiŠ÷ÃúùÛ¾ãg.¥ÊåCû5‡„UCàþóJÕ»å^T‡EUͦÒTlïùø5Ûõ=’…„&ÄXÐÕï»øuBªÚ·ÐÎõø
+R®ïžŽ}é~¯ï~7N³„¬v¹¤!­ÉüèǼQJ•ËG¼j9C[KÝâÉYvY÷„¬ú´Ú,Úü«áO©´m«ã¹ô҄¹c7»±Ä‹ôi†ü©ú¨cÓø²ˆkÁ;$Ä{"ДHŸß#$xl±:påç™^ ÷XD•Ë‡ö´zßT{ÙhokîOiÝõD­íÍv~ª²öÚÏö¹XúÇ +Rgb쑤0à}ÓЎ +‹:No`H(âZ𠪦V5¼ª-Ž?¦„í%<|Àÿô ^Õ¥TD•Ë‡¶NúÝT?ö'ú'bªúéØ.¨>pšÝKҎ‘¶úœýL&/O>¿YÉ#ÈÓmÛí}§Ò^PQÇé%Û>~©xׂ_HЎxñØÝÀñÎ;$xŸáÍôt߅UO~·ß~Ú3ëwCPÍÛò¨¢ª{šãúW1.Õ´££­>g?ÐД ²h·~òÔC{wR½W¡!!ÞµàÑztyôØ;VÚØ!Áãú +2f¸}5«;p·ß~Ú3ëwCÐÞ<£®rìø¢~à€’ +DÊã&ÓN9o6;ì7|ٓž…‘—Òî:ð\ ܗ°_¡!!Þµà¬ï~Z£†š_ôâBÓê¸ýöӞ\¿‚ö]´7jU7¯ã°Õ&$ v1¥f±w¿Åj¼7Õv— +l[=–÷‰ô℄…Åï—r¤ì5$hkþ¤ŠmuÜ~ûiO®Ç +ÁãúŠýÁ !a HË2Ksï×±àñèAûnûâ1>ҋç³M>!Á{Z¨vXB!ÁcêB'ΫãöÛO{r=B‚GÎÔ¾…öpi_‚¿`$Ú+Ôêsö‹·Á“Ç/~÷1íë\Ú#{»×RÕ­/øŠÖÞâ] Ú£íÝ»¢mtÌC‚dzUUÌ\·ß~ړëQù=ÖÕ¾E𐠭ùVër'`iDÝ*Ú;'¸ŸÖ¨Mv¨wt=ªÓ‘IHˆz-h¶wwzq!A;£G{äsVÍí·Ÿö;z„íýÿˏK•VmE]zN³@Ùc´ÃAs®¥QC¡ï$t÷ÍC=:ÿӇ÷ZZVHHp-h5xí:~Žo¤ýüîë‡D]*[•Ý~ûi¿£GH¸ÙYŒRnHÆNn#C& ¦ùœZ±C¡ïrëŽÞ£»làÊ !Áýâ-"$$¾T¯ì}LâÝy"]\Ç?Ÿ‘®ZµÞ~û%8¿ÞÇ3^).$ììË[YL êçô– $úæU—7òøü>‡iŠÇ³à:B‚Õµ zåñ„kª¸Pýí·_‚ó;üÀ/E„ù ›w¶¾}meÈÄÿRj©Çè± Ç3ĉ۪8çÈç0}‘öË + 9\ ªWö~–T\Hð8ìE„„ª\&œß€G8TÉ6$4üU·hmò9ƒKóK\»šq[–ŽLðxMÏ?ðMË
+ ¹] ªW&$©‡‰åVå2‘àüzÔ¢Ø%· m¼‚¶ãNþþæýíkD¤—¬»>ÒtH×ôûü­Z7d{-¨^™¬Â —m•Ë„öü& 9äÌd!AÞH;JV²ë…¬¶íuÔÒd!Ác‰Ãí ß.êÀEÕ +3Ô=?ókAõÊÞ!A;Q‹0DæU.û‰x¬¹íQ‹"|W!A⫶e”¿ŸŸ¹_G-MöLßoXÂÒj¯}ÁáI8jHˆ]ùgq-¨^Ù;$hgº¯}éók+a
+3\U.~ûŸjß%Á[;$h›*‰»]ãôꨥÉ*‰__úvÚW«)$ \ð¡”k!Í1ц„H­yԐà±?fX¥T¹ºD + ª9õò»a“:ji²J’IH¾ ^!¡ k!Í1CH°} \P•ËAšàѓ<p™áâ…m]:ѯŽZêQývÙóxÄærÜ\G3<–Ã5~=£¬k!Í1).$xÜÞ +CBYU.iBBšurŠTw—*ªý¨ÙnãîQ½+‰Ç{-­ö ®£Ëáš?¿öþü9\ i*gq!Áãöî½Eæ@ÅU¹¤ k\[í—ÔŠ´¿aclݒÃ̑…<ê¡ß}ØoÓº¥ÕÞcf¥Ç‡Ÿu·ˆØ'¢¸k!ÞÞ3"…„xŸßcAòá•ßCqU.iB‚ÇÝÌ|‡ú!A»F‡ã3—:j©G=ô ‘Æ$¤_–9ênª—Õî©}X൐lNt¤ïóûMJßW\\•ËDšu„U$Á¯)m’w\G-MvŸñ«óK;HÓ祉%ª•ÂVþ%^ „„P¯Ü`9D‰U.iB‚ßZ¸¶cµ£Â–v}hó¶ûETG-õ¨!~¬ßŸ¥ÇÍcáÀ 0¦êS½rìù¿9\ نÇL¢~~m͟$ŸYb•ËDšpèõÄÖû¡^ÚKuéÙ×þîsoAꨥõЯ‘õ{„º´…õHÂC~Ly¼êp©^YÛ –x-drøü£Î],±Êe"YHðøùf»àFð ýÝç^KoÞß® –zÔC¿à·[´ËHZm2ðF{åjïɪ×6ˆ%^ „„ªÃޖ”£ÓK¬r™Hü†”>qâdÕGͶ–zTƒêUUQ›„‡œ mýÔՋkįBB¿Ê)ïE%V¹L$ ‡^ÿ +—ø6 î+©úϳ­¥õÐ#$øíîäxÐ<’°ú0}N{5ižR½xÔ’ɵmHÈaL¡ïS¼dsJ¬r™H<º¤Üç¡gÜ/Uÿy¶µ4ÁFê’ÇêpM¿¨öVéw“Ôþp“O¥}‹°•ȋgr-$ ‘žžÇþü~}ÅÉnG%V¹LxÌÛRÀƒ­F&˜‡džI{mæYKýª¶úu#¨IÓ~¿a Ú¼í±~µ*MÅ 9\ „„¥ü:Ò̅,±Êe"qHð˜&æ~¾–’²c×Üa!Áñן¶á˳–& Þk(i«Ÿj¤·ß2­ª·ðË!Ú¦Jõâ%^ „„¥¼×2 +òÐAÎ~τ¸«\&‡„C¯‘ Ãk‘ÔÞvä¹ã¯ªàg_;ôÝåQ‹ÇU™g-$ú=hP-@Ԑ“¢z/mQtïvÚú¯ê¬(ñZÐöÞx<âiD
+ i>¿_ƒâ¸KB¶£ëuJ¬r™H´·Ð¶x÷'ÈwlßÑý‡[ðUv=úºûJöX}béç45$x'„‰ó¨°ªiÚ¢êÔõ¾TµmŠêò,ñZÞµØ%RHHöùýNÄÄ÷¹ƒ4(íë¹½—Xå2‘>$úNª<·î?‹ä/åfFA¸×Ãà!Á¯ìº÷N÷h‹ßËQÅ rƽç\ª¦Ü½Zª¦p™”Õóý®…d¬vàVn!Áûg`ó¦î‹Üá§öԐÃ2«\&LB¡ï²Éó^ þ¡Rsníݖ[ôŠêž1bì×㷌Ϸ¯­L_ùo9ޗá$ùªé.‚‡„…QUn1¦ú¡áØݪzMïîîV¼Eê ¼’5²Ú¯à¸2m²Ï8¬ï®yk¹x{S©i ïðý·÷âª\&üFY¾Ào(l[ä4ɇ—ó%FŠ¼šüýÕ@5Q"FHðîBYZUõßpbi¿ý¤Å”ÓԖ¦&Èx‡ü°5<lNHœõÕU¾Âœ wûùÏSܵmHp¬«)CÂáàœÐ9•ò1¤òLßä»þxi/®ÊeÂ0$Î Ú¢Ú "Òο~¼§HÃ!UNÛÈJ­–K&Ÿºê×££È%pµX9Âîg\ÞZŽÃ“"oâ„ÐðøñÕÜØÛ[zû±ç_¼¬kA{³òޛ@û­ïÆÚÏï7ïfZœà^\nïeU¹L؆„Ãhénx†<°ëª¢Í+éZ7\Ù²‘IH“㉡êášT¹ŸËmG‚ùUU {*½çÎ—ù/ëZˆtC~ÀÏxšÏ?cȳ{UqŒdeU¹L˜‡„Ãç3vE’××S÷8éŽíK¨€=]E‡ÝÏÍk©yHh²}¼/(ggÈmÄñ+ÄXäÖ{øÐLYøâ] „o±¯nUï_AU.~7®€ý™­™qªëß)ðx¯ÓöÛ"—ü|o•÷Åh^K?xš.r±§Ùn~0vØJ¯÷2ÈÙézñR®…4¬Çrsù‡„ÃÏÇ¥ ¯E Ͼ¶÷¯”*— ïCãÃ4wÑP?¸šn[ïÞcwTýŽ“/ëwÕÈ÷ê«ýÝ»}qüYáÒ"Ù@µg(Á+yšÓ7äÇ`=,äZðøx;ÇÙñ'[šÏ߯‰
+¡r²j¾äŒ"ª\&¦øÒÒ>ˆ½lTÏFÇÊ3|Xˆûa‘Ò|T¿‹Ýëªc쑣çÒÉßHmϤ~¶!¡™´â^ÜTÉûڊ\’r|røÖ+¹|—ÄcŸ¤®j³\ó!Ý_?çkÁ½~¶ïè½Ki3øÓñïÆi>¿‹fz²Ô +¿Êߜú Ÿ-ó*ÍÀ­þ™/“ϧÂåsÿ×jÖvûC›7fZ79Ú¯&—¡ü«™êÚÜ"ä@¥ÿ.r.¤’wýéJބœd»ë.$7Éf,å:üCr-ŒÊtåïi[Û)31*?U®JRUJ €#i‹›Ù +ÔsŒT{Û0 ôûÿϬÊË
+endstream
+endobj
+42 0 obj
+<<
+/F2 43 0 R
+/F3 47 0 R
+/F1 51 0 R
+/F4 55 0 R
+/F5 59 0 R
+>>
+endobj
+43 0 obj
+<<
+/FirstChar 0
+/Widths [ 365 610 556 222 556 277 666 556 277 556 222 500 556 666 333 556 833 556 277 500 277 556 556 556 556 556 556 277 666 556 556 556 833 777 722 666 556 722 277 277 556 556 556 1015 333 556 556 666 277 556 722 556 556 777 666 610 666 500 190 722 722 666 777 333 333 222 500 556 556 556 722 556 222 500 500 500 277 556 583 548 277 500 556 583 277 277 722 583 556 610 333 556 277 666 354 583 556 943 666 500 666 399 943 583 556 ]
+/Type /Font
+/BaseFont /CAAAAA+LiberationSans
+/LastChar 104
+/Subtype /TrueType
+/FontDescriptor 44 0 R
+/ToUnicode 46 0 R
+>>
+endobj
+44 0 obj
+<<
+/FontBBox [ -203 -303 1049 910 ]
+/FontName /CAAAAA+LiberationSans
+/FontFile2 45 0 R
+/Descent -211
+/Flags 4
+/Type /FontDescriptor
+/StemV 80
+/Ascent 905
+/ItalicAngle 0
+/CapHeight 910
+>>
+endobj
+45 0 obj
+<<
+/Length 23879
+/Filter /FlateDecode
+/Length1 34560
+>>
+stream
+xœÌ½y|Tå½8ü|Ÿçœ™9³dÎlI&“ÉÌd²™I2ÙÃ$'ebX&@  [ ²¨Y”€A„
+Z´îEĪ%m­½V­ÜjÛëµ-ôÖös­R¹½Ö_«dò>Ï9 ´}¼¼Ãä̳¯ßç»?‡þ +]HAÒòµK{\xé„Ð;yù¦~÷ç5ÿã¡áKaëŠÞ•k3‚ý!òBj~åš-+>»HzÒ^Eh]üª®¥üÌ?LCh‹•¶Q¸Š&œÞ©¦ñY4ž²jmÿæ³ÿp‚Æi]³~ùRݧÿ‡¶·-‡æÿ×Ú¥›{ŸSí$ÝÆê»×-]Ûµó…ûi<Ÿö÷ï½ë7öBYãíÒ°üÞ +]½…Kþ­œÆ3èø~IӀþc= +ªXŽW©5‚V§7ÄE“Åj‹‹·'8I.·'ٛ’š–ž‘™å›–íääæó ‹ŠK˦‡Ê+¤Ê3«ªk÷ÔϚ=gn¤a~cӂ…Í-­‹·-iïXºly׊•«º{V¯Y»n=úÿ뇇ÝÎï@6´E~ÞðáJ‘݊Ðøg,vý]øÿí(4ÊÏô::…Nܐu7ºƒ>Ÿ½!í<ú1zFE÷ü‹f_AOO„¢#è®Z®í¤íœ¤ý_ÿtÐÔ-è!Úó9ô=
+(ɤ½®žÈý½õíMÁïá-t?z’–¼½LŸG)ämÃE÷ãyhþ5فîD{éC7:@Ëw “°-¡©Êg êB7ƒËF£­ô^ûð;Æÿ®~Ž|/mçêF}Sj< ÿ`?ÄEÇþ<zINÛ1™©“|ã1v’ïC+éßRøŽóR‰ªx“TÝÒÜÔ8^CdîœÙ³êo© ×ÖTW͜Q)U”‡¦—•–äæüÙÓ2ÒÓRS¼ÉW¼Õ$c :­ Q«xŽ`@Óª½5Ž.Íg³¸w)MX:%¡cÄM“jn,3â¹o,)ђ+n*))%¥k%AtOGÓ³§¹«½î‘w«¼îsÐÚÐLÃ÷Ty[Ü#—åðl9̥ɍx<´†»:~U•{:ÜÕ#5›V +UwTÑöNë´3½3»´ÙÓÐi­Žu44’áí= +å pFuéiŒ4ÖíI­^Ú9ih®®rx<-ÙÓêFb¼Urš)79¢š9¢–›tw³¡£}îÓÓF‡öŸѲŸ¾ÓÛ¹tqóYJë‘ê¡¡»FL¾‘LoÕHæ֏ãéÌ»F¦y«ªG|¬Õúy×ú©¿Þ%Œð©¢×=ô7D§ã½üٍ)K'RT©âß ÖÐåªñºk†:†–ž\æu‹Þ¡ÓzýPo5]ai¦µÎŸc¤fˈر
+J'&[3¯~ÄÒ°¨y§Ö¸W-¥)ô[áõ;<¦–É2‘–èBÐå kêñ°‰ï;'¡e422ØЬÄÝh™ã$|-#¸ƒåŒNæؚXÎàdεê^º›õó›‡F¸ÔºNo5]ã}KG—Qxêa[áGb¾tx¼Cf“»$Ð"—uÓQÕuv»Gø4º,´ÖÔ
+RX•!QŽÄ|©ü\vÐÒLfw‰—6ÃÚ©öVwL|7­Š§ +¸³§„}ÊÖ76HU4 -Ø£êÓ9ZciÝ¢î*yûFÞÞ«wƵýdêîžß,W™¨6b9‚:–OÔ TW±žÝÕCUÊX[ކæWPpüÒé|·ãÅ ÊG-U¬pìL
+WiÕC͝+F\ŽNzÒV¸›ž©…np‹·¹«…]¡ÌK´;ÜãžÙØ\?ß[ßÐÚ\<1%ƒ5Ç¥VßԌ·Ù¡4CAnD“ªq7ci¡Ešà®¡ïŒéô9¢NÕÐ?‘.¸œÊ@uÆtw38Ðdi:Œ‘LwuWÕD9¿¡QžÓÌðdk*¥íÌ ;<-å“= +Ól÷DÇ´††-jx2‹¤RL@Ó0mFNbkÏ`ÞÝìíò¶xW¹G¤H3›[y•'C^ó‰½j¼!6e±è2!͞Œ°Å©ñ9¦.îH­¿ +ߔ]7™íÒxëç±Æ½ +":òºÄ@X*69äÓÏγ·f)=ÄôDËçyè´$±³¼ŠÛ!o]çw~ót¹4Å ·;¶²¾Ì¨êgdO£ÈlÆi/ÜÝpZ‚»ç·6¿"RvêîÆæ0à™3ZN§Ð¼æWÜIr*f©,‘EÜ,ÂZšG#¹¼ã ¡A9—“äøòs€ä4Íd å簒&N¦ašÆ)i’œÆ>t—âWÑ5¦ø»ÚÝÉö綖UC- ÆQ,]ú…ð–ÓÕñ–Ÿ¬Òh½]3FtÞ,½‚¥W(é*–®¦±=mëXíý[|6#”UÑG'ßD¹_5òŸ˜þ‚šÓ\Î;­â3ý‚i&,™gÉ/¨UÂÕé/Kš<¦TÉS…ÝÑx(ºŠoúê™*î]Æ$ ?å­ÎP~K +Hã‚o 
+ÿÈ«Ô -Âa¯À• v)Àá-ïSâuB·ÀýôCá¿-ÀY2i…nZáˆÀ;P `2å6ŽOÑV՟ІñG<%À!JhYœ-è8¼ZØ&ìž^>¾ԍMõ el_ ø¤eB=-BRØ'¥ÅÞ¦éüvð\¡]À9XùžpQÀ#,ÌRÜŽ §–Îõ
+Ð.€$€‹}+hõÂqšñ¹ F}.À Ô& ²^€ˆhÆN 0,Àza»€EÁ-HBDàƸ$ÀyÖ`­tBà*pËÃPž‹V
+#ê˜^õ õˆš¸Õƒj¬>7>*ãÃj7^ĵǫ́¢ârÚÞñµõù !^œ=öq^û’6åÓÇ>äϒ‰XŸnkWâr¬}¢ôµ’“5ssÀä5y
+<6|áÑDn÷ǯ܏S`jáøg\<7%¢T´Zò·z{¼¸5©' 7‘.
+Qu‚ਕ\Nv‚3}0ÕºL`ÊIM¿NÒÙl,IÞ°FãHj*ïŽÄŠ|$&U\®¸l. \6•À×h»œ— þ¦­/ár^àrnNjkkæÅÅo26å—ÓXvBº¼É1X +Vo݆ÈÀ=öGL¡GÖ\ùzÖ®‘λ_^ø¾qø®ì卥üŸ¦+K–„³³Õ zWYóÑ_lzæ;Î[¶/“a{ˆ>Ê©TAÐz)ŸÔŒ"¸À¤.å +ˆÓ Kü_ä)C2Êãã<ôòƒ<6ò._ၦólŽŽäÔð\ƕìQþ‰§E€6d£»l±}“»XBwÎ2h
+š†Îðï|•?¹Îé:ç¡Jô¨”·%´7„·è÷ê1Î a-ŸÀc_¼`
+ó‰¶DœššT#ù…õÅۋ“♃ÖZ‡Õ–¶Ù*j]HÎÌљøÄL˜É2Üt< +±% +‚l·BÀzÀŠ­Vc$Aô#ˆíH^Åå` ­ÏÄö¥â2P`£û ø.³­ÉËc{C·Æ×棻Ã'§Ð-©€‚|?–÷#½œóbm&k,Û5[ Ý%?N÷Ɛô¼ru ±Ycá‘ïžlØùäÂÿM,]X–ßXž¦zM[¼òèºw~žUfLŠIž™¬óÇ•³zñ€wÁŽ¦¬ŸÌ¸µµ Ýúì¡Õ{ç$a®læ’R‡1}fÐ$­žã{õtÔiàH¯Fã(j(Ìo,sßU±¬¿ …S^k]s)¨lƒ¸?SÜ‹<•Ö&?à†Ýnx ²wgã­)C)§^gÓ¥êˆ
+Çâ4Lž1Ãq3¬6o3ï3s¢¡5N2˜Âqq™¨Ur%’ñ©dHÎLÌlE*Q%©ˆZeÎhߞ‰‰™Žv5ÉlW™'á¼ïr Ðm¿å4ÉGaŸ‚]QíìÖì¥(˜‚¹ÕIòÓä„rlaˬ¹?7ÞûÚª±70878Ó3³kfÓÎfô/ÇFÏCecØݐ»xG$z 6ÖmkɃ{VnŸÆïHoÜÑZ¶ª)dԖ¶ÞŠglXá -ûÁÌ%Ó£\üôN
+ƒ ( :) Æ dÔ/•mqïuãþÄ]‰xSìžX¼Å¼×ŒéŸÐcNoÕcà°Žwð”HZ1V£ZiØƔÁœH‘‘ÂÜŰ׺4 ±F´bR„('Ÿâ²Ëm¾>¶ ô Lb2º"f`S ²EPO€–MÆtâίþÒ÷¶Jøã/¿ž^¿¦ªzýœÌi³»Ë«{çdá¤èÇÑ?WíÿΩÙÿ‹ýwœ\–ž¹üä¶;_–‘¾ì vÖ]ãWp?ÂÂ6iaF tÇl‰ÙC2 ÐmØbØk û8àÜô¸­ánãŽqÏréÃëc·ÇâX½!–ˆ5‚æˆy7/ñœšŒ£*¢¯Ð‚V0Z&&øn["ì6vЃÁËqyºÍÈ×FT[_[_*;&oA°(h Ú¼Gge6ÿÇí»
+6ÿÛ¿+ráoø;ÿúםcMs*4*Y`GwS þ Ý£T´ö”<~IJQë©5RÁq4N71}ÉØ÷R:7šÆtL»-qa»¾fÔÈ r — W ¼Æ`WE\¢Åh`˜*O¡4tܗóèùïÛ0¯¹9>ir;(µ Áô`±Ù¡§àj"%qùMÒʺŒ3€1< +˜`’P>¯§¦õÎÆtüüܞJGvÓí +c÷ùÉõ3sÔü´’2k`VsÚâû:Ç
+­ÙK'’q±m” D]C¯ÈåpDñdÙâçÂzQ€aTÀÇèì’iñ9C`ÅM +Ë´œ7r64Ÿbcº9_ÇÅ60tç[Â6,7ÇR´Š•÷ž9s†w?ûìW—¸Ò¯ßT֝âgò3ºîéh@jØ"–8Xž
+Ë ¸k\.MÍ ÆïdRêg‹7’àrowp_tsnw‚èÖôj54—(Ԉš9:JԔ.Rjèʄ6¤ ß
+úåóa
+Þ苧‰Œ¶ÝDéÉ/çØÒÇ©z‡Ô=»c‡ñ¬0}åÁ¥Û_XŸ—RÙ¼rCé¢{WJ†Wb6tÏ^)9prÛ±¾òUkô3o[R²àð»›×~ïö¦`\ÞÂMU1­=Á•Ç”¹"ø1·Ï¢ûà“’0Bß7‚ *`.pú³NÁEàAJó…)Iᄘ’e!¸ +ðã½{Ù^NâÝGWlþSˆƒ£â3"֒’E¯·éSõ„bS£}P²ýZÕ­l$ +ѨÚ]րu®µÝºÝÊ­ïYÇ­Dm•(´ZՖv¨e¶HÙ¼>yÅòÚFaÀŠ¦âS¯IA&
+*-äþÚrvKtÙyÜpû÷o+=y2ºv>~”|¸øø@ÕØGüŽÐúï,ݳoìƒû'Ͼ›î» •K¢-džm6ÞÅ…K¸Dù^ƒì–Xz¼Ì¢Ú(ÓüŠà»¾‰“d
+2¼ñÔÄÆٔ³™îUŽ !ÀY|¥‘’Ø 9'©|aQ)O®QWV^b-_TæT“Çy¾xùކ±wØ/ˆ.$Ÿsõ(U£¥ðÖÜ¡\¼I½G»Ê¡Iߥǭ¥=¥8œf†Lqö¸-q{ã8•3ֹɹÇÉ )/9'¶Ç\ŒÁ1µƒª™£i <(ÏO¯5&€6Á]+Õâ÷jÕºk‡kGj¹ÈÅZ­…¹µ0X{¢kµøBíM¦1¹ˆ¢ceÄ+D
+T¦rPìÒÖvYf3ò*@fU'XVF'ùŒ>ÆhмNڀ1EI” ï5øÇlƒô·(H™ “Ue¹‰`à”¶áRÌKæm¡Îš4l-kê +¯| Íç[ztýƧü˜?Ãÿâ´ÜÈÊÂêå•.—´¬ªpå¼¼è´ÚeÓê’ë7/x>³¾Ô[=ôî]w^¸ov÷R{yQ|ÓëÒ¯þä$oö=º"'g壽Ǘeù;¹†§U^
+7eøG¯ ¬ñK/jta÷9Š¯Çi ¹Œb6ƒ¿æÃÀ?øl2-½¢
+À³_þàö`SZ 
+ÄjD ¸ûM¨ ±†Bß _2ÜýuÞ
+}ú$D^ +Á‘ì AwhK/
+A]|¡²þG> +Á‡!x;¯_/´Hf¨$„!Bð³OC_‡p7e:„^ ½âiöìë%”FXWøZG·‡€öPZZâ\!àXŸ†ð©Ðù¦ùÛC7dëBðð8kF‡‹! ÍœbÍ +áíl0«CxnÊB"¥½]+t”µu „;CP‚
+Ö,C®V
+m í =z5Ä­—ë+]õ¼bƒ!r ÷´}:•¯Y¥ÏÙ<Þfc…ÎÐA6E6TB§ð«ðL裡•V‡ _®d AÉ«4ñë9‚~VE™Qºc}Ѽ“¬0KÞâhCB€;Bá¡ÑG{Ï A H²„@“\É'Ho@¡½2ñeôW}Ex›"®MJl×h×M©SrnÎn¿!{ÊٚRYÁU%×Re­„q.ÿ‚øÓ8Éx¯"ˆÒö¢’Å•Þ¯³ñÅõK¥mIüôH§4ïÖY)/L–úW ²Õ×Ù¥œ¯ñŽùc÷Œ+²)ÿ±9âBdR#3ôÒ³g‘t8͔†Ó2MŽTð ú•Ïd˜; +WøŠ¤Ô¶â!Œ©¬$ ¹eü~pЈ¨mGŸÓr(@9T 4.á]™U·X‚6ø^ô,wÚ
+.gôÏ´¦“"ãéüÏ(Ž{TÒ­EÔÎÔr-;î=}8Á(BŒh¡†GÌØå 8ç:۝۝œÇj£³‚O9Ï;/:?wªËÚi+yÄ)-è ;¥ôia·3ÇÙá$§äBDr‚‘¶‚-=e#v•Õ 2RÄM_Ýh¶«4mkíä’6&ìSÖsN!'ÐiuŸyè¡Ø² +îêS¶9#èÔ½O^¾ZG^Þ¹µ¬«Þ§Rí%|læôô¥;éœK(?t–Òž"ôºÔÔïßåÇëmÛmldu,¤B–lùÀcƺ$GN©ózQ˜ò 9<l9a±KÉ ®N+ٓÂZí´0[ìv‚³£d´–@‰Ìñ¥g…+J@,Ë4>3âF)0œr%§¤¸Å˜ß¡ëÕáAèt<e¼%^žø1—”Ðu…ؾëÔeRÁ@e.P/™K˜Ba˜@›î'ù…lyÔ~JmTŒ Ä%ñälYïw»—Þ0Û|<nx°tiMºÞ@MåàJéýŸ½ø~âcBNU“k¿oöšJ_kS}±|³nmð9¥îY®… +bzeNnE–Ëbʪ^1ûàÑ;öY³J¼Æ[꧕¤;EÝ˜Ñ¬ÀéëôqåÚ*ՐV#f‰È. ҁz¼‹HB4‚x5:ÅD’üO˜d"ñ92Ê_á5n~˜þFÓ_,…åßìù÷¬A AŒŸ®ÔYb¡PCé±¢žxý<¿ã«íò˜vÓýþ”+¥rÇ©úO–§ÔCì15R@w@‡Ïëà€î¸n\GtéƒPs1åóŒRĔº]œ&eDэ¤_IÇãéÐ;!“˜LB±`¬Å¦GÆI„ÉI²šDÙ´ + —ƒ2S¦ Ê^@ÇÆöÄK7j’!0A鿒A¸Ò±y“Ho¼úÜÍRÈpþw¤ÈÀäs*ƒ¸PI‰MÉ]ɸ5¯'—@àBm œÛÂíå8•:VÍø-ÎR#e¡æÏÍØ\0è®uQ†§·`¸»
+`¼F .`»ÍŒtˆFD©Š”(óAL5ÔÇô,“:–IæµM–±‰JŒÉñCþÔ9Ãä:L`\R÷øÖw÷n;™‡äù?KÙ<öŸ‰åÕµkëÒÒnY]3£Cr=¿ª¬ [—i},¾ûµ%=<Ý'hSs
+ ·÷ÄÊÿÊ'6o<¾Ìç_ñ]4Ég^¡¼L2*DK·6º¸Õ×ãÃ%î:7.j,€¶À^àT|,¿‰ßÃsBläÕm»‡ãŠ³k´Ä],ã÷Š»‹‡‹GŠ)'Y £Å0·‹Occq _(¾ÂB ‰u˜D]FD%:󮩨ؚÝÌ<NY¼ ÚƳ¥3ËÒ‡2*,*,¢7qŠðñÏGòÖ̕ +!»Ì>‡ù3@?8¡bÞJ3ëWϨ^r:BKk¦÷Ìõ[!–þK€ÄxʙÙC…éÄ4¼çëBx9¡Øžè ÖçÆáòM':2ƒÝ®[sbU0uéSò¢âg¤çZ‹–Hn7“YA³sL°:tç8AXÄXé ½"%ˆ±á*¾‘ï¤'˜#"qD¥Ò@?Š—W#ô‚m²œGÑ“eeC°Àcâ Rƒ&í¬Œþf? pÓÿðô¿Ž?"ïé*J;íü!”‚ŠÑ))g¿ +¶ÆÅâu··’²•F܉q£·ÓÛï%ÉÉýɤ ©: ï΃<6ºl­!¼+R
+ª¸Ø°­·ZÑ:)z<§Bjé`À¹NÔºµXˎ{|RZXëÌp»óã7ÚmG¨0eTçoäíL0¶ÉG#·7éƒ"gçBW6³Â¿¹¦ŠÁ6%ú +Ց½lãÓ뷝Ûª¾óÕMá;VΉ{6q[Ã-›³s_ØØql}èå”pOMngC0£¾gFåÊp¼Ûsz{í’Ó'_ƒÄt$Í\q-j¨Ù÷‹ý‹Ú˾×[·i~¶³²g֜»:K³›¶²ý3±¦eè?¤·’!‚ðnŒJw—âàî ìàÃnޚ:”Š™V›pB&Ø4°É¿Çé$êÒ!m]¡=·Ý¾ÞŽÓíév­Å½.7—®©Ë°·a XBƒºuŸk)
+íý â=i°8mMÚmiD›–†Ó¼§©Å;t0_·\·QG¬:à)•dëd HÅ0ö+3x˜Ä³òj›(á ÚèÏå¾ dk/SÍë䒲}… ù @ÁEi”T*zà +¦-‰pöŠ-/nÜùb©ð}ï–5·Ü}´zå–àŠeÁu‹Êvï¼õýKºÈ¶GZ6=½&˜^?§éŽy™°{éCÝE•«÷֙ŠÏHÙ³kN{ù˜­hI]ßέëcچe—uÝ=»|͂r‘ʚ™û:ý
+v Íyqã—^֙ÂÚÃèlQ‰Laõ ~X%}D?¢'Ãúz¬gY1i™a½DI¡^ý$:ªC±)2jô1ÅWª<o9x `‡`uZ·eç6ôdÁÂúîÊ]•)º+!~³‘!+˜¥Ÿ¯À›ðLVX6YöXH·ŒI·u‹u¯•lTíTá.%ñûyÜÃÃV4„p jA݈ Ý’d!­„9hRC­,˜€ÙT©ªEð'՗*œÀgñ¥<xø„ÿU¼ÁÀ% ,TŠˆ€àô:.QíVç0ë¨Õ±6’J
+Qøù’RÇSÜys‘Ø‘XœÛ;;{%–ÄRÌ×nµX6ÀÀŽ±×´•dµ&c©‚3µF ˜KB ÈáúUTVS?â!^
+à'é1DM<܁GÇîxìM\ñ!.{^tÆÇÄ9g°ŽE;ßÁáŒy3³yÞ_5/#šKç ø³HƟL§yØGTp·
+v‹Šx“ñ±À4ՇbI,/éma~‘vµv›–h5°>NåÂЋ/aÌlŽÐ ¯¡û¶Q :º¤ñŒ6+“”¡ ãœÛ>:5eB¨M1•ål@§Áþ<¤ïé±Ux÷ëoF‡±hµi¢‡y‹Õª‚¿BEô‡P±Ÿœ½:ë^r+ïLIՏ}¦Ip$¨é\"Wx)®° tÔ+EZR»Sqgg…‡,p¬pà–¸î8̙a“i 3å4ÖéA§-ê½j¼‰ì!˜Ã ¦xaØzŠ­™ƒIë>°o4ª½U֊}SÉ.#P,h®cP0gzgï>³bå ;ëëwéé:½kÖËsú³úçfdÎÝPW»a®ÿðíè§ÏÜrËÓ`{ç—÷Ä̙OD?ùå“wï¾ø½Ç~wWYÙ]¿£sí s5OКHÝO¥Ãwuð þq=~ÚGLÐïÝåÅýî]îƒn²Õ5äzØE¶:†;ȶ„} GHkFOnÁÝ‹ñ”èj ­ X*,x®å¼# “+$*TðjK©n½VXÇD Ù4çN ;-ùÛã×Çãøx>kc²:fcÜ5lH—ÉT´‡2Ù .‹¿‘ísAÙÖuMN˜|N®Ü5y¡hª>ꛋh®Ýõú¦†;W„cŸM¼uQݖ¦l«k[SÔqtMYÅæSë?ýë©u«k*W…Ó½5«ªóVÎ/À¿}-úÇזxjzç:5U]ظ%訾íÔêµ#ÛfDOžš3´¢Ìß´µ¾vkK0¹fµ"3<‰
+Ó³1 +O“ôeÂaèEs|˜"¹+/La£ØšAµH²ÜžTú0'ÒEITX}‘¦È¿41IÖUÑ ƒ>%Å1mqV
+ÊGøûÐQ„9äßç‡~?”ùám?œñƒÎï=C9U?¸ý`õòÃ~¸à‡7ü0ŠîòŸô“?4úA’ˉ~àüpø
+«þ†ÿc?9ÁŠô㈪üÃ²Sü˜¶r‰ùÀ‡ý°Ë½¬v•¿ÓO”ž”n”Þðs,»Ñ•æW²•öùˆÒb•ŸXýJ »ü¬Ý/üVó ?ÙÇJ°Úý~®Hšÿ±<9VCi…§“dÅñ«~`•q=Ýõ¯ýpR™Ã =V’?âïõ“
+¶n?Nr,F‰R"V'ªl²¡V4Óµ·9I}
+ C
+I¤6.ØÆø9SP±* ëÖý%Æûo(}nV÷\Ë\¢<Ó@ÞeMSŠ¦|ÚØ_›Gá‰UêPƒ—aç´ôظ$™[B‰+'EÝñ 0Æ$Æhp£÷D¨ F£Ú$R9?ý5ܪ¶š„ˆ6«zÿFž +öL æó|KÓ¯JdԘ‘ˆ+().
+¬L¿ÚÈ︰VÌ(Åé3Ê­äßeñ’∥ãŸñ¿žÀ‡k¤[¥BB*©0Ï6¨=Ðè›Ł=V˜`•Ð"ÉhŠúܙƒ™8iÑ)íy-vkÁ¨uQ~ÓÞnä¼í¼eR®§"Cٚu.TqÞä”4† S‚y\ì7Ž1ÿëö3Ñ«?ýêùæÅ/ÿԓÀŸ^üãÊí¯m»ãÛ+*·¿¾m×ùmeø§Gÿ:ºjíAüqоڵâµè—ßÝ~á`$rðÂöíïiZpäå¬ònzV-èW–ž³t½/,‹–ÈMC™¨Õ!¢ž°ŽÐCº't8SÀÎ2Mv6 hÆò”›`YŒªÄFñ H.ÑMAbŽ(‰½â¨xAT‰’ +$Û¨í‚풍S€/†²FêŦµäD§/lè$Ð! +…H
+ìOGY“ÒN ¾,¢¶·1àñù€ùxà:Ĥ¥ûIªT܎aò÷Ó¿Ã_Q9”{–ÉÍÉ\ä½ÚÌïø:œ››µ<›Sö^^.J×C‡f¿¤¦Ì’……Tµ.¬Õx¡UQv5r@Ó¾ž¸v B;šbb— +®²µÅ'ó°L…á±y&þžä²¯ÞOò®þœæw‹N8j;6Ùw€öÍ£b)ƒÙ139º9‰ëà¹ÜNÑë#B&:•—%ÀX¦)¡œÇÿFçæ˜ð›a{¼Š¶kBIèRùS²>l}ÚJ:aÀ¹Û‰ŸGð·¡ý߂ZQ"äÀ¸î‚—HA€T"žï•šêzÅAqX$b§ˆgÐ}öŠAƒ(ÚÍ­:B¦“dê0 +›N˜T&É=ì>á&v:v:‰#jNÄí;,Ø9»Å·_ãíض)lÅ<”½£‚¹ÄÄh CB¾ö …t»Â1~&-k2{ dŠƒÂþèýѺóøðæWÞxç"þû´Æͳ¢eðî¼Í³SqÝØËüŽ¢U‡–̼sÍqìQò™´¤Â5ö÷Ìð2^¥;(/O_r“C +#¬(»dcÛ0:A˜ñìŽ"´ŠKMAÐöê?úˆfËmp¥²-¹PJ!jÚ 7Ìa‰‹p—è®Òð Êû²V8þ‡Ž‘îêd[×ÍÁr‹?þ1YýÞ{W|ï=y|Ý´)½lÛ¬’²·ê†t¸›
+XëT*у¨wSÑB×Îìƒn
+4h)’õý?éÊÕÇxg&~1\*-¶yóp‘§ÀÃé{^¬}-r÷™®1ù.÷ùcÑ_Fÿ=úÃ3OA5”€ÿ#ÊÍc¾ŒÞ£—¤Ä††=҄»°,¼ã&_—7eueá´sãÿ!µÐO…L•ì)GRðޔS0©ò€Êë¦9^/m°·áaØüƒ)®Ôu·[·î‚"f&lÌÈSú=*qãFÝN^©ƒØ)å„7(ºVÆ6O*îL%¾Üœö¾ö¾ +Œ“bN2Ku³ y]•¥öZè%ŠÏ9¥;^ݶþñ +3cÎê2ª»Â5¦eQ.4{VeAÜH€xÆv:r†—w?¹I‚·{F¶×ä/ÚZc˨/óú·Î­\;wš˜˜jÅ_‰V¦H)k·RÞ¿C(=*Íà` awÞ*‰¸+¤B¦§ÅÓí!Ý^Hô‚ݎݬr@ºs¤‘Ҳ’h@“5h^géOە†-iL9‹Ã[®¤´0Ò¤Ýk†Åæ5æÛÌDkN0c³ac¼ÒÍMɤüÍ ŠNE÷%0°›Tr(¿Á‚rþF'+ùÈ©ÔÛÊàœ›RÕ^ZØ~K@}N˜ÑÿÝÕÝ'û¦›z·Þ¶vA<¾¸}àÅûn»íîÓ•»’¦·”™fíé*Í[6¼¤v°ÍÊ®Ý%G|õhôêuü;¨ +m€.i|«iȄŒ»Xžü–Àޖgº" +–×À‚ê՘1ç¸.n)n-î)&e+`^VÔTày¤I…RDv`"g5!©¸Wãݘ³kJ4˜J2:Cƒ5n½)»xÅâ=‹I¤´£ó¥¶Ò‚ÒêRno ´•€¶$¡ j¸=ÿž|¬Ë¯ÏÇÚ؄جX²êƒøÔÌ÷µ½É»ÂÖæFâ~ãµñÚyݶç®ÏÅrçâÜþÁÐ/Ð<qž{™7í—<¿úW–å¿r¦8™ô0×B˜UEr‹Óâœ]ø«ô`-ÔÎøÕlŠT(;vYх´™‚âe‘É“•ìÂŲ˜ûH[^žx™~'eDEW2©°š*<\Ošz
+òeäu)‚)-‹‚$lÌ3èÚÑà&¹ÅDaXʱ±ð×8¹JQ9¨×ÍÜôøÒâ¹y±TñÕmµ·7dÔîzeÃôŽºB³*ýX¿ÅìK+_=7{þ£9¶ççö-ÉYðøÿ>Ò}ö¡Íӊòîzâõ?ˆ~øvwŦ§Æ‡ß‚”ó‹ÇK®h)^pçÂìöå՛;jÌB þßáϟíp¤4£OŸÝRí+]yÏüÍ¿}j…#gfƉ¨ªqsnŽ¿áÞÑÞ¿{l±ÔÿèèGïùË3žœ’øÞ¤Š|ÏƟCâSûýæÌ`ͦ‹þýh^ãtOÓÐs 7ß]¾bWmJD9³‹àž‹{)>÷HVJp/e/zõ8¼8( 8¢´1Äí±-‚/à‰2lï’q¥¢Ó¿_ºs·äâ-^p16*^­ +ïI:”„ë-\ǵpx,óX4ÕJ'Üà.Ìâ-5È,šsÌW̜Æ<Rð +5jÄ)š‘ÞÆ"XQYË(_ÑXËô5oR$÷µÉpÒ¢'9-Ý«¨™ˆ¬çW©Uê Áfò +/¸è†mß +bB<Ïôü²¢š“õÿ¯_Sö×­‘•ý8yìg-ËŠs’9Á7=œÎ]Ž¶$Ù챫Z£ŸEÿKÖõ¯xbsÿ#Ë'tý€îeþet­<Ì¿LCÑó/ÓÕH”²׏SÊæDÞQïï%/7ꣽàô/KŒ¯µ²‹öû%û;¯±' »Î†Ì^TTÖßî_ßbFö²¥`ÜE(©¦±³|ÕîÙΗL9Í5²ç Ý ;ŠfçÅwíodÆáêUÕ^ãæú±;ùw¢wxf§«å9 ”›Mi¥ˆ>•^º%¦5¦'†TsM\Gö¡ÔØjì1n5rWæÖ<@¸~´ aÁ~€m*€ȂRÊr|ÿlC©¨‚?¡/¦}8Á˜e,¥rÆÿmü»ó)KŽÝ”QEÈ;(ï6*^yñj8|ž
+RnsČsÌæaó(1>`#&[˜ 8“¯ëÞ(¬·÷1å[û +Ú·$—òIWHÑ•âH̛c?| ö“ô1}ŒÞi‚]oQy̝ÝâÍLOÍô6çK²ÿ×øg¸Döà J [Ð^:¿xƒ)œ‰éL˜ö̍1Ljݕ3¬´…YÅï¶Ë:c[>í¶ò—¾r3Üzþ(½ÍD!ԀÎK»2vgàÏn–YáÄ݉x ~w<Þ7‡·Z†,x«¶j†4x«zH¯ëқª»ªqS}W=n­ì©ÄùëlYë\Æ×\ʶ¸l.-ådŒå®rì*”/'ÃåP>0ÌØmŠqæ@IÉìÀ@‚zöÀ„†LրS*|ÝÒhbx©1Ê¿†ÒaÊïu¼Mñl3+H{Rå}ó‰½Ù-‰K(8{Ûm/ÝZ˜ÛUXÖVá)é}bíƧÖz*ÚB¡U³¦ýÎQÞYW»¬"1¶tE¤ie‘É›XµqÁÜõÕn7e€æõV9aoëáuååk·ÌÞ¼°@àbÊö”ݲsyIéò]·”ö, é9mÁÂÍxVAs…×[Ñ\ÕöûÃÍcß +¶×egß²4¿rõ쬬Ùkåó±ŠÊÞ[èþ¤£ç¤¹Ì%V±QlKݗŠ{Òඔý)¸'z™2º•@–³Ç‰÷ÄAV\Oæ56 +– +ýü¢HBG>•p>»À˜ É¢lÅW¢˜é΄¹WxQ»‹CFшsŒ’±×8h5^0ªŒFm»Í2éɞÐÆd¹ Ýû&…öö¦¬êT¢(KñÌvOâ7¿r»T½ãՁyw­]à9–Ö{øü¦g¢ãÏ-Xt
+ÐÉ߃¿ö%kՊ½ÜWŠ€þP£oÎêÊ9sïî,¹.ď<7=oQMք¿ÉFÙ߄“ýM
+Ðktýlgð S>7þà™™5$åšÇ  î46Ëþ&É%kö%*SR¡Cd¶Åoõ4™p4  h±Á™èÙ÷>äNC¬3ú{«Â§íŠ.äœÜl”†JÑAIì.ÞRŒ»³¶dá=)‡Rp
+£ZJµê\-.\§nQã=äE1S©Y:¤OÌM4Ö Qs(Fâ4âÈt¨˜½Ó‡§c×tŸ£Ó/MljÓ"Éb¬ÑèÐFøÿ jæ»v<(=K"ßâ*üM§îŒ¶#½ýÏûyFÆdÃõóTÆ&œ]š×UÑ{¤-ãõø²e·0«+#jõËËâqò¶ ‡šš;±;§ÌmáUéá²,¤Kòë¶È}ïîè<¶¦8¹ã©»˜»tÝq™ç§0~‚Âx6zEºsz|0#u^*æãmñMñdA,°o¶™›Ìd«~H[ôÝzÜC C]j å^°Í°Ï€%ŠJ´)‹]’ǃ¶»¸°+0ètĶè:°QY§ýJ2$'ó í™±ÏÑI:<¬»¤Ã:Ï]óVQ´Ï×ôSï´Ñx„w’—œüXЖ͝dê*PÉÑ4qV¶œÕsw¾1Üé=›P·joóà뷅fÜùÃíó÷÷-pFá¦ÀöG^[ýBôËÓ-ø§2ˆûl›]˜ß4Ý3©§JÌ.JŒžˆ&ä,œ‘ÆN=
+ÿ²žFu…Š ÿ%pQñ·=Ÿz#ÒÞèoÅWTTbGùR²ìl%‘A
+ŒÒ\t€‚2–Dk¶Ë¾Wú0B²Ÿ•ïÚ!°m<s¶úÿ•õ¿œNêk\@ݯ ̼Xâë4 ϔªõa†Šâׯdäè€kÿÀ(;.o‡ãpŠ2ŒA£).) À£v5áot&žªH
+øØ)¦CòÒ'¬$æ«9O>áþ8öÅ#c?áw£cj£xáï¶*ÐIë•CU9<Q{
+aW.΀§< ó8<>ÏQ×â|ʉ÷™`ŸaoNì*…ŽBè¶Á&dµf²û%#°T
+­I´Ðå·"—è’\Dí²ˆ–ØðfËݖ‡,¤ÌùL« I·æߕ8Ÿ”æƒ%Ÿ´¯Ï‚–,¨ÏUd¥p1íÌ Š2L#ªÈìŠ)˜’:ùà*z¾IO© Ëû¤$zƒ¾”žowÊT¹=ÝÏ_“\¦@f\Ïý½zø£CÑDÿ3㕘Òå÷¯hºwEIņG:Ên]ÛQ“Ñ0üƆßœ÷ZLÁ‚mó—ínðV¬¹7R¹cÓÊY>ØÝrhmèÜó©E­•)Îéí3ª§Å\¾Ò†Õ5geÎÛñ#…‰Þé +Š†Â³‘f6na/Ž2æJ‘ÿJ
+Ä°Ã=šïhð +Ü©º_…7©àô„· (ÒުʼnZØʁ…›ánx(¹K}XMT-¨9NDÙ'£Œx ˜©+ÑaNg¥=èþ¤ûRGÞÐÓԞՑ]:PéÒt5ºº=:–ö&-!htH_Šw…u _‘t”¬%†sãƒRÿÅO› Ði€¨2@¡R kÎÌáÿ£xÁ»  +' +äŸþé—øØ¿2À8k€“Ìm¸Æ°À°ÇpÈð„áMï 2‡hعyõåÑð.ÖÐ
+Ã&¡¥
+ +˜6t˜X↳´4„ð'Ö=lb6: +djÇßìw“Ü'éT—ÓäQð+¯F‹æˆáCþÖ¹üJÁ`£©1pE+äñÈ>Ðòø C3Â%H6€|µÁÖé‚ᒁœ1À aØpÂ@ú +Ða€FHÈ7€ÛrÕds|ø„0«1ôXi•ZËqj X£2"ÊéS™%hŽ+1A]£ôMñ¶mß°Á·aÉkÌ+ÌMI¾¶oó¾½VÊG߆⠌€d£NqPÖ0Ӟó&ÜßÚ§Öôà˜E~‰'úÛèG?‚Ñû~
+1 +zì×¢UxŽ‰.‚ÇǾû…‚“W‘7Á-ûz$KæþÊ8zÌÈÂU¼ÛF1ð»L± +^í=¶s´Î«:í.ZWì¨Sªy*¶ÅóqàˆóŕÅm‹ãžÁ!úÄ2q›Èm3Â[¨hÔ*èj;%X¯cf Å»ÖÉ;r2îù¦yF±¶2µò¤QFQ1ó]=ç¾¾oìà߲ëGçüù¶èÿ@éúׇæà÷F¢ÿûR¿£á©èÕ3Þ¾3ôõéð½°¹?By€L:~;º[
+ÉöÒMÃrµÕZ°µJÃN˜Iî˜Éóqó)31Ë£—£Ž ŽKÎÁ@'%ÆÖ´ÎåÛùõ<9Àçñ{ÊÕ0‰'jÞ¢"†v$[Ÿd‹SPûä*²˜1éçÇË®‹Ác0þ¨ÿÂwŸ/Y±oþ¼û×Uü íį­å;ß";®Xýàß´Ž£ëIçÕûîyïîÊýlÞÇì'øVi|1u”q¦ƒz´afNÂU¸ã4\ˆ±@ì$“-›oP *m¬·i`–|š2 +4v +ž§‚™* iªBV«âTXÿ‘|¨Ÿ1À°|þ•³”#Tdzâ.ÈèY:h€^1)'M”ñ;— ðFd¤0hz#òY§ÑÊZi4ì2œ1|là9C>E+ +o¾0¨Ž]ó:8 õ†m†g ø}†W +Ÿg°ÒòU®‡!#Š¥c´™7hC_4eòðtò𮍟o›<¼ÿûÁ—Ü4x+í°ß@¦ ºQæN’qIŽr$Z5Pª1I-FuW(ç'[Ú’§t0¬ƒí:@:©‚Î8I2Eâ&ø¹B°‹œ"çÉ{„#Ԑ¯®R7ª §Ö§á ™ðʉ£Ô›q} y(ˆfuȈC¾FðO ÄSmË}ìÙ÷ÍË7–ÝHnÀB߂Œ¸æhK4ò>ŽšÞ§„ôö÷£IX‡W=„ƒû5Î[6æTl¾ÑÛe›/ó yLê[c½ÍºßJVÅÃ*'t%ÃB4åBk:´fÃËm–ý’`iµà„ŒÖ l›Ö4 +'¶bM!Øòšò°: †XÞÁÌŠëá¢SŽóìv€Ñár`‡»t°gµ»¹üv>Ö¢m7¡=œ û´É췂~L%%òEçIq³íºW¹•;õ~³ÿK«ñìûÞßc+¨šL”âÒå.\Zãxäóã‘iC~ðÿ|À,µ–84üÁlZ¨ÙøC ýs‹2ÃmÛ£Íøþ‹fHÙwà.š­ÐŒÁOñÏ¢JUƒ*¬Òý•Ù3ݔˆ*y2íMÛåwڊó˜s€"hLܪšt8ޞպéóKö6û|Í{—<¿tk¶î‹þù7ÝÝ¿ý4ºo_ô3ú͟ÇöËcÑÓ±ø䱄%Ãݸ[€…VXHÇrnü¿_dá¿gä1ˆ]ªtTè¯èaÝAyDcSG“ÚÑIyû¾eHâþ16¤ß|Ɔôéoِ¢ûd¹yüçхwKã (VÇëÅ°:C£ ®Æ¦§ŽM´¹m’SÛlñöa;T(zLü‘]úÅ/Ãì—ìø»¥±c£Ý%ÛQÇíü°ý„¬èäæ²âp¢Â~Ê~ÞþžûÜ#öQû;©°Ï¥¥‰Ûä,BË­§ÙXì˜ÙcÓj´5;Ísç…/ځµ<b';«Ïê„ìRAq¸×>HG7bçØ ð¸ì’7-LÇK@c,›õzÉλìk#‚còæëeåE}>zÎ}ò÷MzQOò),.g±k±KڂÁ€ÈLRÈçcÆPӄh_0qUÂÜ{ÆVØN®L4¤ÒrµÊµÙÖòž9Ù¿sVßL?÷8“Fç°û³TvÔɲc,TêÎBFN†EÅFvnÎ?АÝ!‰ñnk\ØÎ5 0fà#˜Â¦Í(§xÑî¶ç؉}g\l¬FÜ@àbÂ÷z„5ès +h,vv«ÇnçãtûÝ|„ïà{å›*Q0ñî]
+j¿•]¾dß¿6ä|>~r²±“Þ­Y .
+ª“þT¹ªÞšmÏMÍ,öCágLåêô%KÆ2gæ؁,SémIæÔ |¶“4 o¹§T€~«èÒd½ÑÇgÒ³®ßTºVþýkåëÐß&î5¥Ó
+šii7ÝkBÜi|ˆŸÊÄ3¤˜MÂoÖÜ­ÁƒÌ-–¹ÀQâ‚Ϋ@ÅÝ+iªŒÀ®ñn‡ÏgV“`˜¬yzÇ'_q›7Ý«öX‚øP÷Gø¿Wýùî'Ö§ž²B ÃÙóÑ=œ›<‹4Ȅú¤Æ=S–a«0$ÈnãC„ô æÒJLŸ?ù¬]»]{@K$m‡»´ U¦ú”ÿl À +0T
+BŸë$@֓í”ر1},[Éû._¾ÌhýQ0r*@Ð&0— ‰Ù}æ=Ic;£#ù^ô44Ük+`£+º?zŸsJpŠï­”œºVIsG§ÐEÄ!àÄè¾":p²xJ‘”zŠ n¨aD}IjШÕÂu÷ؙɻ|Í[F¾”À=ʲʖ s‹Y:¦:uß3¶‘ß1ö,n”`n\×ÝRg—‘_I—0 ìH—j@µ[EºÐڍˆá3íg¦Ozø\½úA=¾ ‡
+ýzýv=AzÐsŸÁgšO{É !ô{à
+ˆ1D‹>1òÊ°nç9þÚ:ÓqÉ ÍÔzc×ÖÚ[ÀԐ`ƒÉþù“0/:òÌ»úÛ§YèI˜{ÖC¿+zOEôkAFÈ9˲Bþ+ˆ(þØÂatˆbüÑ34¬’y!-EÀ:í1þ)<>fÎ^×pÝé:áI¯ësW
+Èn×?¡}¨©Œÿ*ã‹x‘T$ßÏ9Pˆjރ!\j¼Åˆ6B·q‹q¯Q¶B㩸³’ÜJî"$†yArŒ•Ó€lÁ1ŠTzÙ&bN´*æTµK<(¾!~ j>ázœwˆÀ‰  fMŒëð"Œ³°Îì0ˏzó"ó>óQóÛæÌšq3¼aþÀŒO˜a—ù w˜¡ÊÜhÆn3pf«ÿôÒõ,e²‚ªÉËT9X&|ĊÂQÖ,b퀒~ø½*?„–»¹¿KßÏd·ÜÊ©`¥4ÿ¬G%]éVZ®t¬*š:U…þEŸ7ŒéæL1CÀ ÌâŠÕFl@‘ÄaâÍFS$ìI÷ț„ð›Eò)E•8Ó`S¶Yy’"rOºH2W7ùmm}̎fQ<!)NìúeôÖÑ¿¨-V“Je±Ú4_žçJǤ؊ª
+›­bFE,þ‘‚“s)lž¥°)€NšóüÆû0ȒæýêÇÔ¸_½KkÔ Ô+Ô$CDƒ·©€SYU)ª“ª3ªT«Ôj•šè  т”^Éd +Ëò‚”±¸3<ªƒ3:8¡ƒƒ:ÔA¿:tШIùº*]§n—Ž“ »ëæ…Ý:°ÊâF…’sRÇqº¹˜\èÅ`yX.lv¥‡/騣së"º^§–“ +¢5¬"Îê»®¬\UöÉŸ²È¾ë’‚/àóÉìƒü
+”ök†É`°7MÇ–‹{€õàW÷’´}{¯þç>yÝøèBr•®› ¿'•=@à GD8„àñ;"–Uv[C·¤Û ßI‚$Ñ †ï·À l°ÀË
+ ¾ß ÄÌe
+ÍQ¼†þ3%¹Ä#.Øã‚Ô¸Àî• 4.³I.hRy@åIózj<+<›<{<OxÎzÞôüÉó¥GÿSöĶ
+ã~~Ã,ﺱŠêŸÖWybi–âÜÏ2”dÝá/<pÉ?òüƒÏxà„îôÜïÁýèðÀ Ï<Î÷€ÛØcöà=_x°\ô¤çŒË%;=ý,Lñä{ð¿.·€µ rÁXÖ&¬”‹þŠ +䲇ØàÛ O–•ž ¥éPGØôzp‡§×ƒ«<ìöäx0ç±zð%Ï~ÃóÿërEtòÅ`¢L‰†¾‘‘‡5ñpÏ gØ3êá@уÕt§‘;ÉdÔG˜A%;S~et1qþ­¬íÿäâüM(æz¶õÉ׫'ÑG›"u‡ñ“ŒA)Ù×vÝdéaö™ æ¹°¦"•Åis—mž“\ê¶ä˜æÞ4Eç~¬u¹â1‰s&i?øÁ²ï¬/ãԔfmÚáã
+ƞv´¶†]ed^îŸ2Ý2¿8ò‹*T÷¾b•áMi€µÂSl’=Á&æ {¥xRÃQá ”ƒäó'ßkDþ(¿¤è÷‡¯ðð±òê³ <ÙÅäOòDâázÑøù/x2ÌC?¿‹ÇïC‹ýááK<¼ÁÀã¬|
+m^¾„üòÙWˆ‡SòEägžW."?ú½ðàDÍ´CÇÂxèäûi_gø7xÚ p¼•ÇìÁ¾àù¹ö–Û”ëÌëúä_)~ù
+Ö)+óOr”j)Ýά ëx(—
+Åeá·ùx,Dz²sÂÞÇcVícž¬æ·ñ˜N³ æðä<ÿÙ۞”âŠSä׿©AyÑ 0ß„Q˜ XOVû|š\šä“K´)@Ą,Eƚ¸jý£3×Þ54þûh77ý”ʪ^)NÄxŸÀ0ÏWd·§ïƒìö*»ŠP]ÜSÑî;ï¤|d'4sµä3ÙïµFÊUK´:F؍{i æ."`Š^î9å™êò"Ïóè¹\lTܨÚ.ÿN±Hʼw* äWä¤ùàÁ(:xP¡s•¨ˆKáEdAIk
+0”ê`º²ô@ˆJ­Õa2‰ʕê)IáKgL ŽÄΉ#Ƹc:b¸N~q"{Û"Dµæ9A~Õ9÷œI5u”ì­ç\D¸ 0f@` þ®-/ïrsÀ»æVËcÌq%â]£ô£<ssÝ9¯Å[à±Pd ægJg6úÐ}‡/CÑ¢:zøÀC`‹>v;¡9z<aÍAH‰þæ`T™/VîÒó£G"j‘oJZÊΊ쮡áÿí²¡ùÚeCñ£úItL¾pÈôßWƒcìæÑ +—MTò¾~ñðÇyø>Æ’åwr„‡Ê4_K·aتMjX ¬ ¢à!“oì°Û…[é×îÒԒ*-€V«Ëbœ‡ ö³—W!Ø¨ë×Ôö8£û@÷±î 3 *jËnµ6¬S“å\W8Nù Ìí†qg4(Áí¾Ä Í_î0 NÈâ‘)Z•8§¼}LšÈdo!ÔÔZNcägSޞTW챃ҧxNí+¦LŵíLŠº¢¥cþ”PËr–lɉ޿ëÌøèýhüþ²6ºçêRlˆÆËû¸q¼‘CtÍf£¥0W–oæÊGMàkZ݄³Ö6à쒵%·—ÜSÂ5u tìî óË7–ï,'ó[7¶îl%["{#x}Z#~*àB±FÄYuš©Ùà#`'À»l.̧ØR°n–cŽ©«O®+®‡ú¢ú¢95ÎÊxUÏÇם˜hŽ{Ž4gx7‡!6§'<G†[BxNvÝ´iÍu¨Ò]9RI*YrUrZ¸òÃ"x»^+‚‡Šào̟ ¨„³e>ô̇ÿœGçÃÇ1_Äà'bà֘»b𪈙ßVÄ¡¼†ìt]{¥\EÞå@𲙩‘‚7Aù&¯ÃɀÉÜ[ÙÅ`Ù9jÃuÏ(…¶¡©š`ÙCêš!xâÕdⅆ¶oqúNW<¨Šäwn°‹ùézÇr‚gÍZSg|Tã¯jôß¾#oþÚ²ª}½5‰Eó
+sç%•öž\ÝöðÀ\Ë£úÂ9%¥+fe—´o«¬Ü¹ªÒ]ÖXU_ây~_ÐÜ*m™îʝ·fza[U:ÿNRŲjkvi²anm¶äwLjþºžYÙ ëŠ ¶ Ô¬˜?Í!­ší*š™j0gT
+ÃÁÑè¯ë¾%{~m1Æ/Erö½úvNuÐC<ùÕéþ9ÉFˆÔ(8±áj¹Gèy¬•üïë`·~ÃÁ<­RŒmb(ÆwÓÅįÔP¤¾KÕü‹²m^¾wÊ´úAżmâÞ©G¾h‚?m†'€'qG´ž½ž6ÞÏú½?ºgR˜V£\)Iƒá¦H÷(¯qÎv3GÑ¿QÒý7ä¸v+ªïò»m}ì²m™žÛýpø“O¢ Õ;þqÏÚ¦]ߛhÓÅQÉþs&xÿ„#R’r‰Ò#îo<‚)öM´Y4Qtk?ùGîPmÜñ÷}”¾±ÿɁâÐ,rZJùc”fޒ‰·fe>œI
+ÄjˆìJ/)tÖ8q¡|‘óŠK…ô’ĺD\’‰Lt—}æ„( 3Ë0–/9iLV€ÈB1u1˜Êú)Çb‰j@Þ /4{!Víõªc‰13KÌbç©.®Ë‚ü,H˂dÁ›YÊÂ'³àPü?= x”ŵsæÿ÷‘×þy@–<w!ï͐͟@B°KH–$ „ $„$$›dydÃî„Ø›PÊC‚–r ¢x­×ŠKÄñM­¨-Ŷjí§-¨Tm«_Z?õÖB’{föDDï½Ífþÿü3çœ9s朙93³Å6—­Ã&ÅÙàs<œ¶ÛØh‡m“ ’)ÂÛÂh‹TD‡@ˆ²Rá=r¨rZýPýR•îWá 
+[TèPa¹
+ªK¥q*|®Â‡*¼¬ÂS*Ra§
+R¤Â5U¥z^ûŠ“>¥rF²G# +QãTŠ”O«°BíPwªRds"@’‹*¼5Áõ>Æ>Ú96ÌRËU:c÷З*¼¤þF¥O¨ð€
+ÛUØÄ%lWiG…X5]¥²
+﫟©ô +N«€e¹S`v¨›T:QšTŽ 2/“óM­T'2—ï *•«ËUZ0‘¯çKÎޘ(œP·ódGJå(±*ýœáC•PïW)–Á#
+PÎS Tz¥˜ ºKZ¸ ©˜•4ç~õeõ +õsUj]¬ÓÔzIÝ+TÓÔH»*%¨ð7¡¼_pUmW¨O¨²Cņ‡©R£÷<gš¢*Ëø€Ì0€!1KR””Ìð¨Ê¼§Ä9 6E2M“:³›²ù‰;ØL¬Â|íj\k3ûŒÎ“þ®ßR¹–ïõð&uw|={·˜æææænô‰VS¾ÖˆÙ˜ü5ñþÛ8=E²CFz†˜1*‰®‘+.Æ1_¿þúQc”14$$Ôc<ñúدOœ4˜ £1Ä©ùgÏÐÖÃ?ƒbxqˆ>“P›®ææ¨éË,£‹äâÑéæÖ´ŒôT‹s*ýh4.¾,iF
+^-ˆ§çù»ÎM˕òCDOÖ>Mtão;£¢c+¥L<~ØEølÉ·ùïÐs ¸m`Y¸98ôýúA½¤g3Àýȉ";d¯|T>'ëd‡oÞü³Mg›ð•ÎgÃñÁz1_6f6ð>g7ÂH +´ä,Üwxìα;ýãÄú¹¢fÐúíÿ¦õÛ§F¥SÈJ1’¯õÛÏG»í'—m«3¥]ÈÛeiƒô=évIj” K*’ª$Égü¾‘.7¶©Q¼ëB#*C¤H¼ñ„i€·›Á(+ԂŸg¸R´ÕL¼Ûš¯NÍÜ{\ôÖäA¶˜n$Úôƒ¦¦,˜è“‘ré©áòˆô,Ý®Ayő=—à¶RŽüsÚ ëmŠŸvðQ…ÇcPÓ<¯[ù<<Ãى² +twrd“dZ4*Ã_exG†WexX~V¦‡d¸]ÆoрŠM/9[¦
+Ǹ$ÓQÿçeéaŽÊÐ'W†Fj„CƒEΕ)å5Å×­eglœ–#áVJóäG$»yÒ#"VtÈÎþô] Ø@{z:·©Oþdô·ÿ)ÿ|ÿ‚õwé»2îÒBþ fg„ÄGjð;gË Ž¼ð*œ¤1¢‚f š`Û»åAAAæUNú`ë;HøbáC™$òh‡êàXÐT zï‰Ø8Ù0هX÷O-|(™3YøÈà”Av·úOƟ KCÚì|óT¸wìµs¿×ýó÷IïOáëŒK§é€ðÿ1.çôI@-âaÐñ>*-t¿ï2åU–YIE?b\b%eÀ»Ï|„rõ=ÕëøC¥çÎCøØ ®7úÐxW4i®:a k÷MÛ¢yUÀ³Úœ“÷d‹ìG{i–?~œ?ÊÃã;M„MÃC?‡ƒIú‰$Êc ?&¢€_…òƒÌ£Ì¼!_”éòËò²dEÅ”ñÂúc›œOø!&
+CÀ†ØkÆîí‚Õ°Ú3v´tŒ;‹` +´tŽ…fÏØýc÷z uìn¬×=ãŸHc|±úàß'¢^ï|}òD†-5fR½†b؉õ`—otV¡MÐ}t¥UáÛé+¶&ïN¦UÉ+“=ÉR¯e‡….´Ühé´Hûr 1g]N_Ž´-Ú#‘´>À\äá‚s[ ô('ËÑ•
+ì5ÁV,65šÖ™¸1^o²™ŠMRˆ þlúÊD§F¤E̎ôš§!šJñɶäâd)$þœüU2jI³Ì¶Hz |hùÒB-ɆÿmFLF_b·ÇÒXCF7´âsl9Å9’d́sþ‘CsÞ±Ã9;<k‡ãv¸Ëƒv賃׍v¨±­±ڏÛ%»o;«Ù©b‡».þùE$}*òtä[‘’iTæ(7+·)?V†}¸âtŽÇ¥W*›Ù¿³ß1©€¹Ø
+&™Y£z…¬“mf?e'ÙöûofLg``fF_=ƒØ1éföcö f²‡A&›ÃnbRGüžÁƒìF1ØÍà&ÖÅhg F6Ñ¼Âà§Á«LVÉncºCg‚x· ®º*ÎBX£o³ýƒÃì!ö “ö0`§né¯,b`c€9†2øŠÁ_D¦¯1f°‹bǸ€€¢³E¬IY â„3èeð ƒ?0ø%çøó fp„ò½…Á:«,fPÊ ›A"ƒ0—ü•Á» PŠç&ðÉí úl`ÐÌ` ƒ\æ`4‰Âs9œc€ü3¸›Á Çý£»„Aƒ æ\bð)ƒwœeð,ƒGÜÅÙ÷ ö‹Y#£EBœ8!ÎWBœ?q‚âß-Äÿž¿Iˆ?—'°0 Í¬Ÿe/²ólœé *;Ga3GR¤ §Û»o<kˆ©2LÁµ š¢ò¹!Æ;„›¯.‰úÍþÝoÚUW ¨æë£sEÕ«]}«'ÓOòÄö#i.b×ôµXpfWÐ$ãóOµù]ß®|ƛ²³¯úÛ-5IXj^ð¹1ùº.~6-ìˋc­gF£,aaJ¤Á¤(ú/N~¡W“!RÈiIÊWg¤þô®Ü¢âB֑~y›nÛåmŽ[fϪX8¯´Ð,m¸üCsaɼÄ®Š®-³¤~>¬rü]n—XÚb%LqVí؛
+}¶=6ºf™oÙ÷—I ¡¿«¡*{e¶'ŸÝŒ¦e‚šrKÊÞ)Q§êJu«trhh|¨-T
+¥ñÔF% ÏÏá{dÄÅÇÙâ$–•^!”U™§Wç¨{Á¢%ђY*VkÍ ©`.VÕb³¡Àñ(àŸµ¹ø:,¨­tDÔ` rP‚Û—Àš
+_-¨pUPiËZ¢¦GW„K÷'©DP‰4±~©1VÛ9ÃtRÐ:Q¸3_D/±öxßTTtQ¦Ežå='ñirW<ë¯.±¦›‘žqupá53Ë°òЪ6c]Šî‚\ÆP?uJ¬yžÄ+›OÖæÕkÙ_œ|CÓgïÃݳóÿmqtRlxfýŽ¦ª¾†¼²žuó»4鎔u®ÙkgZKâL`¶–ö´Äùy‹VÞ_¶±/¥rC•ZS^^67ž¥›K=ƒu5Û<uÓg,©®N)ó×ÏL¯\[¾¸wÕâé¾W[ÎlÉÞóAgjùAX˜¼%­4ÛÜÙX²mN|áüšË÷x>ÜÊx
+›fÅ$§óo ¿™]bÇ[œÊ.
+ß×A_(´ëhRð/YÚvÄd5ÑÓ~ÓÓßL²É$E58y¿jK¸ä +‡pó€‚æ‹Õ V³Ó\kn1˦f…/ ÂM$™ûßæñU¹ßÆ܍W·ß~ùƒúçzז³,¨mxõáwRž +µ;«³ºnû;œ³îð”§,ó›JwKG.§¸ïóÏÓüIŸ䰳óa€»vénà‹³Hn5üÐ@Û +p7y”Ðu¤ì!ÒJ+Bá®PHå±kÿDë>”oDEG,÷
+ +‹0ÐIž'†P 2wü$rô„GzÐ×D¬/?'ËT”¿1Js×:ç´I瓻Fåe£g‡_|‘þô½Ñ)þöŽ^ÔmG_=rùƒ`÷ViÖÖâ){šDq¯«pn\|윎€a$~˜òI~„„ò¥Ð”㺸㊌Ïûb¢ü»Ÿ
+ã*;{ò„ÉëÍ£Ã9՞’’®j»½º«¤ÄS³!ÍOO›ÏÙü´ôù,AÞRÒµ$'gIWIi×U]ÒUšV631qfYZÚü™II3±%…ïlòÄ +%¤œ¼à¼u»: › ´•B=…†²µetý\h˜ ÅõÅîb©bVý,÷,‰K×2i‘­Á¶Ö&ñ=Ò:-¥¹¥ŽRi)”º¢’ËÉÂý™`̼̈́…q¤¶¼\稝HޞLû¢öDÑä¨)9µ‰‘…µSbuµ¦# Áh4—%w #8Õfb© |gø>Í+*š¼ÐåäžÔ¯mø€/o5ÇH‚´k™¦í$Û¿¶7Ò®Ós¯îÚødùèg×»à•Í|Ÿ¤Ëےîò
+ßҜܥëçáY• ×Ý:©qUSßVÌ÷Øê:±ƒRÆ5»«¼§í¨tM»&·Ëõ¢]®çã|Z»\‡«¾Vߣ—õ’hBè˜Ü#Ŗ̑Eb•*ÔA½ô(=Gu”·Ëy«<?Ÿ·Ëóƒ3äD»¼;(ñvy¶\?8|¼Ð}xlõY¼——c;ïå4’Ev9Ë{Sw¤Òޔ)´!~m<m˜
+ +¦­¦Ý&©!bkÄî©X¿HOC*CC‡²NeѬƒéé1 ®DââKÌ9cöó%æbø7Ü®VÆÄLo ¥¡GÒoH€„iÍÓùݟߔ;zº)J¬íñi®¨íùÁڝXsš»³ê +)ó Á¥®¬šÉ›¦rFÚ¼še5Ž 8N3æU×.)M³-Ù°`ïý¦á°[ßê;Ñçü¹l\¸¾~Aiqy]aEW­£¨ÈuSIÉ*‡uïÖðå?ê™?§óÀ¥»_}Uó$ KþSÙc'›•Ò/ˆ%¸í™Æ/&ö›%ã¿«6ìÔóÞ
+#oûÎ0o¬š,˜ÀÒv#¾ú×C?!åò؆ñ“á ÙÍÏ´߈+ȃ¯@˜·ënô]Øð½a‘F"·ñt}ð|#^W"nâáùyŒÛÁÓµpC†jLFzá\ µZÇƒú"Òª;CÔò}Ãˆû,xíÁ° C§ÑBþójDx;†}ˆÂåEx%Òti²¬åéȳSÈÚ@*±ŒœwÊg†.‘×rÆÙyÞHӏ!ΌÿŠ—á͜—Əó¯ã4üŒºèG†™¼3€ÿexIö¿'?BÚåv2Ÿ—Ó¸>ý<¯ïÄ©;3>Š×n¡—ï!’£éÜÈõ‚²®¥¿Ã|!û0~áv‚ñ•ÈËÊË"哭·BãÏQ±wþHwÓݒUÚ/}&·Èÿ¥KÕ¹téþ¡?m¼Ù¸ßø\HAÈÅP{è}ajØëá±á§"þ-âSé-åe,juÔÑ«¢GŸŽiÙóD̗SLMŽíŽÝkžaÞ;-uÚ +q‰qCq¯Ä}¿/%T&øžKܙÌ’÷'_¶Þd›^>}ߌêkfürÆÅ—SòRîLùcª+õæ´Óék2.gFg^Ä'±8ëDÖeÛÁì—U¢ÚÔåöMöSö·sÕÜ{sŸÉýMî_X4[Å^`oåWϺiÖ3³Sg”ì. ×îÚâç6(‘ùŠ¨$—4àçâÝ)Œã©‰°âʽí¼rŸQˆSƒ)1 +–H<™À—§Gƒu$_ZAXOLø™Â²•Ò`#™1BL¡Áa(Ãì+»}Ûa©G~˜àiB{eŒK,‡àÕÍÒ` Éô˜Sb¢g4X"³è›,lÉk°ŽL“24XO%‡ÈçR£I¦\£Á!$QÞ¬ÁadŽüC +'«ä_hpÓMð4ásVîéô<[ÝíÖöÖ@«õ˜5±Bë|›»»Ýí³.ðúz¼¾Ö€ÇÛm·Î_¿ÞêótvüVŸÛïömr·Ûoð¬qÓ­u­ÝþeîÎÞõ­¾+ô9Ök®¹\áöù9<ÓÎò®&]ƒèñ[[­_k»{C«oÕÛa]†òV¶TkUw›…éôønFzº­õö:»µ¶5àîX[»Û­Ë¯ÖttxÚÜ"²Íí ´"²7ЅR®íõyüíž6ž›ß~½Â×ܛÜÖ%­€Ûïíî
+zŠss7oÞloՐÛ×Þæݐû]i-=îv·ßÓٍŶw6¬¯÷»yy]XÆI%îð¢ð~oG`s«ÏÍËïï]³ÖÝ°¼ˆë¶®Çrt#ik§ÏíÞÀKÚ+$ÞÜåië²nñöZ[ÛÚÜ=ÔGÿ6Îöïvý"!)j҉!€a+šíĊ¡¯[:†!0ü"4ã6ÄéF 7ñaÌâÅs8¶
+^Lµ Üõø³b<ç߅i~qåÆ3§Ý$ò²“0}ˆ¹Jo%uxՍ˜Ë0¥“ô"§VÄøfþ9¾›Ãw§®)þ+ñ3Q"†%¾ÕwsôˆòqD
+—pƒzÆyI—iú­”*BUHÛ&ôå%å\‚wÓ#x×#FÀª”\‘[·ÀZ~k0ǤçúºŠÙ&xóº +rö"Ü¥ér-êÙ'$hteócÎÿך¯Òmy.ñQã<­K\õbüä’ÍâgGœ¯snÓøÚ´1ÿUºÙ‚×n‘îwa·VÛvÁsÞUõânœ¨®‹`=^¿Ž;ękÞ/((I«¨«‰ú÷£× &ÝBœ£WãËqÖkõÑ­åڊ2qj^ouÚ;IǛ…<mx´bY¼˜Æiڏ¡ÙöIÜÿ¿2ÛÿeÍ®¿NNWu*¾ÑA»6ƒ$×ù{,ÄùøŽâxdç>85
+ÇGŒBhÍ%°^‚/j3-Ÿ¹2-wÙ,se[šGúG¨2R3Ò<28r|Dö§‹É–ÞwY”÷Áù¾+ÖòÞ—å܅óF.HÎ ù® ®i–?Ì=_ÿǹRýyêߕÆ-ʛ–7©88_›–à:÷<ªÔò³ÚtËs/dZƟ†ÚážáaI8Gç¹,''kNzOöŸ<zòøICω{O ”°ÿIz”'Á¨<îx|äqi`hÿ:5ôú”{ÜqœÞûèУôÔ£¯?Jsq<B> §zý!ZslðÍ=æ=öâ±ñcò‘»R-µw÷ ¼xº’,?:`¶ô<0~@bw8ï w@ÏàÀ Ý?§_¤5{›÷z÷J;]㖣;`û­3-¿ÃâÇx»K-ݮٖx˜V—?­Þ/Õë±Ì-˜ÖŒa•k¦¥±¡ÒҀ瘼èzêDΓê½(’C¢#KǗRçÒÙs\Î¥i™®sÎåµPå²Z*‘çB Ç]pÞ5â¢.ˆÍ›ZJ}dž‚ +{R,Å¡4+ýŠ¬(¹JâU•óʸbp`܈"y ÆaÿcËë²³Ɨ-2Ô6ÁmCiuüè\Ú0¤¿mˆÔ74ÞôÀ¾•;n¿”%-Ê«»i¨%iåâ¡vœ@ 2é±XR¶2àôŠ$$í÷s&F•(Fšü˜Œhþ€/½ÄŸí€ß