<?php
/**
 * Projet                    : MageStock
 * Logiciel                  : MageStock
 * Fichier                   : password-recovery.php
 * Rôle                      : Page publique de consommation d’un jeton de récupération administrateur.
 * Auteur                    : Régis KREMER
 * Version du fichier        : 1.1.0
 * Version générale          : 2.6.123
 * Lot                       : MAGESTOCK FOOTER MAGEKORP IDENTITY CONTACT1
 * Base requise              : MageStock 2.6.123
 * Création                  : 2026-07-25
 * Dernière modification     : 2026-07-30
 * Dépendances               : config.php, includes/password-recovery.php, auth-gateway.css/js
 * Données touchées          : users.json, password_recovery_tokens.json, password_recovery_audit.json
 * Permissions               : accès public avec jeton valide ; comptes locaux uniquement
 * Sécurité                  : CSRF, rate limiting, no-store, jeton absent de l’URL, usage unique, révocation des sessions
 * Compatibilité             : PHP 8.1 à 8.4, PC, smartphone et tablette
 *
 * Résumé :
 * - Reprend la passerelle MageTicket avec l’identité et les couleurs MageStock.
 * - Applique la politique de mot de passe serveur et ferme les anciennes sessions.
 *
 * Historique :
 * - 2.6.119 — Création de la récupération locale contrôlée.
 * - 2.6.123 — Footer MageKorp harmonisé avec la passerelle publique et contact officiel.
 */

declare(strict_types=1);

require_once __DIR__ . '/config.php';
require_once __DIR__ . '/includes/password-recovery.php';

if(appInstallationRequired()){
    header('Location: ' . appUrl('install'));
    exit;
}

startSession();
$language = appResolveLanguage();
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Referrer-Policy: no-referrer');
header('X-Robots-Tag: noindex, nofollow, noarchive');

$error = '';
$success = false;
if($_SERVER['REQUEST_METHOD'] === 'POST'){
    $csrf = (string)($_POST['csrf_token'] ?? '');
    if(!hash_equals((string)($_SESSION['csrf_token'] ?? ''), $csrf)){
        $error = appTranslate('recovery.error.session_expired', $language, [], 'Session expirée. Rechargez la page puis réessayez.');
    } else {
        $rateKey = 'password_recovery_' . md5((string)($_SERVER['REMOTE_ADDR'] ?? 'unknown'));
        if(!checkRateLimit($rateKey, 10, 900)){
            $error = appTranslate('recovery.error.rate_limit', $language, [], 'Trop de tentatives. Patientez avant de réessayer.');
        } else {
            $token = (string)($_POST['recovery_token'] ?? '');
            $password = (string)($_POST['new_password'] ?? '');
            $confirm = (string)($_POST['new_password_confirm'] ?? '');
            if(!hash_equals($password, $confirm)){
                $error = appTranslate('recovery.error.password_mismatch', $language, [], 'Les deux mots de passe ne correspondent pas.');
            } else {
                try {
                    magestockRecoveryResetPassword($token, $password);
                    $success = true;
                    $_POST = [];
                } catch(MageStockRecoveryException $e) {
                    $error = $e->reasonCode === 'password_policy'
                        ? $e->publicMessage
                        : appTranslate('recovery.error.invalid_token', $language, [], $e->publicMessage);
                } catch(Throwable $e) {
                    error_log('[MageStock Public Recovery] ' . $e->getMessage());
                    $error = appTranslate('recovery.error.unavailable', $language, [], 'Le service de récupération est momentanément indisponible.');
                }
            }
        }
    }
}

$settings = getSettings();
$company = trim((string)($settings['companyName'] ?? 'MageStock')) ?: 'MageStock';
$csrfToken = csrfToken();
$langNames = [
    'fr'=>'Français','en'=>'English','de'=>'Deutsch','es'=>'Español','it'=>'Italiano','nl'=>'Nederlands',
    'pt'=>'Português','pl'=>'Polski','ru'=>'Русский','tr'=>'Türkçe','ar'=>'العربية','ja'=>'日本語',
    'ko'=>'한국어','zh'=>'中文','hi'=>'हिन्दी','id'=>'Bahasa Indonesia','uk'=>'Українська',
];
$themeLight = appTranslate('login.theme.light', $language, [], 'Passer en thème clair');
$themeDark = appTranslate('login.theme.dark', $language, [], 'Passer en thème sombre');
?>
<!doctype html>
<html lang="<?= htmlspecialchars($language) ?>" dir="<?= $language === 'ar' ? 'rtl' : 'ltr' ?>" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#0b1320">
<meta name="description" content="<?= htmlspecialchars(appTranslate('recovery.meta_description', $language, [], 'Réinitialisation contrôlée du mot de passe MageStock.')) ?>">
<meta name="robots" content="noindex,nofollow,noarchive">
<link rel="icon" href="<?= htmlspecialchars(appUrl('favicon.ico?v=2.6.121')) ?>" sizes="any">
<link rel="icon" type="image/svg+xml" href="<?= htmlspecialchars(assetUrl('img/favicon.svg?v=2.6.121')) ?>">
<title><?= htmlspecialchars(appTranslate('recovery.page_title', $language, [], 'Réinitialiser le mot de passe')) ?> — <?= htmlspecialchars($company) ?></title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,400;9..40,500;9..40,600;9..40,700;9..40,800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="<?= htmlspecialchars(assetUrl('css/app.css?v=' . filemtime(__DIR__.'/assets/css/app.css'))) ?>">
<link rel="stylesheet" href="<?= htmlspecialchars(assetUrl('css/auth-gateway.css?v=' . filemtime(__DIR__.'/assets/css/auth-gateway.css'))) ?>">
</head>
<body class="auth-page" data-theme-light-label="<?= htmlspecialchars($themeLight) ?>" data-theme-dark-label="<?= htmlspecialchars($themeDark) ?>">
<div class="auth-gateway">
    <aside class="auth-brand-panel auth-brand-panel--recovery" aria-labelledby="recoveryBrandName">
        <div class="auth-brand-shell">
            <div class="auth-brand-copy">
                <div class="auth-wordmark">
                    <img class="auth-wordmark__logo" src="<?= htmlspecialchars(assetUrl('img/sm.png')) ?>" alt="">
                    <div>
                        <strong class="auth-wordmark__name" id="recoveryBrandName">MageStock</strong>
                        <span class="auth-wordmark__strapline"><?= htmlspecialchars(appTranslate('recovery.brand.strapline', $language, [], 'Compte · accès · sécurité')) ?></span>
                    </div>
                </div>
                <p class="auth-brand-lead"><?= htmlspecialchars(appTranslate('recovery.brand.lead', $language, [], 'Récupérez l’accès à votre espace MageStock avec un jeton temporaire créé par votre administrateur.')) ?></p>
                <div class="auth-brand-detail-list">
                    <div class="auth-brand-detail">
                        <span class="auth-brand-detail__icon"><svg viewBox="0 0 24 24"><path d="M12 15v2"></path><path d="M8 10V7a4 4 0 0 1 8 0v3"></path><rect x="5" y="10" width="14" height="10"></rect></svg></span>
                        <div><strong><?= htmlspecialchars(appTranslate('recovery.brand.step.request.title', $language, [], 'Demander un jeton')) ?></strong><span><?= htmlspecialchars(appTranslate('recovery.brand.step.request.text', $language, [], 'Contactez votre administrateur ou votre support interne.')) ?></span></div>
                    </div>
                    <div class="auth-brand-detail">
                        <span class="auth-brand-detail__icon"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"></circle><path d="M12 7v5l3 2"></path></svg></span>
                        <div><strong><?= htmlspecialchars(appTranslate('recovery.brand.step.expiry.title', $language, [], 'L’utiliser avant son expiration')) ?></strong><span><?= htmlspecialchars(appTranslate('recovery.brand.step.expiry.text', $language, [], 'Le jeton est personnel, limité dans le temps et remplace tout jeton précédent.')) ?></span></div>
                    </div>
                    <div class="auth-brand-detail">
                        <span class="auth-brand-detail__icon"><svg viewBox="0 0 24 24"><path d="M12 3l8 4v5c0 5-3.4 8.2-8 9-4.6-.8-8-4-8-9V7l8-4Z"></path><path d="M8.5 12h7"></path></svg></span>
                        <div><strong><?= htmlspecialchars(appTranslate('recovery.brand.step.sessions.title', $language, [], 'Révocation des sessions')) ?></strong><span><?= htmlspecialchars(appTranslate('recovery.brand.step.sessions.text', $language, [], 'Après validation, toutes les connexions précédentes sont automatiquement fermées.')) ?></span></div>
                    </div>
                </div>
                <p class="auth-brand-foot"><?= htmlspecialchars(appTranslate('recovery.brand.notice', $language, [], 'MageStock ne demande jamais votre ancien mot de passe et ne conserve pas le nouveau dans le navigateur.')) ?></p>
            </div>

            <section class="auth-product-stage auth-product-stage--security" aria-label="<?= htmlspecialchars(appTranslate('recovery.stage.aria', $language, [], 'Aperçu de démonstration de la protection du compte MageStock')) ?>">
                <div class="auth-product-stage__grid" aria-hidden="true"></div>
                <header class="auth-stage-head">
                    <div>
                        <span class="auth-stage-eyebrow"><?= htmlspecialchars(appTranslate('recovery.stage.eyebrow', $language, [], 'Protection du compte')) ?></span>
                        <strong><?= htmlspecialchars(appTranslate('recovery.stage.account', $language, [], 'Accès MageStock')) ?></strong>
                    </div>
                    <span class="auth-stage-status"><i></i><?= htmlspecialchars(appTranslate('recovery.stage.status', $language, [], 'Contrôle actif')) ?></span>
                </header>
                <div class="auth-security-visual" aria-hidden="true">
                    <span class="auth-security-visual__shield"><svg viewBox="0 0 24 24"><path d="M12 3 20 7v5c0 5-3.4 8.2-8 9-4.6-.8-8-4-8-9V7l8-4Z"></path><path d="m8.5 12 2.2 2.2 4.8-5"></path></svg></span>
                    <div class="auth-security-visual__lines"><i></i><i></i><i></i></div>
                </div>
                <div class="auth-stage-security-list">
                    <article><span><?= htmlspecialchars(appTranslate('recovery.stage.token', $language, [], 'Jeton temporaire')) ?></span><strong><?= htmlspecialchars(appTranslate('recovery.stage.token_value', $language, [], 'Usage unique')) ?></strong></article>
                    <article><span><?= htmlspecialchars(appTranslate('recovery.stage.sessions', $language, [], 'Sessions')) ?></span><strong><?= htmlspecialchars(appTranslate('recovery.stage.sessions_value', $language, [], 'Révocation automatique')) ?></strong></article>
                    <article><span><?= htmlspecialchars(appTranslate('recovery.stage.storage', $language, [], 'Stockage')) ?></span><strong><?= htmlspecialchars(appTranslate('recovery.stage.storage_value', $language, [], 'Aucun secret en clair')) ?></strong></article>
                </div>
                <footer class="auth-stage-movement auth-stage-movement--security">
                    <span class="auth-stage-movement__icon"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v18"></path><path d="M5 8h14"></path><path d="M7 13h10"></path><path d="M9 18h6"></path></svg></span>
                    <span><small><?= htmlspecialchars(appTranslate('recovery.stage.policy', $language, [], 'Politique de mot de passe')) ?></small><strong><?= htmlspecialchars(appTranslate('recovery.stage.policy_value', $language, [], 'Contrôlée côté serveur')) ?></strong></span>
                    <em><?= htmlspecialchars(appTranslate('recovery.stage.local', $language, [], 'Compte local')) ?></em>
                </footer>
            </section>
        </div>
    </aside>

    <main class="auth-form-panel">
        <div class="auth-toolbar">
            <label class="sr-only" for="authLanguageSelect"><?= htmlspecialchars(appTranslate('login.language', $language, [], 'Langue')) ?></label>
            <select id="authLanguageSelect" aria-label="<?= htmlspecialchars(appTranslate('login.language', $language, [], 'Langue')) ?>">
                <?php foreach(appSupportedLanguages() as $code): ?>
                    <option value="<?= htmlspecialchars($code) ?>" <?= $code === $language ? 'selected' : '' ?>><?= htmlspecialchars($langNames[$code] ?? strtoupper($code)) ?></option>
                <?php endforeach; ?>
            </select>
            <button type="button" id="authThemeToggle" aria-label="<?= htmlspecialchars($themeLight) ?>" title="<?= htmlspecialchars($themeLight) ?>"><svg id="authThemeIcon" viewBox="0 0 24 24" aria-hidden="true"></svg></button>
        </div>

        <div class="auth-form-panel__inner">
            <p class="auth-kicker"><?= htmlspecialchars(appTranslate('recovery.form.kicker', $language, [], 'Sécurité du compte')) ?></p>
            <h1 class="auth-title"><?= htmlspecialchars(appTranslate('recovery.form.title', $language, [], 'Réinitialiser le mot de passe')) ?></h1>
            <p class="auth-intro"><?= htmlspecialchars(appTranslate('recovery.form.intro', $language, [], 'Renseignez le jeton fourni par votre administrateur puis choisissez un mot de passe qui respecte tous les critères.')) ?></p>

            <?php if($error !== ''): ?>
                <div class="auth-alert" role="alert"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"></circle><path d="M12 7v6"></path><path d="M12 17h.01"></path></svg><span><?= htmlspecialchars($error) ?></span></div>
            <?php endif; ?>
            <?php if($success): ?>
                <div class="auth-alert auth-alert--success" role="status"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"></circle><path d="M8 12l2.5 2.5L16.5 9"></path></svg><span><?= htmlspecialchars(appTranslate('recovery.success', $language, [], 'Le mot de passe a été réinitialisé. Toutes les anciennes sessions ont été fermées.')) ?></span></div>
                <a class="auth-submit" href="<?= htmlspecialchars(appUrl('login')) ?>"><svg viewBox="0 0 24 24"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><path d="M16 17l5-5-5-5"></path><path d="M21 12H9"></path></svg><?= htmlspecialchars(appTranslate('recovery.return_login', $language, [], 'Retour à la connexion')) ?></a>
            <?php else: ?>
                <form id="passwordRecoveryForm" method="post" action="<?= htmlspecialchars(appUrl('password-recovery.php')) ?>" autocomplete="off">
                    <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken) ?>">
                    <label class="auth-field" for="recoveryToken">
                        <span class="auth-field__label"><?= htmlspecialchars(appTranslate('recovery.token.label', $language, [], 'Jeton de récupération')) ?></span>
                        <input class="auth-input" id="recoveryToken" name="recovery_token" type="text" maxlength="180" required spellcheck="false" autocapitalize="none" autocomplete="off" value="<?= htmlspecialchars((string)($_POST['recovery_token'] ?? '')) ?>">
                        <span class="auth-field__hint"><?= htmlspecialchars(appTranslate('recovery.token.hint', $language, [], 'Collez le jeton complet sans ajouter d’espace. Il ne peut être utilisé qu’une seule fois.')) ?></span>
                    </label>
                    <div class="auth-row">
                        <label class="auth-field" for="recoveryPassword">
                            <span class="auth-field__label"><?= htmlspecialchars(appTranslate('recovery.password.label', $language, [], 'Nouveau mot de passe')) ?></span>
                            <span class="auth-input-wrap"><input class="auth-input auth-input--with-action" id="recoveryPassword" name="new_password" type="password" minlength="14" maxlength="255" required autocomplete="new-password"><button class="auth-input-action" type="button" data-password-toggle="recoveryPassword" aria-label="<?= htmlspecialchars(appTranslate('auth.password.toggle', $language, [], 'Afficher ou masquer le mot de passe')) ?>" aria-pressed="false"><svg viewBox="0 0 24 24"><path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12Z"></path><circle cx="12" cy="12" r="2.5"></circle></svg></button></span>
                        </label>
                        <label class="auth-field" for="recoveryPasswordConfirm">
                            <span class="auth-field__label"><?= htmlspecialchars(appTranslate('recovery.password_confirm.label', $language, [], 'Confirmer le mot de passe')) ?></span>
                            <span class="auth-input-wrap"><input class="auth-input auth-input--with-action" id="recoveryPasswordConfirm" name="new_password_confirm" type="password" minlength="14" maxlength="255" required autocomplete="new-password"><button class="auth-input-action" type="button" data-password-toggle="recoveryPasswordConfirm" aria-label="<?= htmlspecialchars(appTranslate('auth.password.toggle', $language, [], 'Afficher ou masquer le mot de passe')) ?>" aria-pressed="false"><svg viewBox="0 0 24 24"><path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12Z"></path><circle cx="12" cy="12" r="2.5"></circle></svg></button></span>
                        </label>
                    </div>
                    <section class="auth-policy" aria-labelledby="passwordPolicyHeading">
                        <div class="auth-policy__heading"><span><?= htmlspecialchars(appTranslate('recovery.policy.kicker', $language, [], 'Critères obligatoires')) ?></span><strong id="passwordPolicyHeading"><?= htmlspecialchars(appTranslate('recovery.policy.title', $language, [], 'Mot de passe attendu')) ?></strong></div>
                        <ul class="auth-policy__rules">
                            <li data-policy-rule="length"><?= htmlspecialchars(appTranslate('recovery.policy.length', $language, [], 'Entre 14 et 255 caractères')) ?></li>
                            <li data-policy-rule="lowercase"><?= htmlspecialchars(appTranslate('recovery.policy.lowercase', $language, [], 'Au moins une lettre minuscule')) ?></li>
                            <li data-policy-rule="uppercase"><?= htmlspecialchars(appTranslate('recovery.policy.uppercase', $language, [], 'Au moins une lettre majuscule')) ?></li>
                            <li data-policy-rule="number"><?= htmlspecialchars(appTranslate('recovery.policy.number', $language, [], 'Au moins un chiffre')) ?></li>
                            <li data-policy-rule="symbol"><?= htmlspecialchars(appTranslate('recovery.policy.symbol', $language, [], 'Au moins un symbole ou signe')) ?></li>
                            <li data-policy-rule="identityFree"><?= htmlspecialchars(appTranslate('recovery.policy.identity', $language, [], 'Aucune suite liée à MageStock ou au compte')) ?></li>
                            <li data-policy-rule="matches"><?= htmlspecialchars(appTranslate('recovery.policy.matches', $language, [], 'Les deux saisies correspondent')) ?></li>
                        </ul>
                        <p class="auth-policy__status" id="passwordPolicyStatus" data-pending-text="<?= htmlspecialchars(appTranslate('recovery.policy.pending', $language, [], 'Saisissez un mot de passe pour vérifier les critères.')) ?>" data-valid-text="<?= htmlspecialchars(appTranslate('recovery.policy.valid', $language, [], 'Tous les critères visibles sont satisfaits.')) ?>"><?= htmlspecialchars(appTranslate('recovery.policy.pending', $language, [], 'Saisissez un mot de passe pour vérifier les critères.')) ?></p>
                    </section>
                    <button class="auth-submit" id="recoverySubmit" type="submit" disabled><svg viewBox="0 0 24 24"><path d="M15 7a4 4 0 1 0-7.7 1.5L3 13v4h4v-2h2v-2h2l1.5-1.5"></path></svg><?= htmlspecialchars(appTranslate('recovery.submit', $language, [], 'Réinitialiser et fermer les anciennes connexions')) ?></button>
                </form>
                <div class="auth-security-note"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"></circle><path d="M12 11v5"></path><path d="M12 8h.01"></path></svg><span><strong><?= htmlspecialchars(appTranslate('recovery.no_token.title', $language, [], 'Vous n’avez pas de jeton ?')) ?></strong><br><?= htmlspecialchars(appTranslate('recovery.no_token.text', $language, [], 'Un jeton ne peut être créé que par un administrateur MageStock autorisé. Demandez-lui d’ouvrir votre compte dans Utilisateurs et accès.')) ?></span></div>
            <?php endif; ?>
            <div class="auth-links"><a href="<?= htmlspecialchars(appUrl('login')) ?>"><?= htmlspecialchars(appTranslate('recovery.return_login', $language, [], 'Retour à la connexion')) ?></a></div>
            <footer class="auth-page-footer" aria-label="MageStock">
                <a class="auth-page-footer__brand"
                   href="https://magekorp.com/contact"
                   target="_blank"
                   rel="noopener noreferrer external"
                   aria-label="<?= htmlspecialchars(appTranslate('shell.footer.contact', $language, [], 'Contacter MageKorp')) ?>">
                    <picture>
                        <source media="(max-width: 640px)" srcset="<?= htmlspecialchars(assetUrl('img/magekorp-mark.png')) ?>">
                        <img src="<?= htmlspecialchars(assetUrl('img/magekorp-logo-horizontal.png')) ?>" alt="MageKorp">
                    </picture>
                </a>
                <div class="auth-page-footer__meta">
                    <span><?= htmlspecialchars(appTranslate('shell.footer.developed_by', $language, [], 'Développé par')) ?> <strong>Régis KREMER</strong></span>
                    <span aria-hidden="true">•</span>
                    <strong>MageStock</strong>
                    <span class="auth-page-footer__version">v<?= htmlspecialchars(APP_VER) ?></span>
                    <span aria-hidden="true">•</span>
                    <a href="https://magekorp.com/contact"
                       target="_blank"
                       rel="noopener noreferrer external"><?= htmlspecialchars(appTranslate('shell.footer.magekorp_app', $language, [], 'une application web MageKorp')) ?></a>
                    <span aria-hidden="true">•</span>
                    <span>&copy; <?= date('Y') ?></span>
                    <span aria-hidden="true">•</span>
                    <span><?= htmlspecialchars(appTranslate('shell.footer.license', $language, [], 'Licence d’utilisation')) ?></span>
                    <span aria-hidden="true">•</span>
                    <a href="https://magekorp.com/contact"
                       target="_blank"
                       rel="noopener noreferrer external"><?= htmlspecialchars(appTranslate('shell.footer.contact', $language, [], 'Contacter MageKorp')) ?></a>
                </div>
            </footer>
        </div>
    </main>
</div>
<script>
window.MageStockRuntime = <?= json_encode(['basePath'=>appBasePath(),'cookiePath'=>appCookiePath(),'apiBase'=>apiUrl(),'assetBase'=>assetUrl(),'version'=>APP_VER], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?>;
window._SM_LANG_PHP = <?= json_encode($language) ?>;
window.SM_LANG = window._SM_LANG_PHP;
</script>
<script src="<?= htmlspecialchars(assetUrl('js/i18n.js?v=' . filemtime(__DIR__ . '/assets/js/i18n.js'))) ?>"></script>
<script src="<?= htmlspecialchars(assetUrl('js/auth-gateway.js?v=2.6.121')) ?>"></script>
<?php if(!$success): ?><script src="<?= htmlspecialchars(assetUrl('js/password-recovery.js?v=2.6.121')) ?>"></script><?php endif; ?>
</body>
</html>
