spip_nursit/ecrire/base/upgrade.php
2023-06-01 17:30:12 +02:00

576 lines
18 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/***************************************************************************\
* SPIP, Systeme de publication pour l'internet *
* *
* Copyright (c) 2001-2019 *
* Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
* *
* Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
* Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
\***************************************************************************/
/**
* Mise à jour de la base de données
*
* @package SPIP\Core\SQL\Upgrade
*/
if (!defined('_ECRIRE_INC_VERSION')) {
return;
}
/**
* Programme de mise à jour des tables SQL lors d'un changement de version.
*
* L'entrée dans cette fonction est reservée aux mises à jour de SPIP coeur.
*
* Marche aussi pour les plugins en appelant directement la fonction `maj_plugin`
* Pour que ceux-ci profitent aussi de la reprise sur interruption,
* ils doivent simplement indiquer leur numero de version installée dans une meta
* et fournir le tableau `$maj` à la fonction `maj_plugin`.
* La reprise sur timeout se fait alors par la page admin_plugin et jamais par ici.
*
* @uses creer_base()
* @uses maj_base()
* @uses auth_synchroniser_distant()
*
* @param string $titre
* @param string $reprise Inutilisé
* @return void
*/
function base_upgrade_dist($titre = '', $reprise = '') {
if (!$titre) {
return;
} // anti-testeur automatique
if ($GLOBALS['spip_version_base'] != $GLOBALS['meta']['version_installee']) {
if (!is_numeric(_request('reinstall'))) {
include_spip('base/create');
spip_log('recree les tables eventuellement disparues', 'maj.' . _LOG_INFO_IMPORTANTE);
creer_base();
}
// quand on rentre par ici, c'est toujours une mise a jour de SPIP
// lancement de l'upgrade SPIP
$res = maj_base();
if ($res) {
// on arrete tout ici !
exit;
}
}
spip_log('Fin de mise a jour SQL. Debut m-a-j acces et config', 'maj.' . _LOG_INFO_IMPORTANTE);
// supprimer quelques fichiers temporaires qui peuvent se retrouver invalides
@spip_unlink(_CACHE_RUBRIQUES);
@spip_unlink(_CACHE_PIPELINES);
@spip_unlink(_CACHE_PLUGINS_PATH);
@spip_unlink(_CACHE_PLUGINS_OPT);
@spip_unlink(_CACHE_PLUGINS_FCT);
@spip_unlink(_CACHE_CHEMIN);
@spip_unlink(_DIR_TMP . 'plugin_xml_cache.gz');
include_spip('inc/auth');
auth_synchroniser_distant();
$config = charger_fonction('config', 'inc');
$config();
}
/**
* Mise à jour de base de SPIP
*
* Exécute toutes les fonctions de mises à jour de SPIP nécessaires,
* en fonction de la meta `version_installee` indiquant le numéro de
* schéma actuel de la base de données.
*
* Les fonctions de mises à jour se trouvent dans `ecrire/maj/`
*
* @uses upgrade_test()
* @uses maj_while()
*
* @param int $version_cible
* @param string $redirect
* @return array|bool
*/
function maj_base($version_cible = 0, $redirect = '', $debut_page = true) {
$version_installee = @$GLOBALS['meta']['version_installee'];
//
// Si version nulle ou inexistante, c'est une nouvelle installation
// => ne pas passer par le processus de mise a jour.
// De meme en cas de version superieure: ca devait etre un test,
// il y a eu le message d'avertissement il doit savoir ce qu'il fait
//
// version_installee = 1.702; quand on a besoin de forcer une MAJ
spip_log(
"Version anterieure: $version_installee. Courante: " . $GLOBALS['spip_version_base'],
'maj.' . _LOG_INFO_IMPORTANTE
);
if (!$version_installee or ($GLOBALS['spip_version_base'] < $version_installee)) {
sql_replace(
'spip_meta',
array(
'nom' => 'version_installee',
'valeur' => $GLOBALS['spip_version_base'],
'impt' => 'non'
)
);
return false;
}
if (!upgrade_test()) {
return true;
}
$cible = ($version_cible ? $version_cible : $GLOBALS['spip_version_base']);
if ($version_installee <= 1.926) {
$n = floor($version_installee * 10);
while ($n < 19) {
$nom = sprintf('v%03d', $n);
$f = charger_fonction($nom, 'maj', true);
if ($f) {
spip_log("$f repercute les modifications de la version " . ($n / 10), 'maj.' . _LOG_INFO_IMPORTANTE);
$f($version_installee, $GLOBALS['spip_version_base']);
} else {
spip_log("pas de fonction pour la maj $n $nom", 'maj.' . _LOG_INFO_IMPORTANTE);
}
$n++;
}
include_spip('maj/v019_pre193');
v019_pre193($version_installee, $version_cible);
}
if ($version_installee < 2000) {
if ($version_installee < 2) {
$version_installee = $version_installee * 1000;
}
include_spip('maj/v019');
}
if ($cible < 2) {
$cible = $cible * 1000;
}
include_spip('maj/svn10000');
ksort($GLOBALS['maj']);
$res = maj_while($version_installee, $cible, $GLOBALS['maj'], 'version_installee', 'meta', $redirect, $debut_page);
if ($res) {
if (!is_array($res)) {
spip_log("Pb d'acces SQL a la mise a jour", 'maj.' . _LOG_INFO_ERREUR);
} else {
echo _T('avis_operation_echec') . ' ' . join(' ', $res);
echo install_fin_html();
}
}
return $res;
}
/**
* Mise à jour d'un plugin de SPIP
*
* Fonction appelée par la fonction de mise à jour d'un plugin.
* On lui fournit un tableau de fonctions élementaires
* dont l'indice est la version.
*
* @uses maj_while()
*
* @param string $nom_meta_base_version
* Nom de la meta informant de la version du schéma de données du plugin installé dans SPIP
* @param string $version_cible
* Version du schéma de données dans le plugin (déclaré dans paquet.xml)
* @param array $maj
* Tableau d'actions à faire à l'installation (clé `create`) et pour chaque
* version intermédiaire entre la version actuelle du schéma du plugin dans SPIP
* et la version du schéma déclaré dans le plugin (ex. clé `1.1.0`).
*
* Chaque valeur est un tableau contenant une liste de fonctions à exécuter,
* cette liste étant elle-même un tableau avec premier paramètre le nom de la fonction
* et les suivant les paramètres à lui passer
*
* Exemple :
*
* ```
* array(
* 'create' => array(
* array('maj_tables', array('spip_rubriques', 'spip_articles')),
* array('creer_base)),
* '1.1.0' => array(
* array('sql_alter', 'TABLE spip_articles ADD INDEX truc (truc)'))
* )
* ```
* @param string $table_meta
* Nom de la table meta (sans le prefixe spip_) dans laquelle trouver la meta $nom_meta_base_version
* @return void
*/
function maj_plugin($nom_meta_base_version, $version_cible, $maj, $table_meta = 'meta') {
if ($table_meta !== 'meta') {
lire_metas($table_meta);
}
$current_version = null;
if ((!isset($GLOBALS[$table_meta][$nom_meta_base_version]))
|| (!spip_version_compare($current_version = $GLOBALS[$table_meta][$nom_meta_base_version], $version_cible, '='))
) {
// $maj['create'] contient les directives propres a la premiere creation de base
// c'est une operation derogatoire qui fait aboutir directement dans la version_cible
if (isset($maj['create'])) {
if (!isset($GLOBALS[$table_meta][$nom_meta_base_version])) {
// installation : on ne fait que l'operation create
$maj = array('init' => $maj['create']);
// et on lui ajoute un appel a inc/config
// pour creer les metas par defaut
$config = charger_fonction('config', 'inc');
$maj[$version_cible] = array(array($config));
}
// dans tous les cas enlever cet index du tableau
unset($maj['create']);
}
// si init, deja dans le bon ordre
if (!isset($maj['init'])) {
include_spip('inc/plugin'); // pour spip_version_compare
uksort($maj, 'spip_version_compare');
}
// la redirection se fait par defaut sur la page d'administration des plugins
// sauf lorsque nous sommes sur l'installation de SPIP
// ou define _REDIRECT_MAJ_PLUGIN
$redirect = (defined('_REDIRECT_MAJ_PLUGIN') ? _REDIRECT_MAJ_PLUGIN : generer_url_ecrire('admin_plugin'));
if (defined('_ECRIRE_INSTALL')) {
$redirect = parametre_url(generer_url_ecrire('install'), 'etape', _request('etape'));
}
$res = maj_while($current_version, $version_cible, $maj, $nom_meta_base_version, $table_meta, $redirect);
if ($res) {
if (!is_array($res)) {
spip_log("Pb d'acces SQL a la mise a jour", 'maj.' . _LOG_INFO_ERREUR);
} else {
echo '<p>' . _T('avis_operation_echec') . ' ' . join(' ', $res) . '</p>';
}
}
}
}
/**
* Relancer le hit de mise à jour avant timeout
*
* si pas de redirect fourni, on redirige vers `exec=upgrade` pour finir
* ce qui doit être une mise à jour SPIP
*
* @uses redirige_formulaire()
*
* @param string $meta
* @param string $table
* @param string $redirect
* @return void
*/
function relance_maj($meta, $table, $redirect = '') {
include_spip('inc/headers');
if (!$redirect) {
// recuperer la valeur installee en cours
// on la tronque numeriquement, elle ne sert pas reellement
// sauf pour verifier que ce n'est pas oui ou non
// sinon is_numeric va echouer sur un numero de version 1.2.3
$installee = intval($GLOBALS[$table][$meta]);
$redirect = generer_url_ecrire('upgrade', "reinstall=$installee&meta=$meta&table=$table", true);
}
echo redirige_formulaire($redirect);
exit();
}
/**
* Initialiser la page pour l'affichage des progrès de l'upgrade
* uniquement si la page n'a pas déjà été initilalisée
*
* @param string $installee
* @param string $meta
* @param string $table
* @return void
*/
function maj_debut_page($installee, $meta, $table) {
static $done = false;
if ($done) {
return;
}
include_spip('inc/minipres');
@ini_set('zlib.output_compression', '0'); // pour permettre l'affichage au fur et a mesure
$timeout = _UPGRADE_TIME_OUT * 2;
$titre = _T('titre_page_upgrade');
$balise_img = charger_filtre('balise_img');
$titre .= $balise_img(chemin_image('searching.gif'));
echo(install_debut_html($titre));
// script de rechargement auto sur timeout
$redirect = generer_url_ecrire('upgrade', "reinstall=$installee&meta=$meta&table=$table", true);
echo http_script("window.setTimeout('location.href=\"" . $redirect . "\";'," . ($timeout * 1000) . ')');
echo "<div style='text-align: left'>\n";
if (ob_get_level()) {
ob_flush();
}
flush();
$done = true;
}
if (!defined('_UPGRADE_TIME_OUT')) {
/**
* Durée en secondes pour relancer les scripts de mises à jour, x secondes
* avant que la durée d'exécution du script provoque un timeout
*
* @var int
**/
define('_UPGRADE_TIME_OUT', 20);
}
/**
* Gestion des mises à jour de SPIP et des plugins
*
* À partir des versions > 1.926 (i.e SPIP > 1.9.2), cette fonction gere les MAJ.
*
* Se relancer soi-même pour éviter l'interruption pendant une operation SQL
* (qu'on espère pas trop longue chacune) évidemment en ecrivant dans la meta
* à quel numero on en est.
*
* Cette fonction peut servir aux plugins qui doivent donner comme arguments :
*
* 1. le numero de version courant (numéro de version 1.2.3 ou entier)
* 2. le numero de version à atteindre (numéro de version 1.2.3 ou entier)
* 3. le tableau des instructions de mise à jour à exécuter
* Pour profiter du mécanisme de reprise sur interruption il faut de plus
* 4. le nom de la meta permettant de retrouver tout ca
* 5. la table des meta ou elle se trouve (`$table_prefix . '_meta'` par défaut)
* (cf début de fichier)
*
* les fonctions `sql_xx` appelées lors des mises à jour sont supposées
* atomiques et ne sont pas relancées en cas de timeout, mais les fonctions
* spécifiques sont relancées jusqu'à ce qu'elles finissent.
* Elles doivent donc s'assurer de progresser à chaque reprise.
*
* @uses maj_debut_page()
* @uses serie_alter()
* @uses relance_maj()
*
* @param string $installee
* @param string $cible
* @param array $maj
* @param string $meta
* @param string $table
* @param string $redirect
* @param bool $debut_page
* @return array
* - tableau (étape, sous-étape) en cas d'échec,
* - tableau vide sinon.
*/
function maj_while($installee, $cible, $maj, $meta = '', $table = 'meta', $redirect = '', $debut_page = false) {
# inclusions pour que les procedures d'upgrade disposent des fonctions de base
include_spip('base/create');
include_spip('base/abstract_sql');
$trouver_table = charger_fonction('trouver_table', 'base');
include_spip('inc/plugin'); // pour spip_version_compare
$n = 0;
$time = time();
if (!defined('_TIME_OUT')) {
/**
* Définir le timeout qui peut-être utilisé dans les fonctions
* de mises à jour qui durent trop longtemps
*
* À utiliser tel que : `if (time() >= _TIME_OUT)`
*
* @var int
*/
define('_TIME_OUT', $time + _UPGRADE_TIME_OUT);
}
foreach ($maj as $v => $operations) {
// si une maj pour cette version
if ($v == 'init' or
(spip_version_compare($v, $installee, '>')
and spip_version_compare($v, $cible, '<='))
) {
if ($debut_page) {
maj_debut_page($v, $meta, $table);
}
echo "MAJ $v";
$etape = serie_alter($v, $operations, $meta, $table, $redirect);
$trouver_table(''); // vider le cache des descriptions de table
# echec sur une etape en cours ?
# on sort
if ($etape) {
return array($v, $etape);
}
$n = time() - $time;
spip_log("$table $meta: $v en $n secondes", 'maj.' . _LOG_INFO_IMPORTANTE);
if ($meta) {
ecrire_meta($meta, $installee = $v, 'oui', $table);
}
echo '<br />';
}
if (time() >= _TIME_OUT) {
relance_maj($meta, $table, $redirect);
}
}
$trouver_table(''); // vider le cache des descriptions de table
// indispensable pour les chgt de versions qui n'ecrivent pas en base
// tant pis pour la redondance eventuelle avec ci-dessus
if ($meta) {
ecrire_meta($meta, $cible, 'oui', $table);
}
spip_log("MAJ terminee. $meta: $installee", 'maj.' . _LOG_INFO_IMPORTANTE);
return array();
}
/**
* Appliquer une serie de changements qui risquent de partir en timeout
*
* Alter crée une copie temporaire d'une table, c'est lourd.
*
* @uses relance_maj()
*
* @param string $serie
* numero de version upgrade
* @param array $q
* tableau des operations pour cette version
* @param string $meta
* nom de la meta qui contient le numero de version
* @param string $table
* nom de la table meta
* @param string $redirect
* url de redirection en cas d'interruption
* @return int
*/
function serie_alter($serie, $q = array(), $meta = '', $table = 'meta', $redirect = '') {
$meta2 = $meta . '_maj_' . $serie;
$etape = intval(@$GLOBALS[$table][$meta2]);
foreach ($q as $i => $r) {
if ($i >= $etape) {
$msg = "maj $table $meta2 etape $i";
if (is_array($r)
and function_exists($f = array_shift($r))
) {
// note: $r (arguments de la fonction $f) peut avoir des données tabulaires
spip_log("$msg: $f " . @join(',', $r), 'maj.' . _LOG_INFO_IMPORTANTE);
// pour les fonctions atomiques sql_xx
// on enregistre le meta avant de lancer la fonction,
// de maniere a eviter de boucler sur timeout
// mais pour les fonctions complexes,
// il faut les rejouer jusqu'a achevement.
// C'est a elle d'assurer qu'elles progressent a chaque rappel
if (strncmp($f, 'sql_', 4) == 0) {
ecrire_meta($meta2, $i + 1, 'non', $table);
}
echo " <span title='$i'>.</span>";
call_user_func_array($f, $r);
// si temps imparti depasse, on relance sans ecrire en meta
// car on est peut etre sorti sur timeout si c'est une fonction longue
if (time() >= _TIME_OUT) {
relance_maj($meta, $table, $redirect);
}
ecrire_meta($meta2, $i + 1, 'non', $table);
spip_log("$meta2: ok", 'maj.' . _LOG_INFO_IMPORTANTE);
} else {
if (!is_array($r)) {
spip_log("maj $i format incorrect", 'maj.' . _LOG_ERREUR);
} else {
spip_log("maj $i fonction $f non definie", 'maj.' . _LOG_ERREUR);
}
// en cas d'erreur serieuse, on s'arrete
// mais on permet de passer par dessus en rechargeant la page.
return $i + 1;
}
}
}
effacer_meta($meta2, $table);
return 0;
}
/**
* Mise à jour des types MIME de documents
*
* Fonction utilisé par les vieilles mises à jour de SPIP, à appeler dans
* le tableau `$maj` quand on rajoute des types MIME. Remplacé actuellement
* par le plugin Medias.
*
* @deprecated Utiliser directement `creer_base_types_doc()` du plugin Medias
* @example
* ```
* $GLOBALS['maj'][1953] = array(array('upgrade_types_documents'));
*
* ```
* @uses creer_base_types_doc()
*
**/
function upgrade_types_documents() {
if (include_spip('base/medias')
and function_exists('creer_base_types_doc')
) {
creer_base_types_doc();
}
}
/**
* Vérifie qu'il est possible d'ajouter une colonne à une table SQL
*
* @return bool True si possible.
**/
function upgrade_test() {
sql_drop_table('spip_test', true);
sql_create('spip_test', array('a' => 'int'));
sql_alter('TABLE spip_test ADD b INT');
sql_insertq('spip_test', array('b' => 1), array('field' => array('b' => 'int')));
$result = sql_select('b', 'spip_test');
// ne pas garder le resultat de la requete sinon sqlite3
// ne peut pas supprimer la table spip_test lors du sql_alter qui suit
// car cette table serait alors 'verouillee'
$result = $result ? true : false;
sql_alter('TABLE spip_test DROP b');
return $result;
}
/**
* Mise à jour des versions de SPIP < 1.926
*
* @deprecated Utiliser `maj_plugin()` ou la globale `maj` pour le core.
* @see maj_plugin()
* @see maj_base()
*
* @param float $version
* @param bool $test
* @return void
**/
function maj_version($version, $test = true) {
if ($test) {
if ($version >= 1.922) {
ecrire_meta('version_installee', $version, 'oui');
} else {
// on le fait manuellement, car ecrire_meta utilise le champs impt qui est absent sur les vieilles versions
$GLOBALS['meta']['version_installee'] = $version;
sql_updateq('spip_meta', array('valeur' => $version), 'nom=' . sql_quote('version_installee'));
}
spip_log("mise a jour de la base en $version", 'maj.' . _LOG_INFO_IMPORTANTE);
} else {
echo _T('alerte_maj_impossible', array('version' => $version));
exit;
}
}
/**
* Teste de mise à jour des versions de SPIP < 1.926
*
* @deprecated Utiliser `maj_plugin()` ou la globale `maj` pour le core.
* @see maj_plugin()
* @see maj_base()
*
* @param float $version
* @param float $version_installee
* @param int $version_cible
* @return bool true si la mise à jour doit se réaliser
**/
function upgrade_vers($version, $version_installee, $version_cible = 0) {
return ($version_installee < $version
and (($version_cible >= $version) or ($version_cible == 0))
);
}