source: spip-zone/_plugins_/isocode/trunk/inc/isocode_sourcer.php @ 100373

Last change on this file since 100373 was 100373, checked in by eric@…, 3 years ago

Renommer l'action vider en decharger car sinon il y a collision avec la fonction d'administration isocode_vider_table.

  • Property svn:eol-style set to native
File size: 13.3 KB
Line 
1<?php
2/**
3 * Ce fichier contient la fonction générique de lecture d'un fichier ou d'une page HTML source
4 * en un tableau d'éléments prêt à être inséré dans une table de la base de données.
5 *
6 * Dans les fonctions du package, les conventions suivantes sont utilisées:
7 * - source          : désigne le fichier ou la page HTML à partir duquel la table des codes ISO est remplie.
8 * - contenu         : le contenu de la source sous quelque forme que ce soit.
9 * - élément         : un élément du contenu destiné à devenir un enregistrement de la table concernée. Un élément
10 *                     est un tableau de couples (nom, valeur).
11 * - éléments        : liste des éléments constitutifs du contenu de la source.
12 * - enregistrement  : un tableau de couples (champ, valeur) pour un sous-ensemble des champs d'une table.
13 * - enregistrements : la liste des enregistrements à insérer dans la table concernée.
14 * - titre ou nom    : le libellé de la donnée dans la source (par ex, le titre d'une colonne dans un fichier CSV).
15 *
16 * @package SPIP\ISOCODE\SOURCE
17 */
18if (!defined('_ECRIRE_INC_VERSION')) {
19        return;
20}
21
22
23/**
24 * Constitue, à partir, d'un fichier CSV ou XML ou d'une page HTML au format texte, un tableau des éléments
25 * prêt à être inséré dans une table de la base de données.
26 * La fonction utilise le service et le nom de table pour récupérer la configuration permettant l'analyse
27 * du fichier et sa traduction en élements de la table (délimiteur ou regexp, nom des colonnes...).
28 * Il est possible, pour chaque élément ou pour l'ensemble d'appliquer une fonction spécifique à la table
29 * qui complète l'élément.
30 *
31 * @param string $service
32 *      Nom du service associé à la lecture de la table.
33 * @param string $table
34 *      Nom de la table concernée par la lecture de la source.
35 *
36 * @return array
37 *      Tableau à deux éléments:
38 *      - index 0 : la liste des éléments à enregistrer dans la table concernée
39 *      - index 1 : le sha256 de la source des éléments de la table
40 *      - index 2 : indicateur de sha identique
41 */
42function lire_source($service, $table) {
43
44        // Initialisations
45        $enregistrements = array();
46        $completer_enregistrement = "${table}_completer_enregistrement";
47        $completer_table = "${table}_completer_table";
48        $source_identique = true;
49
50        // Inclusion des configurations et des fonctions spécifiques au service qui fournit les données
51        // de la table à remplir.
52        include_spip("services/${service}/${service}_api");
53
54        // Acquisition de la configuration de lecture pour la table concernée.
55        $config = $GLOBALS['isocode'][$service]['tables'][$table];
56
57        // Détermination de la clé primaire de la table.
58        $cle_primaire_table = obtenir_cle_primaire($table);
59
60        // Initialisation d'un élément de la table par défaut (uniquement les champs de base).
61        // Cela permet de s'assurer que chaque élément du tableau de sortie aura la même structure
62        // quelque soit les données lues dans la source.
63        $enregistrement_defaut = initialiser_enregistrement($table, $config['basic_fields']);
64
65        // Récupération du contenu du fichier ou de la page HTML source et du sha associé. Pour les fichiers CSV
66        // on renvoie aussi la liste des titres des colonnes qui existe toujours.
67        list($contenu, $titres, $sha) = extraire_contenu_source($service, $table, $config);
68        if ($contenu and $sha and $enregistrement_defaut) {
69                // On n'analyse le contenu que si celui-ci a changé (sha différent de celui stocké).
70                if (!sha_identique($sha, $table)) {
71                        $source_identique = false;
72                        $liste_cles_primaires = array();
73                        foreach ($contenu as $_contenu) {
74                                // Pour chaque élément on récupère un tableau associatif [titre colonne] = valeur colonne.
75                                $element = extraire_element($_contenu, $titres, $config);
76                                // Création de chaque enregistrement de la table
77                                $enregistrement = $enregistrement_defaut;
78                                $cle_primaire_existe = false;
79                                $cle_primaire = array();
80                                foreach ($element as $_titre => $_valeur) {
81                                        $titre = trim($_titre);
82                                        // Seuls les champs identifiés dans la configuration sont récupérés dans le fichier
83                                        if (isset($config['basic_fields'][$titre])) {
84                                                // Si la valeur n'est pas vide on l'affecte au champ sinon on laisse la valeur par défaut
85                                                // déjà initialisée.
86                                                if ($_valeur) {
87                                                        $enregistrement[$config['basic_fields'][$titre]] = trim($_valeur);
88                                                }
89                                                // Vérifier si le champ en cours fait partie de la clé primaire et élaborer la clé
90                                                // primaire de l'élément en cours
91                                                if (in_array($config['basic_fields'][$titre], $cle_primaire_table)) {
92                                                        $cle_primaire[$config['basic_fields'][$titre]] = $_valeur;
93                                                        if (count($cle_primaire) == count($cle_primaire_table)) {
94                                                                $cle_primaire_existe = true;
95                                                        }
96                                                }
97                                        } else {
98                                                spip_log("Le champ <${_titre}> n'existe pas dans la configuration de la table ${table}", 'isocode' . _LOG_INFO);
99                                        }
100                                }
101                                // On ajoute l'élément que si la clé primaire a bien été trouvée et si la valeur de cette clé
102                                // n'est pas en doublon avec un élément déjà enregistré.
103                                if ($cle_primaire_existe) {
104                                        // On tri la clé primaire et on la transforme en chaine pour la tester et la stocker
105                                        ksort($cle_primaire);
106                                        $cle_primaire = implode(',', $cle_primaire);
107                                        if (!in_array($cle_primaire, $liste_cles_primaires)) {
108                                                // On rajoute cette clé dans la liste
109                                                $liste_cles_primaires[] = $cle_primaire;
110                                                // Si besoin on appelle une fonction pour chaque enregistrement afin de le compléter
111                                                if (function_exists($completer_enregistrement)) {
112                                                        $enregistrement = $completer_enregistrement($enregistrement, $config);
113                                                }
114                                                $enregistrements[] = $enregistrement;
115                                        } else {
116                                                spip_log("L'élément de clé primaire <${cle_primaire}> de la table <${table}> est en doublon", 'isocode' . _LOG_ERREUR);
117                                        }
118                                } else {
119                                        spip_log("L'élément <" . var_export($_contenu, true) . "> de la table <${table}> n'a pas de clé primaire", 'isocode' . _LOG_ERREUR);
120                                }
121                        }
122                        // Si besoin on appelle une fonction pour toute la table
123                        if (function_exists($completer_table)) {
124                                $enregistrements = $completer_table($enregistrements, $config);
125                        }
126                }
127        }
128
129        return array($enregistrements, $sha, $source_identique);
130}
131
132
133/**
134 * @param $table
135 *
136 * @return array
137 */
138function obtenir_cle_primaire($table) {
139
140        include_spip('base/objets');
141        $cle_primaire = array();
142
143        if ($id_table = id_table_objet($table)) {
144                // On stocke la clé sous forme de liste pour les tests d'appartenance.
145                $cle_primaire = explode(',', $id_table);
146                // On trie la liste et on recompose la clé sous forme de chaine pour la gestion des doublons.
147//              sort($cle_primaire);
148        }
149
150        return $cle_primaire;
151}
152
153
154/**
155 * Initialise un élément d'une table donnée avec les valeurs par défaut configurées dans
156 * la déclaration de la base ou avec une valeur prédéfinie par type.
157 *
158 * @param string $table
159 *      Nom de la table concernée par la lecture sans le préfixe `spip_`.
160 * @param array  $config_champs
161 *      Configuration de la correspondance entre le nom de la donnée dans la source
162 *      et celui du champ dans la table.
163 *
164 * @return array
165 */
166function initialiser_enregistrement($table, $config_champs) {
167
168        $enregistrement = array();
169
170        // Acquisition de la description de la table (champs, clés, jointures) et définition
171        // de la regexp permettant d'isoler la valeur par défaut si elle existe.
172        $description = sql_showtable("spip_${table}");
173        $regexp_default = '/DEFAULT\s+\'(.*)\'/i';
174
175        if (!empty($description['field'])) {
176                foreach ($config_champs as $_champ) {
177                        if (isset($description['field'][$_champ])) {
178                                // On normalise la description du champ en supprimant les espaces inutiles
179                                $description['field'][$_champ] = preg_replace('/\s2,/', ' ', $description['field'][$_champ]);
180                                $description_champ = explode(' ', $description['field'][$_champ]);
181
182                                // On compare maintenant avec le format du champ
183                                if (isset($description_champ[0])) {
184                                        $type = strtoupper($description_champ[0]);
185
186                                        // On cherche une instruction DEFAULT
187                                        $defaut = null;
188                                        if (preg_match($regexp_default, $description['field'][$_champ], $matches)) {
189                                                $defaut = $matches[1];
190                                        }
191
192                                        // On finalise l'initialisation du champ en fonction de son type
193                                        if ((strpos($type, 'CHAR') !== false) or (strpos($type, 'TEXT') !== false)
194                                                or (strpos($type, 'BLOB') !== false) or (strpos($type, 'BINARY') !== false)
195                                        ) {
196                                                $enregistrement[$_champ] = ($defaut != null) ? $defaut : '';
197                                        } elseif (strpos($type, 'DATE') !== false) {
198                                                $enregistrement[$_champ] = ($defaut != null) ? $defaut : '0000-00-00 00:00:00';
199                                        } else {
200                                                $enregistrement[$_champ] = ($defaut != null) ? intval($defaut) : 0;
201                                        }
202                                } else {
203                                        // On a un problème de configuration: on le trace et on arrête la boucle
204                                        // La table ne sera pas mise à jour.
205                                        $enregistrement = array();
206                                        spip_log("La description du champ <${_champ}> de la table <${table}> est mal formée", 'isocode' . _LOG_ERREUR);
207                                        break;
208                                }
209                        } else {
210                                // On a un problème de configuration: on le trace et on arrête la boucle.
211                                // La table ne sera pas mise à jour.
212                                $enregistrement = array();
213                                spip_log("Le champ <${_champ}> n'est pas un champ de la table <${table}>", 'isocode' . _LOG_ERREUR);
214                                break;
215                        }
216                }
217        }
218
219        return $enregistrement;
220}
221
222/**
223 * @param $service
224 * @param $table
225 * @param $config
226 *      Configuration de la méthode de lecture de la source pour la table concernée.
227 *
228 * @return array
229 */
230function extraire_contenu_source($service, $table, $config) {
231
232        // Initialisation des données de sortie
233        $contenu = array();
234        $titres = array();
235        $sha = false;
236
237        if ($config['populating'] == 'page_text') {
238                // Acquisition de la page ciblée par l'url
239                include_spip('inc/distant');
240                $options = array();
241                $flux = recuperer_url($config['url'], $options);
242                if (!empty($flux['page']) and ($sha = sha1($flux['page']))) {
243                        // Chaque élément est identifié soit par un délimiteur, soit par une regexp suivant la méthode configurée.
244                        if ($config['parsing']['element']['method'] == 'explode') {
245                                // On récupére donc un tableau des éléments à lire en utilisant la fonction explode
246                                $contenu = explode($config['parsing']['element']['delimiter'], $flux['page']);
247                        } else {
248                                // TODO : c'est une regexp... à compléter
249                        }
250                }
251        } else {
252                // La source est un fichier.
253                // On construit son nom et on lit son contenu en fonction du type du fichier.
254                $fichier = find_in_path("services/${service}/${table}{$config['extension']}");
255                if (file_exists($fichier) and ($sha = sha1_file($fichier))) {
256                        if ($config['populating'] == 'file_csv') {
257                                $lignes = file($fichier);
258                                if ($lignes) {
259                                        // La première ligne d'un CSV contient toujours les titres des colonnes.
260                                        // On sauvegarde ces titres dans une variable et on élimine la ligne du contenu retourné.
261                                        $titres = explode($config['delimiter'], trim(array_shift($lignes), "\r\n"));
262                                        $titres = array_map('trim', $titres);
263                                        // On renvoie le contenu sans titre
264                                        $contenu = $lignes;
265                                }
266                        } elseif ($config['populating'] == 'file_xml') {
267                                include_spip('inc/flock');
268                                lire_fichier($fichier, $xml);
269                                $arbre = json_decode(json_encode(simplexml_load_string($xml)), true);
270
271                                include_spip('inc/filtres');
272                                if (table_valeur($arbre, $config['base'], '')) {
273                                        $contenu = table_valeur($arbre, $config['base'], '');
274                                }
275                        }
276                }
277        }
278
279        return array($contenu, $titres, $sha);
280}
281
282
283/**
284 * @param $contenu
285 * @param $titres
286 * @param $config
287 *
288 * @return array
289 */
290function extraire_element($contenu, $titres, $config) {
291
292        $element = array();
293
294        if ($config['populating'] == 'file_csv') {
295                // Chaque valeur de colonne est séparée par le délimiteur configuré.
296                $valeurs = explode($config['delimiter'], trim($contenu, "\r\n"));
297                // On construit un tableau associatif [nom colonne] => valeur colonne
298                foreach ($titres as $_cle => $_titre) {
299                        $element[$_titre] = trim($valeurs[$_cle]);
300                }
301        } elseif ($config['populating'] == 'page_text') {
302                // Chaque couple (nom donnée, valeur donnée) est identifiée par une REGEXP configurée
303                if (preg_match_all($config['parsing']['field']['regexp'], $contenu, $matches)) {
304                        // L'index 1 correspond à la liste des titres et l'index 2 à la liste des
305                        // valeurs correspondantes.
306                        // Il faut donc reconstruire un tableau associatif [nom] => valeur
307                        foreach ($matches[1] as $_cle => $_titre) {
308                                $element[trim($_titre)] = $matches[2][trim($_cle)];
309                        }
310                }
311        } else {
312                // Fichier XML.
313                // Le tableau associatif (nom donnée, valeur donnée) est déjà correctement formé.
314                $element = $contenu;
315        }
316
317        return $element;
318}
319
320
321/**
322 * Compare le sha passé en argument pour la table concernée avec le sha stocké dans la meta
323 * pour cette même table.
324 *
325 * @api
326 *
327 * @param string $sha
328 *      SHA à comparer à celui de la table.
329 * @param string $table
330 *      Nom de la table sans préfixe `spip_` dont il faut comparer le sha
331 *      stocké dans sa meta de chargement.
332 *
333 * @return bool
334 *      `true` si le sha passé en argument est identique au sha stocké pour la table choisie, `false` sinon.
335 */
336function sha_identique($sha, $table) {
337
338        $sha_identique = false;
339
340        // On récupère le sha de la table dans les metas si il existe (ie. la table a été chargée)
341        include_spip('inc/config');
342        $sha_stocke = lire_config("isocode/tables/${table}/sha", false);
343
344        if ($sha_stocke and ($sha == $sha_stocke)) {
345                $sha_identique = true;
346        }
347
348        return $sha_identique;
349}
Note: See TracBrowser for help on using the repository browser.