[SPIP] v3.2.1-->v3.2.2
[lhc/web/www.git] / www / ecrire / base / upgrade.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2019 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
8 * *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
12
13 /**
14 * Mise à jour de la base de données
15 *
16 * @package SPIP\Core\SQL\Upgrade
17 */
18
19 if (!defined('_ECRIRE_INC_VERSION')) {
20 return;
21 }
22
23 /**
24 * Programme de mise à jour des tables SQL lors d'un changement de version.
25 *
26 * L'entrée dans cette fonction est reservée aux mises à jour de SPIP coeur.
27 *
28 * Marche aussi pour les plugins en appelant directement la fonction `maj_plugin`
29 * Pour que ceux-ci profitent aussi de la reprise sur interruption,
30 * ils doivent simplement indiquer leur numero de version installée dans une meta
31 * et fournir le tableau `$maj` à la fonction `maj_plugin`.
32 * La reprise sur timeout se fait alors par la page admin_plugin et jamais par ici.
33 *
34 * @uses creer_base()
35 * @uses maj_base()
36 * @uses auth_synchroniser_distant()
37 *
38 * @param string $titre
39 * @param string $reprise Inutilisé
40 * @return void
41 */
42 function base_upgrade_dist($titre = '', $reprise = '') {
43 if (!$titre) {
44 return;
45 } // anti-testeur automatique
46 if ($GLOBALS['spip_version_base'] != $GLOBALS['meta']['version_installee']) {
47 if (!is_numeric(_request('reinstall'))) {
48 include_spip('base/create');
49 spip_log('recree les tables eventuellement disparues', 'maj.' . _LOG_INFO_IMPORTANTE);
50 creer_base();
51 }
52
53 // quand on rentre par ici, c'est toujours une mise a jour de SPIP
54 // lancement de l'upgrade SPIP
55 $res = maj_base();
56
57 if ($res) {
58 // on arrete tout ici !
59 exit;
60 }
61 }
62 spip_log('Fin de mise a jour SQL. Debut m-a-j acces et config', 'maj.' . _LOG_INFO_IMPORTANTE);
63
64 // supprimer quelques fichiers temporaires qui peuvent se retrouver invalides
65 @spip_unlink(_CACHE_RUBRIQUES);
66 @spip_unlink(_CACHE_PIPELINES);
67 @spip_unlink(_CACHE_PLUGINS_PATH);
68 @spip_unlink(_CACHE_PLUGINS_OPT);
69 @spip_unlink(_CACHE_PLUGINS_FCT);
70 @spip_unlink(_CACHE_CHEMIN);
71 @spip_unlink(_DIR_TMP . 'plugin_xml_cache.gz');
72
73 include_spip('inc/auth');
74 auth_synchroniser_distant();
75 $config = charger_fonction('config', 'inc');
76 $config();
77 }
78
79 /**
80 * Mise à jour de base de SPIP
81 *
82 * Exécute toutes les fonctions de mises à jour de SPIP nécessaires,
83 * en fonction de la meta `version_installee` indiquant le numéro de
84 * schéma actuel de la base de données.
85 *
86 * Les fonctions de mises à jour se trouvent dans `ecrire/maj/`
87 *
88 * @uses upgrade_test()
89 * @uses maj_while()
90 *
91 * @param int $version_cible
92 * @param string $redirect
93 * @return array|bool
94 */
95 function maj_base($version_cible = 0, $redirect = '') {
96
97 $version_installee = @$GLOBALS['meta']['version_installee'];
98 //
99 // Si version nulle ou inexistante, c'est une nouvelle installation
100 // => ne pas passer par le processus de mise a jour.
101 // De meme en cas de version superieure: ca devait etre un test,
102 // il y a eu le message d'avertissement il doit savoir ce qu'il fait
103 //
104 // version_installee = 1.702; quand on a besoin de forcer une MAJ
105
106 spip_log(
107 "Version anterieure: $version_installee. Courante: " . $GLOBALS['spip_version_base'],
108 'maj.' . _LOG_INFO_IMPORTANTE
109 );
110 if (!$version_installee or ($GLOBALS['spip_version_base'] < $version_installee)) {
111 sql_replace(
112 'spip_meta',
113 array(
114 'nom' => 'version_installee',
115 'valeur' => $GLOBALS['spip_version_base'],
116 'impt' => 'non'
117 )
118 );
119 return false;
120 }
121 if (!upgrade_test()) {
122 return true;
123 }
124
125 $cible = ($version_cible ? $version_cible : $GLOBALS['spip_version_base']);
126
127 if ($version_installee <= 1.926) {
128 $n = floor($version_installee * 10);
129 while ($n < 19) {
130 $nom = sprintf('v%03d', $n);
131 $f = charger_fonction($nom, 'maj', true);
132 if ($f) {
133 spip_log("$f repercute les modifications de la version " . ($n / 10), 'maj.' . _LOG_INFO_IMPORTANTE);
134 $f($version_installee, $GLOBALS['spip_version_base']);
135 } else {
136 spip_log("pas de fonction pour la maj $n $nom", 'maj.' . _LOG_INFO_IMPORTANTE);
137 }
138 $n++;
139 }
140 include_spip('maj/v019_pre193');
141 v019_pre193($version_installee, $version_cible);
142 }
143 if ($version_installee < 2000) {
144 if ($version_installee < 2) {
145 $version_installee = $version_installee * 1000;
146 }
147 include_spip('maj/v019');
148 }
149 if ($cible < 2) {
150 $cible = $cible * 1000;
151 }
152
153 include_spip('maj/svn10000');
154 ksort($GLOBALS['maj']);
155 $res = maj_while($version_installee, $cible, $GLOBALS['maj'], 'version_installee', 'meta', $redirect, true);
156 if ($res) {
157 if (!is_array($res)) {
158 spip_log("Pb d'acces SQL a la mise a jour", 'maj.' . _LOG_INFO_ERREUR);
159 } else {
160 echo _T('avis_operation_echec') . ' ' . join(' ', $res);
161 echo install_fin_html();
162 }
163 }
164
165 return $res;
166 }
167
168 /**
169 * Mise à jour d'un plugin de SPIP
170 *
171 * Fonction appelée par la fonction de mise à jour d'un plugin.
172 * On lui fournit un tableau de fonctions élementaires
173 * dont l'indice est la version.
174 *
175 * @uses maj_while()
176 *
177 * @param string $nom_meta_base_version
178 * Nom de la meta informant de la version du schéma de données du plugin installé dans SPIP
179 * @param string $version_cible
180 * Version du schéma de données dans le plugin (déclaré dans paquet.xml)
181 * @param array $maj
182 * Tableau d'actions à faire à l'installation (clé `create`) et pour chaque
183 * version intermédiaire entre la version actuelle du schéma du plugin dans SPIP
184 * et la version du schéma déclaré dans le plugin (ex. clé `1.1.0`).
185 *
186 * Chaque valeur est un tableau contenant une liste de fonctions à exécuter,
187 * cette liste étant elle-même un tableau avec premier paramètre le nom de la fonction
188 * et les suivant les paramètres à lui passer
189 *
190 * Exemple :
191 *
192 * ```
193 * array(
194 * 'create' => array(
195 * array('maj_tables', array('spip_rubriques', 'spip_articles')),
196 * array('creer_base)),
197 * '1.1.0' => array(
198 * array('sql_alter', 'TABLE spip_articles ADD INDEX truc (truc)'))
199 * )
200 * ```
201 * @param string $table_meta
202 * Nom de la table meta (sans le prefixe spip_) dans laquelle trouver la meta $nom_meta_base_version
203 * @return void
204 */
205 function maj_plugin($nom_meta_base_version, $version_cible, $maj, $table_meta = 'meta') {
206
207 if ($table_meta !== 'meta') {
208 lire_metas($table_meta);
209 }
210
211 $current_version = null;
212
213 if ((!isset($GLOBALS[$table_meta][$nom_meta_base_version]))
214 || (!spip_version_compare($current_version = $GLOBALS[$table_meta][$nom_meta_base_version], $version_cible, '='))
215 ) {
216 // $maj['create'] contient les directives propres a la premiere creation de base
217 // c'est une operation derogatoire qui fait aboutir directement dans la version_cible
218 if (isset($maj['create'])) {
219 if (!isset($GLOBALS[$table_meta][$nom_meta_base_version])) {
220 // installation : on ne fait que l'operation create
221 $maj = array('init' => $maj['create']);
222 // et on lui ajoute un appel a inc/config
223 // pour creer les metas par defaut
224 $config = charger_fonction('config', 'inc');
225 $maj[$version_cible] = array(array($config));
226 }
227 // dans tous les cas enlever cet index du tableau
228 unset($maj['create']);
229 }
230 // si init, deja dans le bon ordre
231 if (!isset($maj['init'])) {
232 include_spip('inc/plugin'); // pour spip_version_compare
233 uksort($maj, 'spip_version_compare');
234 }
235
236 // la redirection se fait par defaut sur la page d'administration des plugins
237 // sauf lorsque nous sommes sur l'installation de SPIP
238 // ou define _REDIRECT_MAJ_PLUGIN
239 $redirect = (defined('_REDIRECT_MAJ_PLUGIN') ? _REDIRECT_MAJ_PLUGIN : generer_url_ecrire('admin_plugin'));
240 if (defined('_ECRIRE_INSTALL')) {
241 $redirect = parametre_url(generer_url_ecrire('install'), 'etape', _request('etape'));
242 }
243
244 $res = maj_while($current_version, $version_cible, $maj, $nom_meta_base_version, $table_meta, $redirect);
245 if ($res) {
246 if (!is_array($res)) {
247 spip_log("Pb d'acces SQL a la mise a jour", 'maj.' . _LOG_INFO_ERREUR);
248 } else {
249 echo '<p>' . _T('avis_operation_echec') . ' ' . join(' ', $res) . '</p>';
250 }
251 }
252 }
253 }
254
255 /**
256 * Relancer le hit de mise à jour avant timeout
257 *
258 * si pas de redirect fourni, on redirige vers `exec=upgrade` pour finir
259 * ce qui doit être une mise à jour SPIP
260 *
261 * @uses redirige_formulaire()
262 *
263 * @param string $meta
264 * @param string $table
265 * @param string $redirect
266 * @return void
267 */
268 function relance_maj($meta, $table, $redirect = '') {
269 include_spip('inc/headers');
270 if (!$redirect) {
271 // recuperer la valeur installee en cours
272 // on la tronque numeriquement, elle ne sert pas reellement
273 // sauf pour verifier que ce n'est pas oui ou non
274 // sinon is_numeric va echouer sur un numero de version 1.2.3
275 $installee = intval($GLOBALS[$table][$meta]);
276 $redirect = generer_url_ecrire('upgrade', "reinstall=$installee&meta=$meta&table=$table", true);
277 }
278 echo redirige_formulaire($redirect);
279 exit();
280 }
281
282 /**
283 * Initialiser la page pour l'affichage des progrès de l'upgrade
284 * uniquement si la page n'a pas déjà été initilalisée
285 *
286 * @param string $installee
287 * @param string $meta
288 * @param string $table
289 * @return void
290 */
291 function maj_debut_page($installee, $meta, $table) {
292 static $done = false;
293 if ($done) {
294 return;
295 }
296 include_spip('inc/minipres');
297 @ini_set('zlib.output_compression', '0'); // pour permettre l'affichage au fur et a mesure
298 $timeout = _UPGRADE_TIME_OUT * 2;
299 $titre = _T('titre_page_upgrade');
300 $balise_img = charger_filtre('balise_img');
301 $titre .= $balise_img(chemin_image('searching.gif'));
302 echo(install_debut_html($titre));
303 // script de rechargement auto sur timeout
304 $redirect = generer_url_ecrire('upgrade', "reinstall=$installee&meta=$meta&table=$table", true);
305 echo http_script("window.setTimeout('location.href=\"" . $redirect . "\";'," . ($timeout * 1000) . ')');
306 echo "<div style='text-align: left'>\n";
307 if (ob_get_level()) {
308 ob_flush();
309 }
310 flush();
311 $done = true;
312 }
313
314 if (!defined('_UPGRADE_TIME_OUT')) {
315 /**
316 * Durée en secondes pour relancer les scripts de mises à jour, x secondes
317 * avant que la durée d'exécution du script provoque un timeout
318 *
319 * @var int
320 **/
321 define('_UPGRADE_TIME_OUT', 20);
322 }
323
324 /**
325 * Gestion des mises à jour de SPIP et des plugins
326 *
327 * À partir des versions > 1.926 (i.e SPIP > 1.9.2), cette fonction gere les MAJ.
328 *
329 * Se relancer soi-même pour éviter l'interruption pendant une operation SQL
330 * (qu'on espère pas trop longue chacune) évidemment en ecrivant dans la meta
331 * à quel numero on en est.
332 *
333 * Cette fonction peut servir aux plugins qui doivent donner comme arguments :
334 *
335 * 1. le numero de version courant (numéro de version 1.2.3 ou entier)
336 * 2. le numero de version à atteindre (numéro de version 1.2.3 ou entier)
337 * 3. le tableau des instructions de mise à jour à exécuter
338 * Pour profiter du mécanisme de reprise sur interruption il faut de plus
339 * 4. le nom de la meta permettant de retrouver tout ca
340 * 5. la table des meta ou elle se trouve (`$table_prefix . '_meta'` par défaut)
341 * (cf début de fichier)
342 *
343 * les fonctions `sql_xx` appelées lors des mises à jour sont supposées
344 * atomiques et ne sont pas relancées en cas de timeout, mais les fonctions
345 * spécifiques sont relancées jusqu'à ce qu'elles finissent.
346 * Elles doivent donc s'assurer de progresser à chaque reprise.
347 *
348 * @uses maj_debut_page()
349 * @uses serie_alter()
350 * @uses relance_maj()
351 *
352 * @param string $installee
353 * @param string $cible
354 * @param array $maj
355 * @param string $meta
356 * @param string $table
357 * @param string $redirect
358 * @param bool $debut_page
359 * @return array
360 * - tableau (étape, sous-étape) en cas d'échec,
361 * - tableau vide sinon.
362 */
363 function maj_while($installee, $cible, $maj, $meta = '', $table = 'meta', $redirect = '', $debut_page = false) {
364 # inclusions pour que les procedures d'upgrade disposent des fonctions de base
365 include_spip('base/create');
366 include_spip('base/abstract_sql');
367 $trouver_table = charger_fonction('trouver_table', 'base');
368 include_spip('inc/plugin'); // pour spip_version_compare
369 $n = 0;
370 $time = time();
371
372 if (!defined('_TIME_OUT')) {
373 /**
374 * Définir le timeout qui peut-être utilisé dans les fonctions
375 * de mises à jour qui durent trop longtemps
376 *
377 * À utiliser tel que : `if (time() >= _TIME_OUT)`
378 *
379 * @var int
380 */
381 define('_TIME_OUT', $time + _UPGRADE_TIME_OUT);
382 }
383
384 reset($maj);
385 while (list($v, ) = each($maj)) {
386 // si une maj pour cette version
387 if ($v == 'init' or
388 (spip_version_compare($v, $installee, '>')
389 and spip_version_compare($v, $cible, '<='))
390 ) {
391 if ($debut_page) {
392 maj_debut_page($v, $meta, $table);
393 }
394 echo "MAJ $v";
395 $etape = serie_alter($v, $maj[$v], $meta, $table, $redirect);
396 $trouver_table(''); // vider le cache des descriptions de table
397 # echec sur une etape en cours ?
398 # on sort
399 if ($etape) {
400 return array($v, $etape);
401 }
402 $n = time() - $time;
403 spip_log("$table $meta: $v en $n secondes", 'maj.' . _LOG_INFO_IMPORTANTE);
404 if ($meta) {
405 ecrire_meta($meta, $installee = $v, 'oui', $table);
406 }
407 echo '<br />';
408 }
409 if (time() >= _TIME_OUT) {
410 relance_maj($meta, $table, $redirect);
411 }
412 }
413 $trouver_table(''); // vider le cache des descriptions de table
414 // indispensable pour les chgt de versions qui n'ecrivent pas en base
415 // tant pis pour la redondance eventuelle avec ci-dessus
416 if ($meta) {
417 ecrire_meta($meta, $cible, 'oui', $table);
418 }
419 spip_log("MAJ terminee. $meta: $installee", 'maj.' . _LOG_INFO_IMPORTANTE);
420
421 return array();
422 }
423
424 /**
425 * Appliquer une serie de changements qui risquent de partir en timeout
426 *
427 * Alter crée une copie temporaire d'une table, c'est lourd.
428 *
429 * @uses relance_maj()
430 *
431 * @param string $serie
432 * numero de version upgrade
433 * @param array $q
434 * tableau des operations pour cette version
435 * @param string $meta
436 * nom de la meta qui contient le numero de version
437 * @param string $table
438 * nom de la table meta
439 * @param string $redirect
440 * url de redirection en cas d'interruption
441 * @return int
442 */
443 function serie_alter($serie, $q = array(), $meta = '', $table = 'meta', $redirect = '') {
444 $meta2 = $meta . '_maj_' . $serie;
445 $etape = intval(@$GLOBALS[$table][$meta2]);
446 foreach ($q as $i => $r) {
447 if ($i >= $etape) {
448 $msg = "maj $table $meta2 etape $i";
449 if (is_array($r)
450 and function_exists($f = array_shift($r))
451 ) {
452 // note: $r (arguments de la fonction $f) peut avoir des données tabulaires
453 spip_log("$msg: $f " . @join(',', $r), 'maj.' . _LOG_INFO_IMPORTANTE);
454 // pour les fonctions atomiques sql_xx
455 // on enregistre le meta avant de lancer la fonction,
456 // de maniere a eviter de boucler sur timeout
457 // mais pour les fonctions complexes,
458 // il faut les rejouer jusqu'a achevement.
459 // C'est a elle d'assurer qu'elles progressent a chaque rappel
460 if (strncmp($f, 'sql_', 4) == 0) {
461 ecrire_meta($meta2, $i + 1, 'non', $table);
462 }
463 echo " <span title='$i'>.</span>";
464 call_user_func_array($f, $r);
465 // si temps imparti depasse, on relance sans ecrire en meta
466 // car on est peut etre sorti sur timeout si c'est une fonction longue
467 if (time() >= _TIME_OUT) {
468 relance_maj($meta, $table, $redirect);
469 }
470 ecrire_meta($meta2, $i + 1, 'non', $table);
471 spip_log("$meta2: ok", 'maj.' . _LOG_INFO_IMPORTANTE);
472 } else {
473 if (!is_array($r)) {
474 spip_log("maj $i format incorrect", 'maj.' . _LOG_ERREUR);
475 } else {
476 spip_log("maj $i fonction $f non definie", 'maj.' . _LOG_ERREUR);
477 }
478 // en cas d'erreur serieuse, on s'arrete
479 // mais on permet de passer par dessus en rechargeant la page.
480 return $i + 1;
481 }
482 }
483 }
484 effacer_meta($meta2, $table);
485
486 return 0;
487 }
488
489
490 /**
491 * Mise à jour des types MIME de documents
492 *
493 * Fonction utilisé par les vieilles mises à jour de SPIP, à appeler dans
494 * le tableau `$maj` quand on rajoute des types MIME. Remplacé actuellement
495 * par le plugin Medias.
496 *
497 * @deprecated Utiliser directement `creer_base_types_doc()` du plugin Medias
498 * @example
499 * ```
500 * $GLOBALS['maj'][1953] = array(array('upgrade_types_documents'));
501 *
502 * ```
503 * @uses creer_base_types_doc()
504 *
505 **/
506 function upgrade_types_documents() {
507 if (include_spip('base/medias')
508 and function_exists('creer_base_types_doc')
509 ) {
510 creer_base_types_doc();
511 }
512 }
513
514 /**
515 * Vérifie qu'il est possible d'ajouter une colonne à une table SQL
516 *
517 * @return bool True si possible.
518 **/
519 function upgrade_test() {
520 sql_drop_table('spip_test', true);
521 sql_create('spip_test', array('a' => 'int'));
522 sql_alter('TABLE spip_test ADD b INT');
523 sql_insertq('spip_test', array('b' => 1), array('field' => array('b' => 'int')));
524 $result = sql_select('b', 'spip_test');
525 // ne pas garder le resultat de la requete sinon sqlite3
526 // ne peut pas supprimer la table spip_test lors du sql_alter qui suit
527 // car cette table serait alors 'verouillee'
528 $result = $result ? true : false;
529 sql_alter('TABLE spip_test DROP b');
530
531 return $result;
532 }
533
534 /**
535 * Mise à jour des versions de SPIP < 1.926
536 *
537 * @deprecated Utiliser `maj_plugin()` ou la globale `maj` pour le core.
538 * @see maj_plugin()
539 * @see maj_base()
540 *
541 * @param float $version
542 * @param bool $test
543 * @return void
544 **/
545 function maj_version($version, $test = true) {
546 if ($test) {
547 if ($version >= 1.922) {
548 ecrire_meta('version_installee', $version, 'oui');
549 } else {
550 // on le fait manuellement, car ecrire_meta utilise le champs impt qui est absent sur les vieilles versions
551 $GLOBALS['meta']['version_installee'] = $version;
552 sql_updateq('spip_meta', array('valeur' => $version), 'nom=' . sql_quote('version_installee'));
553 }
554 spip_log("mise a jour de la base en $version", 'maj.' . _LOG_INFO_IMPORTANTE);
555 } else {
556 echo _T('alerte_maj_impossible', array('version' => $version));
557 exit;
558 }
559 }
560
561 /**
562 * Teste de mise à jour des versions de SPIP < 1.926
563 *
564 * @deprecated Utiliser `maj_plugin()` ou la globale `maj` pour le core.
565 * @see maj_plugin()
566 * @see maj_base()
567 *
568 * @param float $version
569 * @param float $version_installee
570 * @param int $version_cible
571 * @return bool true si la mise à jour doit se réaliser
572 **/
573 function upgrade_vers($version, $version_installee, $version_cible = 0) {
574 return ($version_installee < $version
575 and (($version_cible >= $version) or ($version_cible == 0))
576 );
577 }