<?php

/***************************************************************************\
 *  SPIP, Système de publication pour l'internet                           *
 *                                                                         *
 *  Copyright © avec tendresse depuis 2001                                 *
 *  Arnaud Martin, Antoine Pitrou, Philippe Rivière, Emmanuel Saint-James  *
 *                                                                         *
 *  Ce programme est un logiciel libre distribué sous licence GNU/GPL.     *
\***************************************************************************/

/**
 * Gestion de l'installation des plugins
 *
 * @package SPIP\Core\Plugins
 **/


if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}

/**
 * Installe ou retire un plugin
 *
 * Permet d'installer ou retirer un plugin en incluant les fichiers
 * associés et en lançant les fonctions spécifiques.
 *
 * 1. d'abord sur l'argument `test`,
 * 2. ensuite sur l'action demandée si le test repond `false`
 * 3. enfin sur l'argument `test` à nouveau.
 *
 * L'index `install_test` du tableau résultat est un tableau formé :
 *
 *  - du résultat 3
 *  - des echo de l'étape 2
 *
 * @note
 *     La fonction quitte (retourne false) si le plugin
 *     n'a pas de version d'installation définie
 *     (information `schema` dans le paquet.xml)
 *
 * @param string $plug
 *     Nom du plugin
 * @param string $action
 *     Nom de l'action (install|uninstall)
 * @param string $dir_type
 *     Répertoire du plugin
 * @return array|bool
 *     - False si le plugin n'a pas d'installation,
 *     - true si déjà installé,
 *     - le tableau de get_infos sinon
 */
function plugins_installer_dist($plug, $action, $dir_type = '_DIR_PLUGINS') {

	// Charger les informations du XML du plugin et vérification de l'existence d'une installation
	$get_infos = charger_fonction('get_infos', 'plugins');
	$infos = $get_infos($plug, false, constant($dir_type));
	if (!isset($infos['install']) or !$infos['install']) {
		return false;
	}

	// Passer en chemin absolu si possible, c'est plus efficace
	$dir = str_replace('_DIR_', '_ROOT_', $dir_type);
	if (!defined($dir)) {
		$dir = $dir_type;
	}
	$dir = constant($dir);
	foreach ($infos['install'] as $file) {
		$file = $dir . $plug . '/' . trim($file);
		if (file_exists($file)) {
			include_once($file);
		}
	}

	// Détermination de la table meta et du nom de la meta plugin
	$table = 'meta';
	if (isset($infos['meta']) and ($infos['meta'] !== 'meta')) {
		$table = $infos['meta'];
		// S'assurer que les metas de la table spécifique sont bien accessibles dans la globale
		lire_metas($table);
	}
	$nom_meta = $infos['prefix'] . '_base_version';

	// Détermination de la fonction à appeler et de ses arguments
	$f = $infos['prefix'] . '_install';
	if (!function_exists($f)) {
		$f = isset($infos['schema']) ? 'spip_plugin_install' : '';
		$arg = $infos;
		// On passe la table et la meta pour éviter de les recalculer dans la fonction appelée
		$arg['meta'] = $table;
		$arg['nom_meta'] = $nom_meta;
	} else {
		// Ancienne méthode d'installation - TODO à supprimer à terme
		// stupide: info deja dans le nom
		$arg = $infos['prefix'];
	}
	$version = $infos['schema'] ?? '';

	if (!$f) {
		// installation sans operation particuliere
		$infos['install_test'] = [true, ''];
		return $infos;
	}

	// Tester si l'action demandée est nécessaire ou pas.
	$test = $f('test', $arg, $version);
	if ($action == 'uninstall') {
		$test = !$test;
	}
	// Si deja fait, on ne fait rien et on ne dit rien
	if ($test) {
		return true;
	}

	// Si install et que l'on a la meta d'installation, c'est un upgrade. On le consigne dans $infos
	// pour renvoyer le bon message en retour de la fonction.
	if ($action == 'install' && !empty($GLOBALS[$table][$nom_meta])) {
		$infos['upgrade'] = true;
	}

	// executer l'installation ou l'inverse
	// et renvoyer la trace (mais il faudrait passer en AJAX plutot)
	ob_start();
	$f($action, $arg, $version);
	$aff = ob_get_contents();
	ob_end_clean();

	// vider le cache des descriptions de tables a chaque (de)installation
	$trouver_table = charger_fonction('trouver_table', 'base');
	$trouver_table('');
	$infos['install_test'] = [$f('test', $arg, $version), $aff];

	// Si la table meta n'est pas spip_meta et qu'on est dans la première installation du plugin
	// on force la création du fichier cache à la date du moment.
	// On relit les metas de la table pour être sur que la globale soit à jour pour touch_meta.
	if (
		($table !== 'meta')
		and ($action == 'install')
		and empty($infos['upgrade'])
	) {
		touch_meta(false, $table);
	}

	return $infos;
}

/**
 * Fonction standard utilisée par defaut pour install/desinstall
 *
 * @param string $action
 *     Nom de l'action (install|uninstall)
 * @param array  $infos
 *     Tableau des informations du XML du plugin complété par le nom et la table meta
 * @param string $version_cible
 *     Référence de la version du schéma de données cible
 *
 * @return bool|void
 */
function spip_plugin_install($action, $infos, $version_cible) {
	$nom_meta = $infos['nom_meta'];
	$table = $infos['meta'];
	switch ($action) {
		case 'test':
			return (isset($GLOBALS[$table])
				and isset($GLOBALS[$table][$nom_meta])
				and spip_version_compare($GLOBALS[$table][$nom_meta], $version_cible, '>='));
			break;
		case 'install':
			if (function_exists($upgrade = $infos['prefix'] . '_upgrade')) {
				$upgrade($nom_meta, $version_cible, $table);
			}
			break;
		case 'uninstall':
			if (function_exists($vider_tables = $infos['prefix'] . '_vider_tables')) {
				include_spip('base/abstract_sql');
				include_spip('base/objets');
				// Tous les objets gérés (et leur description)
				$objets_declares = lister_tables_objets_sql();
				// Supprimer les tables du plugin
				$vider_tables($nom_meta, $table);
				/**
				 * Comparer les tables après suppression aux objets gérés
				 * @var array<string, array{type: string, ...<string,mixed>}>
				 */
				$objets_supprimes = array_diff_key($objets_declares, array_flip(sql_alltable('%')));
				pipeline('trig_supprimer_objets_tables', $objets_supprimes);
			}
			break;
	}
}



/**
 * Retourne un tableau des plugins activés sur le site
 *
 * Retourne la meta `plugin` désérialisée.
 * Chaque élément du tableau est lui-même un tableau contenant
 * les détails du plugin en question : répertoire et version.
 *
 * @note
 *   Si le contenu de la meta n’est pas un tableau, cette fonction transforme
 *   l’ancien format en tableau sérialisé pour être conforme au nouveau fonctionnement (SPIP >= 1.9.2)
 *
 * @return array Tableau des plugins actifs
 **/
function liste_plugin_actifs() {
	$liste = $GLOBALS['meta']['plugin'] ?? '';
	if (!$liste) {
		return [];
	}
	if (!is_array($liste = unserialize($liste))) {
		// compatibilite pre 1.9.2, mettre a jour la meta
		spip_log("MAJ meta plugin vieille version : $liste", 'plugin');
		$new = true;
		[, $liste] = liste_plugin_valides(explode(',', $liste));
	} else {
		$new = false;
		// compat au moment d'une migration depuis version anterieure
		// si pas de dir_type, alors c'est _DIR_PLUGINS
		foreach ($liste as $prefix => $infos) {
			if (!isset($infos['dir_type'])) {
				$liste[$prefix]['dir_type'] = '_DIR_PLUGINS';
				$new = true;
			}
		}
	}
	if ($new) {
		ecrire_meta('plugin', serialize($liste));
	}

	return $liste;
}
