fix: block doc artifacts and add error templates
This commit is contained in:
@@ -8,6 +8,8 @@ robots.txt
|
|||||||
site.webmanifest
|
site.webmanifest
|
||||||
mentions-legales.php
|
mentions-legales.php
|
||||||
dons.php
|
dons.php
|
||||||
|
404.php
|
||||||
|
500.php
|
||||||
|
|
||||||
# Fichiers de l'IDE/éditeur
|
# Fichiers de l'IDE/éditeur
|
||||||
.vscode/
|
.vscode/
|
||||||
@@ -37,6 +39,9 @@ uploads/*
|
|||||||
# vendor/
|
# vendor/
|
||||||
# node_modules/
|
# node_modules/
|
||||||
|
|
||||||
|
# Rapport d'audit interne OKI (ne pas committer)
|
||||||
|
audit/
|
||||||
|
|
||||||
# Artefacts de tests locaux
|
# Artefacts de tests locaux
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Page d'erreur 404 - Modèle pour ANNU KUTE CED
|
||||||
|
*
|
||||||
|
* Pour l'activer : copiez ce fichier vers 404.php
|
||||||
|
*
|
||||||
|
* Utilisée automatiquement par dons.php lorsque les dons sont désactivés.
|
||||||
|
* Peut aussi servir de page d'erreur globale, par exemple :
|
||||||
|
* - Apache : ErrorDocument 404 /404.php
|
||||||
|
* - nginx : error_page 404 /404.php;
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Chargement autonome si la page est appelée directement
|
||||||
|
// (dons.php a déjà chargé ces fichiers avant d'inclure celui-ci)
|
||||||
|
if (!defined('SITE_NAME')) {
|
||||||
|
require_once __DIR__ . '/includes/config.php';
|
||||||
|
require_once __DIR__ . '/includes/security.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code(404);
|
||||||
|
|
||||||
|
// Incluse depuis dons.php, la page est servie avant setSecurityHeaders() :
|
||||||
|
// on applique les en-têtes de sécurité ici (aucune sortie n'a encore eu lieu).
|
||||||
|
if (function_exists('setSecurityHeaders')) {
|
||||||
|
setSecurityHeaders();
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
<title>Page introuvable - <?php echo SITE_NAME; ?></title>
|
||||||
|
<meta name="description" content="La page demandée est introuvable ou n'est pas disponible.">
|
||||||
|
<meta name="robots" content="noindex">
|
||||||
|
|
||||||
|
<!-- Styles -->
|
||||||
|
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime(__DIR__ . '/css/styles.css'); ?>">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" crossorigin="anonymous">
|
||||||
|
|
||||||
|
<!-- Favicons -->
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="img/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="img/favicon-16x16.png">
|
||||||
|
<link rel="manifest" href="site.webmanifest">
|
||||||
|
<link rel="icon" type="image/x-icon" href="img/favicon.ico">
|
||||||
|
<meta name="theme-color" content="#FF0000">
|
||||||
|
|
||||||
|
<!-- Script pour éviter le flash en mode sombre -->
|
||||||
|
<script nonce="<?php echo getCspNonce(); ?>">
|
||||||
|
(function() {
|
||||||
|
const savedTheme = localStorage.getItem('theme');
|
||||||
|
const systemPrefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
const shouldUseDark = savedTheme === 'dark' || (!savedTheme && systemPrefersDark);
|
||||||
|
if (shouldUseDark) {
|
||||||
|
document.documentElement.setAttribute('data-theme', 'dark');
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<?php include __DIR__ . '/includes/sidebar.php'; ?>
|
||||||
|
|
||||||
|
<main class="main-content">
|
||||||
|
<?php include __DIR__ . '/includes/header.php'; ?>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<section class="error-page">
|
||||||
|
<p class="error-code" aria-hidden="true">404</p>
|
||||||
|
<h1>Page introuvable</h1>
|
||||||
|
<p>La page que vous recherchez n'existe pas, a été déplacée ou n'est pas disponible pour le moment.</p>
|
||||||
|
<p class="error-actions">
|
||||||
|
<a href="index.php" class="error-home-link"><i class="fas fa-home" aria-hidden="true"></i> Retour à l'accueil</a>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php include __DIR__ . '/includes/footer.php'; ?>
|
||||||
|
<?php include __DIR__ . '/includes/mobile-menu.php'; ?>
|
||||||
|
|
||||||
|
<script src="js/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Page d'erreur 500 - Modèle pour ANNU KUTE CED
|
||||||
|
*
|
||||||
|
* Pour l'activer : copiez ce fichier vers 500.php
|
||||||
|
*
|
||||||
|
* Utilisée automatiquement par dons.php lorsqu'aucune plateforme de don
|
||||||
|
* n'est configurée. Peut aussi servir de page d'erreur globale, par exemple :
|
||||||
|
* - Apache : ErrorDocument 500 /500.php
|
||||||
|
* - nginx : error_page 500 /500.php;
|
||||||
|
*
|
||||||
|
* N'affichez jamais le détail de l'erreur (trace, chemin serveur…) sur
|
||||||
|
* cette page : il est consigné dans le journal d'erreurs du serveur.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Chargement autonome si la page est appelée directement
|
||||||
|
// (dons.php a déjà chargé ces fichiers avant d'inclure celui-ci)
|
||||||
|
if (!defined('SITE_NAME')) {
|
||||||
|
require_once __DIR__ . '/includes/config.php';
|
||||||
|
require_once __DIR__ . '/includes/security.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code(500);
|
||||||
|
|
||||||
|
// Incluse depuis dons.php, la page est servie avant setSecurityHeaders() :
|
||||||
|
// on applique les en-têtes de sécurité ici (aucune sortie n'a encore eu lieu).
|
||||||
|
if (function_exists('setSecurityHeaders')) {
|
||||||
|
setSecurityHeaders();
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
<title>Erreur interne - <?php echo SITE_NAME; ?></title>
|
||||||
|
<meta name="description" content="Une erreur interne est survenue. Veuillez réessayer plus tard.">
|
||||||
|
<meta name="robots" content="noindex">
|
||||||
|
|
||||||
|
<!-- Styles -->
|
||||||
|
<link rel="stylesheet" href="css/styles.css?v=<?php echo filemtime(__DIR__ . '/css/styles.css'); ?>">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" crossorigin="anonymous">
|
||||||
|
|
||||||
|
<!-- Favicons -->
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="img/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="img/favicon-16x16.png">
|
||||||
|
<link rel="manifest" href="site.webmanifest">
|
||||||
|
<link rel="icon" type="image/x-icon" href="img/favicon.ico">
|
||||||
|
<meta name="theme-color" content="#FF0000">
|
||||||
|
|
||||||
|
<!-- Script pour éviter le flash en mode sombre -->
|
||||||
|
<script nonce="<?php echo getCspNonce(); ?>">
|
||||||
|
(function() {
|
||||||
|
const savedTheme = localStorage.getItem('theme');
|
||||||
|
const systemPrefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
const shouldUseDark = savedTheme === 'dark' || (!savedTheme && systemPrefersDark);
|
||||||
|
if (shouldUseDark) {
|
||||||
|
document.documentElement.setAttribute('data-theme', 'dark');
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<?php include __DIR__ . '/includes/sidebar.php'; ?>
|
||||||
|
|
||||||
|
<main class="main-content">
|
||||||
|
<?php include __DIR__ . '/includes/header.php'; ?>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<section class="error-page">
|
||||||
|
<p class="error-code" aria-hidden="true">500</p>
|
||||||
|
<h1>Une erreur est survenue</h1>
|
||||||
|
<p>Le site rencontre un problème temporaire. L'incident a été consigné
|
||||||
|
et sera examiné par l'équipe technique. Merci de réessayer dans quelques instants.</p>
|
||||||
|
<p class="error-actions">
|
||||||
|
<a href="index.php" class="error-home-link"><i class="fas fa-home" aria-hidden="true"></i> Retour à l'accueil</a>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php include __DIR__ . '/includes/footer.php'; ?>
|
||||||
|
<?php include __DIR__ . '/includes/mobile-menu.php'; ?>
|
||||||
|
|
||||||
|
<script src="js/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -22,6 +22,13 @@ RewriteRule ^(includes|cache|docs|conf)/ - [F,L]
|
|||||||
Require all denied
|
Require all denied
|
||||||
</FilesMatch>
|
</FilesMatch>
|
||||||
|
|
||||||
|
# Bloquer les artefacts de documentation à la racine web
|
||||||
|
# (DEPLOY.adoc/html/pdf, README.adoc…) : ils divulguent le plan
|
||||||
|
# d'infrastructure et ne doivent jamais être servis s'ils sont copiés
|
||||||
|
# sur l'hébergement. [^/]+ = fichiers à la racine uniquement, les
|
||||||
|
# éventuels .html légitimes en sous-répertoire restent servis.
|
||||||
|
RewriteRule ^[^/]+\.(adoc|html|pdf)$ - [F,L,NC]
|
||||||
|
|
||||||
# Empêcher l'exploration des répertoires
|
# Empêcher l'exploration des répertoires
|
||||||
Options -Indexes
|
Options -Indexes
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,16 @@ server {
|
|||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Bloquer les artefacts de documentation à la racine web
|
||||||
|
# (DEPLOY.adoc/html/pdf, README.adoc…) : ils divulguent le plan
|
||||||
|
# d'infrastructure et ne doivent jamais être servis s'ils sont copiés
|
||||||
|
# sur l'hébergement. [^/]+ = fichiers à la racine uniquement, les
|
||||||
|
# éventuels .html légitimes en sous-répertoire restent servis.
|
||||||
|
location ~* ^/[^/]+\.(adoc|html|pdf)$ {
|
||||||
|
deny all;
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
# Bloquer l'accès aux fichiers cachés
|
# Bloquer l'accès aux fichiers cachés
|
||||||
location ~ /\. {
|
location ~ /\. {
|
||||||
deny all;
|
deny all;
|
||||||
|
|||||||
+18
-16
@@ -123,7 +123,7 @@ function linkUrlsInText(string $text): string {
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<!-- Script pour éviter le flash en mode sombre -->
|
<!-- Script pour éviter le flash en mode sombre -->
|
||||||
<script>
|
<script nonce="<?php echo getCspNonce(); ?>">
|
||||||
(function() {
|
(function() {
|
||||||
const savedTheme = localStorage.getItem('theme');
|
const savedTheme = localStorage.getItem('theme');
|
||||||
const systemPrefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
const systemPrefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
@@ -223,10 +223,10 @@ function linkUrlsInText(string $text): string {
|
|||||||
|
|
||||||
<!-- Onglets don ponctuel / mensuel -->
|
<!-- Onglets don ponctuel / mensuel -->
|
||||||
<div class="donation-tabs">
|
<div class="donation-tabs">
|
||||||
<button class="tab-btn active" onclick="switchTab('onetime')">
|
<button class="tab-btn active" data-tab="onetime">
|
||||||
<i class="fas fa-hand-holding-heart"></i> Don ponctuel
|
<i class="fas fa-hand-holding-heart"></i> Don ponctuel
|
||||||
</button>
|
</button>
|
||||||
<button class="tab-btn" onclick="switchTab('monthly')">
|
<button class="tab-btn" data-tab="monthly">
|
||||||
<i class="fas fa-sync-alt"></i> Don mensuel
|
<i class="fas fa-sync-alt"></i> Don mensuel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -308,20 +308,22 @@ function linkUrlsInText(string $text): string {
|
|||||||
<?php include 'includes/mobile-menu.php'; ?>
|
<?php include 'includes/mobile-menu.php'; ?>
|
||||||
|
|
||||||
<!-- Script pour les onglets -->
|
<!-- Script pour les onglets -->
|
||||||
<script>
|
<script nonce="<?php echo getCspNonce(); ?>">
|
||||||
function switchTab(tab) {
|
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||||
// Cacher tous les contenus et désactiver tous les boutons
|
btn.addEventListener('click', function() {
|
||||||
document.querySelectorAll('.tab-content').forEach(content => {
|
// Cacher tous les contenus et désactiver tous les boutons
|
||||||
content.classList.remove('active');
|
document.querySelectorAll('.tab-content').forEach(content => {
|
||||||
});
|
content.classList.remove('active');
|
||||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
});
|
||||||
btn.classList.remove('active');
|
document.querySelectorAll('.tab-btn').forEach(otherBtn => {
|
||||||
});
|
otherBtn.classList.remove('active');
|
||||||
|
});
|
||||||
|
|
||||||
// Afficher le contenu sélectionné et activer le bouton
|
// Afficher le contenu sélectionné et activer le bouton
|
||||||
document.getElementById(tab + '-tab').classList.add('active');
|
document.getElementById(this.dataset.tab + '-tab').classList.add('active');
|
||||||
event.target.classList.add('active');
|
this.classList.add('active');
|
||||||
}
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="js/main.js"></script>
|
<script src="js/main.js"></script>
|
||||||
|
|||||||
@@ -0,0 +1,208 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Tests du groupe « dons » : synchronisation dons.php / dons.php.sample,
|
||||||
|
* gabarits d'erreur 404/500 et blocage des artefacts de documentation.
|
||||||
|
*
|
||||||
|
* ARC-7 : dons.php inclut 404.php / 500.php — les gabarits .sample existent
|
||||||
|
* et le chemin d'erreur affiche une page propre (sans warning PHP).
|
||||||
|
* SEC-6 : DEPLOY.adoc/html/pdf à la racine sont bloqués par les modèles
|
||||||
|
* nginx / Apache.
|
||||||
|
* Dérive : linkUrlsInText() présent dans dons.php comme dans le .sample.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$root = dirname(__DIR__, 2);
|
||||||
|
|
||||||
|
// --- Synchronisation dons.php / dons.php.sample -----------------------------
|
||||||
|
|
||||||
|
$donsLive = file_get_contents($root . '/dons.php');
|
||||||
|
$donsSample = file_get_contents($root . '/dons.php.sample');
|
||||||
|
|
||||||
|
assertContains(
|
||||||
|
'function linkUrlsInText',
|
||||||
|
$donsLive,
|
||||||
|
'dons.php définit linkUrlsInText() comme le .sample'
|
||||||
|
);
|
||||||
|
assertContains(
|
||||||
|
'linkUrlsInText(DONATIONS_OKI_DISCLAIMER)',
|
||||||
|
$donsLive,
|
||||||
|
'dons.php rend le message de transparence avec linkUrlsInText()'
|
||||||
|
);
|
||||||
|
assertNotContains(
|
||||||
|
'htmlspecialchars(DONATIONS_OKI_DISCLAIMER)',
|
||||||
|
$donsLive,
|
||||||
|
'dons.php n\'utilise plus un simple htmlspecialchars() pour le message de transparence'
|
||||||
|
);
|
||||||
|
|
||||||
|
// La définition de la fonction doit être identique dans les deux fichiers
|
||||||
|
// (verrou anti-dérive entre le modèle et la copie d'instance)
|
||||||
|
$extractFunction = function (string $source): ?string {
|
||||||
|
if (preg_match('/(function linkUrlsInText\(string \$text\): string \{.*?\n\})/s', $source, $m)) {
|
||||||
|
return $m[1];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
$extractFunction($donsSample),
|
||||||
|
$extractFunction($donsLive),
|
||||||
|
'linkUrlsInText() est identique dans dons.php et dons.php.sample'
|
||||||
|
);
|
||||||
|
|
||||||
|
// --- linkUrlsInText() : comportement ----------------------------------------
|
||||||
|
|
||||||
|
if (!function_exists('linkUrlsInText')) {
|
||||||
|
eval($extractFunction($donsSample));
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
'Texte & "échappé"',
|
||||||
|
linkUrlsInText('Texte & "échappé"'),
|
||||||
|
'linkUrlsInText échappe le texte sans URL'
|
||||||
|
);
|
||||||
|
|
||||||
|
$linked = linkUrlsInText('Soutenez-nous sur https://example.com/dons merci');
|
||||||
|
assertContains(
|
||||||
|
'<a href="https://example.com/dons" target="_blank" rel="noopener noreferrer">https://example.com/dons</a>',
|
||||||
|
$linked,
|
||||||
|
'linkUrlsInText rend les URLs cliquables avec target et rel'
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
'<script>alert(1)</script>',
|
||||||
|
linkUrlsInText('<script>alert(1)</script>'),
|
||||||
|
'linkUrlsInText échappe le HTML injecté dans le texte'
|
||||||
|
);
|
||||||
|
|
||||||
|
assertContains(
|
||||||
|
'href="https://example.com/?a=1&b=2"',
|
||||||
|
linkUrlsInText('Lien : https://example.com/?a=1&b=2'),
|
||||||
|
'linkUrlsInText échappe les & dans l\'URL du lien'
|
||||||
|
);
|
||||||
|
|
||||||
|
// --- Gabarits d'erreur 404.php.sample / 500.php.sample (ARC-7) ---------------
|
||||||
|
|
||||||
|
foreach (['404' => '404.php.sample', '500' => '500.php.sample'] as $code => $file) {
|
||||||
|
$path = $root . '/' . $file;
|
||||||
|
assertTrue(file_exists($path), "$file existe");
|
||||||
|
$template = file_get_contents($path);
|
||||||
|
|
||||||
|
assertContains(
|
||||||
|
"http_response_code($code)",
|
||||||
|
$template,
|
||||||
|
"$file positionne le code HTTP $code"
|
||||||
|
);
|
||||||
|
assertContains(
|
||||||
|
'noindex',
|
||||||
|
$template,
|
||||||
|
"$file demande la non-indexation (robots noindex)"
|
||||||
|
);
|
||||||
|
assertNotContains(
|
||||||
|
'style="',
|
||||||
|
$template,
|
||||||
|
"$file n'utilise pas d'attribut style inline (bloqué par la CSP)"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tout <script> inline (sans src) doit porter un nonce CSP
|
||||||
|
preg_match_all('/<script(?![^>]*\bsrc=)[^>]*>/i', $template, $inlineScripts);
|
||||||
|
$allNonced = true;
|
||||||
|
foreach ($inlineScripts[0] as $scriptTag) {
|
||||||
|
if (strpos($scriptTag, 'nonce=') === false) {
|
||||||
|
$allNonced = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue($allNonced, "$file : tous les scripts inline portent un nonce CSP");
|
||||||
|
|
||||||
|
// Le gabarit doit être syntaxiquement valide
|
||||||
|
$lint = shell_exec(escapeshellarg(PHP_BINARY) . ' -l ' . escapeshellarg($path) . ' 2>&1');
|
||||||
|
assertContains('No syntax errors', $lint, "$file passe php -l");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Blocage des artefacts de documentation (SEC-6) --------------------------
|
||||||
|
|
||||||
|
$nginxConf = file_get_contents($root . '/conf/nginx.conf.sample');
|
||||||
|
assertContains(
|
||||||
|
'^/[^/]+\.(adoc|html|pdf)$',
|
||||||
|
$nginxConf,
|
||||||
|
'nginx.conf.sample bloque les .adoc/.html/.pdf à la racine web'
|
||||||
|
);
|
||||||
|
|
||||||
|
$htaccess = file_get_contents($root . '/conf/.htaccess.sample');
|
||||||
|
assertContains(
|
||||||
|
'RewriteRule ^[^/]+\.(adoc|html|pdf)$ - [F,L,NC]',
|
||||||
|
$htaccess,
|
||||||
|
'.htaccess.sample bloque les .adoc/.html/.pdf à la racine web'
|
||||||
|
);
|
||||||
|
|
||||||
|
// --- dons.php : chemins d'erreur 404 et 500 (exécution réelle) ---------------
|
||||||
|
|
||||||
|
// dons.php inclut 404.php / 500.php (copies d'instance, non versionnées) :
|
||||||
|
// on les crée à partir des .sample si elles sont absentes, puis on nettoie.
|
||||||
|
$createdCopies = [];
|
||||||
|
foreach (['404.php', '500.php'] as $copy) {
|
||||||
|
$copyPath = $root . '/' . $copy;
|
||||||
|
if (!file_exists($copyPath)) {
|
||||||
|
copy($root . '/' . $copy . '.sample', $copyPath);
|
||||||
|
$createdCopies[] = $copyPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exécute dons.php dans un sous-processus PHP avec des constantes prédéfinies
|
||||||
|
* (PEERTUBE_URL privée : aucun appel réseau, cf. bootstrap.php).
|
||||||
|
*
|
||||||
|
* @param array $defines Constantes définies avant le chargement de dons.php
|
||||||
|
* @return string Sortie complète du sous-processus (stdout + stderr)
|
||||||
|
*/
|
||||||
|
$runDonsPage = function (array $defines) use ($root): string {
|
||||||
|
$lines = ['<?php'];
|
||||||
|
foreach ($defines as $name => $value) {
|
||||||
|
$lines[] = 'define(' . var_export($name, true) . ', ' . var_export($value, true) . ');';
|
||||||
|
}
|
||||||
|
$lines[] = 'chdir(' . var_export($root, true) . ');';
|
||||||
|
$lines[] = 'require ' . var_export($root . '/dons.php', true) . ';';
|
||||||
|
|
||||||
|
$runner = tempnam(sys_get_temp_dir(), 'dons-runner-') . '.php';
|
||||||
|
file_put_contents($runner, implode("\n", $lines) . "\n");
|
||||||
|
$output = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($runner) . ' 2>&1');
|
||||||
|
unlink($runner);
|
||||||
|
|
||||||
|
return $output ?? '';
|
||||||
|
};
|
||||||
|
|
||||||
|
$noPlatform = [
|
||||||
|
'PEERTUBE_URL' => 'http://127.0.0.1', // URL privée volontaire : bloque tout appel API réel
|
||||||
|
'APP_HOST_NAME' => 'test.local',
|
||||||
|
'DONATIONS_ENABLED' => true,
|
||||||
|
'LIBERAPAY_URL' => '',
|
||||||
|
'KOFI_URL' => '',
|
||||||
|
'STRIPE_ENABLED' => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
$phpErrorNeedles = ['PHP Warning', 'PHP Notice', 'PHP Deprecated', 'PHP Fatal error',
|
||||||
|
'Fatal error', 'Warning:', 'Notice:', 'Deprecated:', 'Uncaught'];
|
||||||
|
|
||||||
|
// Dons activés mais aucune plateforme configurée -> page 500 propre
|
||||||
|
$output500 = $runDonsPage($noPlatform);
|
||||||
|
assertContains(
|
||||||
|
'<p class="error-code" aria-hidden="true">500</p>',
|
||||||
|
$output500,
|
||||||
|
'dons.php sans plateforme configurée affiche la page 500'
|
||||||
|
);
|
||||||
|
foreach ($phpErrorNeedles as $needle) {
|
||||||
|
assertNotContains($needle, $output500, "page 500 sans erreur PHP ($needle)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dons désactivés -> page 404 propre
|
||||||
|
$output404 = $runDonsPage(['DONATIONS_ENABLED' => false] + $noPlatform);
|
||||||
|
assertContains(
|
||||||
|
'<p class="error-code" aria-hidden="true">404</p>',
|
||||||
|
$output404,
|
||||||
|
'dons.php avec dons désactivés affiche la page 404'
|
||||||
|
);
|
||||||
|
foreach ($phpErrorNeedles as $needle) {
|
||||||
|
assertNotContains($needle, $output404, "page 404 sans erreur PHP ($needle)");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($createdCopies as $copyPath) {
|
||||||
|
unlink($copyPath);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user