v1.6.10 — fix suppression article (permissions répertoire) #78

Merged
cedricAbonnel merged 1 commits from dev into main 2026-05-15 20:30:49 +00:00
5 changed files with 44 additions and 12 deletions
Showing only changes of commit 996ab3e508 - Show all commits
+10
View File
@@ -5,6 +5,16 @@ Format : [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/) — versionnag
---
## [1.6.10] - 2026-05-15
### Corrigé
- Suppression d'article échouait silencieusement quand le répertoire UUID appartenait à un autre utilisateur que `www-data` (permissions `2755` → groupe sans écriture) — `removeDir()` échouait sans erreur visible et l'article restait accessible
- `ArticleManager::delete()` retourne maintenant `bool` : si le répertoire existe encore après tentative de suppression, les index ne sont pas reconstruits et l'utilisateur est redirigé vers l'article avec un message d'erreur
- `removeDir()` : erreurs PHP supprimées silencieusement (`@unlink`, `@rmdir`, `@scandir`) pour éviter les warnings qui cassaient les redirects
- `mkArticleDir()` : nouvelle méthode privée qui crée les répertoires d'articles avec `chmod 0775` explicite (contourne le umask), garantissant que `www-data` (groupe) a toujours les droits d'écriture
---
## [Unreleased]
---
+7 -1
View File
@@ -1114,7 +1114,13 @@ switch ($action) {
case 'delete':
requireAuth();
if ($uuid !== '') {
$articles->delete($uuid);
if (!$articles->delete($uuid)) {
$failedArt = $articles->getByUuid($uuid);
$failedSlug = $failedArt['slug'] ?? '';
$back = $failedSlug !== '' ? '/post/' . rawurlencode($failedSlug) : '/';
header('Location: ' . $back . '?delete_failed=1');
exit;
}
}
header('Location: /');
exit;
+1 -1
View File
@@ -1 +1 @@
1.6.9
1.6.10
+20 -10
View File
@@ -105,8 +105,8 @@ class ArticleManager
$publishedAt = $publishedAt !== '' ? $publishedAt : $now;
$dir = $this->dataDir . '/' . $uuid;
mkdir($dir, 0755, true);
mkdir($dir . '/files', 0755, true);
$this->mkArticleDir($dir);
$this->mkArticleDir($dir . '/files');
$meta = [
'uuid' => $uuid,
@@ -155,7 +155,7 @@ class ArticleManager
if ($contentChanged || $titleChanged) {
$revDir = $this->dataDir . '/' . $uuid . '/revisions';
if (!is_dir($revDir)) {
mkdir($revDir, 0755, true);
$this->mkArticleDir($revDir);
}
$n = count($revisions) + 1;
$revFile = sprintf('%s/%04d.md', $revDir, $n);
@@ -482,7 +482,7 @@ class ArticleManager
$isImage = str_starts_with($mime, 'image/');
$filesDir = $this->dataDir . '/' . $uuid . '/files';
if (!is_dir($filesDir)) {
mkdir($filesDir, 0755, true);
$this->mkArticleDir($filesDir);
}
if ($isImage) {
@@ -806,10 +806,10 @@ class ArticleManager
$this->git?->commit("featured: " . ($meta['title'] ?? $uuid) . " (" . ($featured ? 'on' : 'off') . ")");
}
public function delete(string $uuid): void
public function delete(string $uuid): bool
{
if (!$this->isValidUuid($uuid)) {
return;
return false;
}
$dir = $this->dataDir . '/' . $uuid;
$title = null;
@@ -823,9 +823,13 @@ class ArticleManager
@unlink($this->slugIndexPath());
$this->removeDir($dir);
}
if (is_dir($dir)) {
return false;
}
$this->rebuildSearchIndex();
$this->rebuildBacklinksCache();
$this->git?->commit("delete: " . ($title ?? $uuid));
return true;
}
// ------------------------------------------------------------------ //
@@ -1110,7 +1114,7 @@ class ArticleManager
}
$dir = $this->dataDir . '/' . $uuid . '/files';
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
$this->mkArticleDir($dir);
}
$mime = mime_content_type($uploadedFile['tmp_name']) ?: 'application/octet-stream';
@@ -1370,13 +1374,19 @@ class ArticleManager
*/
private function removeDir(string $dir): void
{
foreach (scandir($dir) as $entry) {
foreach (@scandir($dir) ?: [] as $entry) {
if ($entry === '.' || $entry === '..') {
continue;
}
$path = $dir . '/' . $entry;
is_dir($path) ? $this->removeDir($path) : unlink($path);
is_dir($path) ? $this->removeDir($path) : @unlink($path);
}
rmdir($dir);
@rmdir($dir);
}
private function mkArticleDir(string $path): void
{
mkdir($path, 0777, true);
chmod($path, 0775);
}
}
+6
View File
@@ -173,6 +173,12 @@ $hasSources = (!empty($externalLinks) || !empty($files))
</div>
</div>
<div class="card-body">
<?php if (($_GET['delete_failed'] ?? '') === '1' && function_exists('isAdmin') && isAdmin()): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
Suppression impossible — droits insuffisants sur le répertoire de données.
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<div class="card-text post-content">
<?= $_renderedContent ?>
</div>