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

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

Mise au point des commentaires.

  • Property svn:eol-style set to native
File size: 13.2 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           : 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 éléments 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        }
147
148        return $cle_primaire;
149}
150
151
152/**
153 * Initialise un élément d'une table donnée avec les valeurs par défaut configurées dans
154 * la déclaration de la base ou avec une valeur prédéfinie par type.
155 *
156 * @param string $table
157 *      Nom de la table concernée par la lecture sans le préfixe `spip_`.
158 * @param array  $config_champs
159 *      Configuration de la correspondance entre le nom de la donnée dans la source
160 *      et celui du champ dans la table.
161 *
162 * @return array
163 */
164function initialiser_enregistrement($table, $config_champs) {
165
166        $enregistrement = array();
167
168        // Acquisition de la description de la table (champs, clés, jointures) et définition
169        // de la regexp permettant d'isoler la valeur par défaut si elle existe.
170        $description = sql_showtable("spip_${table}");
171        $regexp_default = '/DEFAULT\s+\'(.*)\'/i';
172
173        if (!empty($description['field'])) {
174                foreach ($config_champs as $_champ) {
175                        if (isset($description['field'][$_champ])) {
176                                // On normalise la description du champ en supprimant les espaces inutiles
177                                $description['field'][$_champ] = preg_replace('/\s2,/', ' ', $description['field'][$_champ]);
178                                $description_champ = explode(' ', $description['field'][$_champ]);
179
180                                // On compare maintenant avec le format du champ
181                                if (isset($description_champ[0])) {
182                                        $type = strtoupper($description_champ[0]);
183
184                                        // On cherche une instruction DEFAULT
185                                        $defaut = null;
186                                        if (preg_match($regexp_default, $description['field'][$_champ], $matches)) {
187                                                $defaut = $matches[1];
188                                        }
189
190                                        // On finalise l'initialisation du champ en fonction de son type
191                                        if ((strpos($type, 'CHAR') !== false) or (strpos($type, 'TEXT') !== false)
192                                                or (strpos($type, 'BLOB') !== false) or (strpos($type, 'BINARY') !== false)
193                                        ) {
194                                                $enregistrement[$_champ] = ($defaut != null) ? $defaut : '';
195                                        } elseif (strpos($type, 'DATE') !== false) {
196                                                $enregistrement[$_champ] = ($defaut != null) ? $defaut : '0000-00-00 00:00:00';
197                                        } else {
198                                                $enregistrement[$_champ] = ($defaut != null) ? intval($defaut) : 0;
199                                        }
200                                } else {
201                                        // On a un problème de configuration: on le trace et on arrête la boucle
202                                        // La table ne sera pas mise à jour.
203                                        $enregistrement = array();
204                                        spip_log("La description du champ <${_champ}> de la table <${table}> est mal formée", 'isocode' . _LOG_ERREUR);
205                                        break;
206                                }
207                        } else {
208                                // On a un problème de configuration: on le trace et on arrête la boucle.
209                                // La table ne sera pas mise à jour.
210                                $enregistrement = array();
211                                spip_log("Le champ <${_champ}> n'est pas un champ de la table <${table}>", 'isocode' . _LOG_ERREUR);
212                                break;
213                        }
214                }
215        }
216
217        return $enregistrement;
218}
219
220/**
221 * @param $service
222 * @param $table
223 * @param $config
224 *      Configuration de la méthode de lecture de la source pour la table concernée.
225 *
226 * @return array
227 */
228function extraire_contenu_source($service, $table, $config) {
229
230        // Initialisation des données de sortie
231        $contenu = array();
232        $titres = array();
233        $sha = false;
234
235        if ($config['populating'] == 'page_text') {
236                // Acquisition de la page ciblée par l'url
237                include_spip('inc/distant');
238                $options = array();
239                $flux = recuperer_url($config['url'], $options);
240                if (!empty($flux['page']) and ($sha = sha1($flux['page']))) {
241                        // Chaque élément est identifié soit par un délimiteur, soit par une regexp suivant la méthode configurée.
242                        if ($config['parsing']['element']['method'] == 'explode') {
243                                // On récupére donc un tableau des éléments à lire en utilisant la fonction explode
244                                $contenu = explode($config['parsing']['element']['delimiter'], $flux['page']);
245                        } else {
246                                // TODO : c'est une regexp... à compléter
247                        }
248                }
249        } else {
250                // La source est un fichier.
251                // On construit son nom et on lit son contenu en fonction du type du fichier.
252                $fichier = find_in_path("services/${service}/${table}{$config['extension']}");
253                if (file_exists($fichier) and ($sha = sha1_file($fichier))) {
254                        if ($config['populating'] == 'file_csv') {
255                                $lignes = file($fichier);
256                                if ($lignes) {
257                                        // La première ligne d'un CSV contient toujours les titres des colonnes.
258                                        // On sauvegarde ces titres dans une variable et on élimine la ligne du contenu retourné.
259                                        $titres = explode($config['delimiter'], trim(array_shift($lignes), "\r\n"));
260                                        $titres = array_map('trim', $titres);
261                                        // On renvoie le contenu sans titre
262                                        $contenu = $lignes;
263                                }
264                        } elseif ($config['populating'] == 'file_xml') {
265                                include_spip('inc/flock');
266                                lire_fichier($fichier, $xml);
267                                $arbre = json_decode(json_encode(simplexml_load_string($xml)), true);
268
269                                include_spip('inc/filtres');
270                                if (table_valeur($arbre, $config['base'], '')) {
271                                        $contenu = table_valeur($arbre, $config['base'], '');
272                                }
273                        }
274                }
275        }
276
277        return array($contenu, $titres, $sha);
278}
279
280
281/**
282 * @param $contenu
283 * @param $titres
284 * @param $config
285 *
286 * @return array
287 */
288function extraire_element($contenu, $titres, $config) {
289
290        $element = array();
291
292        if ($config['populating'] == 'file_csv') {
293                // Chaque valeur de colonne est séparée par le délimiteur configuré.
294                $valeurs = explode($config['delimiter'], trim($contenu, "\r\n"));
295                // On construit un tableau associatif [nom colonne] => valeur colonne
296                foreach ($titres as $_cle => $_titre) {
297                        $element[$_titre] = trim($valeurs[$_cle]);
298                }
299        } elseif ($config['populating'] == 'page_text') {
300                // Chaque couple (nom donnée, valeur donnée) est identifiée par une REGEXP configurée
301                if (preg_match_all($config['parsing']['field']['regexp'], $contenu, $matches)) {
302                        // L'index 1 correspond à la liste des titres et l'index 2 à la liste des
303                        // valeurs correspondantes.
304                        // Il faut donc reconstruire un tableau associatif [nom] => valeur
305                        foreach ($matches[1] as $_cle => $_titre) {
306                                $element[trim($_titre)] = $matches[2][trim($_cle)];
307                        }
308                }
309        } else {
310                // Fichier XML.
311                // Le tableau associatif (nom donnée, valeur donnée) est déjà correctement formé.
312                $element = $contenu;
313        }
314
315        return $element;
316}
317
318
319/**
320 * Compare le sha passé en argument pour la table concernée avec le sha stocké dans la meta
321 * pour cette même table.
322 *
323 * @api
324 *
325 * @param string $sha
326 *      SHA à comparer à celui de la table.
327 * @param string $table
328 *      Nom de la table sans préfixe `spip_` dont il faut comparer le sha
329 *      stocké dans sa meta de chargement.
330 *
331 * @return bool
332 *      `true` si le sha passé en argument est identique au sha stocké pour la table choisie, `false` sinon.
333 */
334function sha_identique($sha, $table) {
335
336        $sha_identique = false;
337
338        // On récupère le sha de la table dans les metas si il existe (ie. la table a été chargée)
339        include_spip('inc/config');
340        $sha_stocke = lire_config("isocode/tables/${table}/sha", false);
341
342        if ($sha_stocke and ($sha == $sha_stocke)) {
343                $sha_identique = true;
344        }
345
346        return $sha_identique;
347}
Note: See TracBrowser for help on using the repository browser.