<?php
/**
 * Plugin Name: SitesManager — Plugin Installer
 * Plugin URI:  https://saas-multisites.trameas.fr/
 * Description: Permet à SitesManager d'installer des plugins WordPress depuis une URL ZIP arbitraire. Détection fiable via snapshot pré/post install. Expose POST /wp-json/sm/v1/install-zip.
 * Version:     1.1.0
 * Author:      SitesManager
 * License:     GPL-2.0+
 *
 * INSTALLATION :
 *   Place ce fichier dans wp-content/mu-plugins/sm-plugin-installer.php
 *   (créer le dossier mu-plugins s'il n'existe pas)
 *   Les mu-plugins sont chargés automatiquement par WP, sans activation.
 *
 * SÉCURITÉ :
 *   L'endpoint exige le capability install_plugins (rôle Administrateur uniquement).
 *   L'auth se fait via Application Password ou cookie WP standard.
 *
 * CHANGELOG :
 *   v1.1.0 (2026-06-17) — Détection fiable du plugin installé via snapshot diff
 *                         (corrige le faux positif "WooCommerce Stripe Gateway")
 *   v1.0.0 (2026-06-12) — Version initiale
 */

if (!defined('ABSPATH')) exit;

define('SM_PLUGIN_INSTALLER_VERSION', '1.1.0');

add_action('rest_api_init', function () {
    // POST /wp-json/sm/v1/install-zip
    // Body JSON : { url: "https://...../plugin.zip", activate: true }
    register_rest_route('sm/v1', '/install-zip', [
        'methods'             => 'POST',
        'callback'            => 'sm_install_plugin_from_zip',
        'permission_callback' => function () {
            return current_user_can('install_plugins');
        },
        'args' => [
            'url'      => [
                'required'          => true,
                'sanitize_callback' => 'esc_url_raw',
                'validate_callback' => function ($u) {
                    return filter_var($u, FILTER_VALIDATE_URL) !== false;
                },
            ],
            'activate' => ['type' => 'boolean', 'default' => true],
        ],
    ]);

    // GET /wp-json/sm/v1/ping
    register_rest_route('sm/v1', '/ping', [
        'methods'             => 'GET',
        'callback'            => function () {
            return [
                'ok'                 => true,
                'plugin'             => 'sm-plugin-installer',
                'version'            => SM_PLUGIN_INSTALLER_VERSION,
                'wp_version'         => get_bloginfo('version'),
                'php_version'        => phpversion(),
                'can_install'        => current_user_can('install_plugins') ? true : false,
                'disallow_file_mods' => defined('DISALLOW_FILE_MODS') && DISALLOW_FILE_MODS,
            ];
        },
        'permission_callback' => function () {
            return current_user_can('read');
        },
    ]);
});

/**
 * Installe un plugin depuis une URL ZIP via Plugin_Upgrader.
 *
 * DÉTECTION FIABLE :
 *   1. Snapshot des plugins AVANT install (get_plugins)
 *   2. Lance Plugin_Upgrader::install()
 *   3. Snapshot APRÈS, calcule le diff = nouveau plugin
 *   4. Si diff = 0 mais install OK → utilise $upgrader->result['destination_name']
 *   5. Vérification finale : le fichier doit exister sur disque
 *
 * Si rien ne marche → retourne une erreur EXPLICITE avec diagnostic complet,
 * jamais de faux positif sur un autre plugin.
 */
function sm_install_plugin_from_zip(WP_REST_Request $request)
{
    $url      = $request->get_param('url');
    $activate = (bool) $request->get_param('activate');

    require_once ABSPATH . 'wp-admin/includes/file.php';
    require_once ABSPATH . 'wp-admin/includes/misc.php';
    require_once ABSPATH . 'wp-admin/includes/plugin.php';
    require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';

    if (defined('DISALLOW_FILE_MODS') && DISALLOW_FILE_MODS) {
        return new WP_Error(
            'disallow_file_mods',
            'WordPress refuse les modifications de fichiers (DISALLOW_FILE_MODS=true dans wp-config.php). Décommentez cette constante pour autoriser.',
            ['status' => 403]
        );
    }

    WP_Filesystem();
    if (empty($GLOBALS['wp_filesystem'])) {
        return new WP_Error('fs_unavailable', 'Système de fichiers WP indisponible (WP_Filesystem() non initialisé)', ['status' => 500]);
    }

    // ============================================================
    // ÉTAPE 1 — Snapshot AVANT install
    // ============================================================
    wp_clean_plugins_cache();
    $plugins_before = array_keys(get_plugins());

    // ============================================================
    // ÉTAPE 2 — Install
    // ============================================================
    $skin     = new Automatic_Upgrader_Skin();
    $upgrader = new Plugin_Upgrader($skin);
    $result   = $upgrader->install($url, ['overwrite_package' => true]);

    $upgrader_data = is_array($upgrader->result) ? $upgrader->result : [];
    $feedback      = method_exists($skin, 'get_upgrade_messages') ? $skin->get_upgrade_messages() : [];
    if (!is_array($feedback)) $feedback = [];

    if (is_wp_error($result)) {
        return new WP_Error(
            'install_failed',
            $result->get_error_message(),
            ['status' => 500, 'feedback' => $feedback, 'upgrader_result' => $upgrader_data]
        );
    }
    if ($result === false || $result === null) {
        $msg = 'Plugin_Upgrader::install() a retourné ' . var_export($result, true);
        if (!empty($feedback)) {
            $msg .= '. Messages installer : ' . implode(' | ', array_map('strval', $feedback));
        }
        return new WP_Error(
            'install_failed',
            $msg,
            ['status' => 500, 'feedback' => $feedback, 'upgrader_result' => $upgrader_data]
        );
    }

    // ============================================================
    // ÉTAPE 3 — Snapshot APRÈS install + diff
    // ============================================================
    wp_clean_plugins_cache();
    $plugins_after = array_keys(get_plugins());
    $new_plugins   = array_values(array_diff($plugins_after, $plugins_before));

    $plugin_basename  = '';
    $detection_method = '';

    // Méthode 1 (la plus fiable) : 1 seul nouveau plugin détecté = c'est lui
    if (count($new_plugins) === 1) {
        $plugin_basename  = $new_plugins[0];
        $detection_method = 'snapshot_diff';
    }
    // Méthode 2 : si >1 ou 0, utiliser le destination_name du Upgrader
    elseif (!empty($upgrader_data['destination_name'])) {
        $dest = (string) $upgrader_data['destination_name'];
        foreach ($plugins_after as $file) {
            // Match "destination_name/anything.php" OU "destination_name.php"
            if (strpos($file, $dest . '/') === 0 || $file === ($dest . '.php')) {
                $plugin_basename  = $file;
                $detection_method = 'upgrader_destination_name';
                break;
            }
        }
    }
    // Méthode 3 : plugin_info() comme dernier recours mais VALIDÉ
    if (!$plugin_basename) {
        $info = $upgrader->plugin_info();
        if ($info && in_array($info, $plugins_after, true)) {
            $plugin_basename  = $info;
            $detection_method = 'plugin_info';
        }
    }

    // ============================================================
    // ÉTAPE 4 — Si rien détecté → erreur explicite (PAS de faux positif)
    // ============================================================
    if (!$plugin_basename) {
        return new WP_Error(
            'plugin_not_detected',
            'Le plugin a été soi-disant installé mais aucun nouveau plugin n\'apparaît dans wp-content/plugins. '
            . 'Le ZIP est probablement invalide, corrompu, ou ne contient pas un plugin WordPress standard '
            . '(structure attendue : dossier-racine/fichier-principal.php avec header Plugin Name).',
            [
                'status' => 500,
                'diagnostic' => [
                    'plugins_before_count' => count($plugins_before),
                    'plugins_after_count'  => count($plugins_after),
                    'plugins_diff'         => $new_plugins,
                    'upgrader_result'      => $upgrader_data,
                    'feedback'             => $feedback,
                ],
            ]
        );
    }

    // ============================================================
    // ÉTAPE 5 — Vérification finale : fichier existe ?
    // ============================================================
    if (!file_exists(WP_PLUGIN_DIR . '/' . $plugin_basename)) {
        return new WP_Error(
            'plugin_file_missing',
            "Le plugin détecté ({$plugin_basename}) n'existe pas sur le disque dans " . WP_PLUGIN_DIR,
            ['status' => 500, 'detection_method' => $detection_method]
        );
    }

    // ============================================================
    // ÉTAPE 6 — Activation optionnelle + métadonnées
    // ============================================================
    $response = [
        'installed'            => true,
        'activated'            => false,
        'plugin'               => $plugin_basename,
        'detection_method'     => $detection_method,
        'upgrader_destination' => $upgrader_data['destination_name'] ?? null,
        'feedback'             => $feedback,
    ];

    if ($activate) {
        $activate_result = activate_plugin($plugin_basename);
        if (is_wp_error($activate_result)) {
            $response['activation_error'] = $activate_result->get_error_message();
        } else {
            $response['activated'] = true;
        }
    }

    $data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin_basename, false, false);
    $response['name']    = $data['Name']    ?? '';
    $response['version'] = $data['Version'] ?? '';
    $response['author']  = isset($data['Author']) ? wp_strip_all_tags($data['Author']) : '';

    return $response;
}
