# Slim Blog ![PHP](https://img.shields.io/badge/PHP-8.4.1%2B-777BB4?logo=php&logoColor=white) ![Slim](https://img.shields.io/badge/Slim-4-74a045) ![PHPStan](https://img.shields.io/badge/PHPStan-niveau%208-blue) ![Tests](https://img.shields.io/badge/tests-355%20passing-brightgreen) ![Licence](https://img.shields.io/badge/licence-MIT-green) Blog multi-utilisateurs modulaire développé avec Slim 4. Les domaines `Auth`, `Category`, `Media`, `User` et `Shared` sont indépendants du domaine métier et réutilisables sans modification pour d'autres projets (boutique, portfolio…). ## Fonctionnalités - **Articles** — création, édition, suppression avec éditeur WYSIWYG, slugs stables - **Catégories** — filtrage sur la page d'accueil et dans l'interface admin - **Médias** — upload WebP avec déduplication SHA-256 - **Recherche** — full-text FTS5 cumulable avec le filtre catégorie - **Comptes** — trois rôles (`user`, `editor`, `admin`), réinitialisation de mot de passe par email - **RSS** — flux 2.0 des 20 derniers articles (`/rss.xml`) - **Protection brute-force** — verrouillage par IP après trop de tentatives échouées sur la connexion et la réinitialisation de mot de passe ## Stack | Rôle | Librairie | |-------------------|------------------------------------------------------------| | Framework HTTP | [Slim 4](https://www.slimframework.com) | | Base de données | [SQLite](https://sqlite.org) via PDO natif | | Templates | [Twig](https://twig.symfony.com) | | CSS | [Sass](https://sass-lang.com) (7-1, BEM) | | Éditeur WYSIWYG | [Trumbowyg](https://alex-d.github.io/Trumbowyg/) | | Emails | [PHPMailer](https://github.com/PHPMailer/PHPMailer) | | Logging | [Monolog](https://github.com/Seldaek/monolog) | | Sanitisation HTML | [HTMLPurifier](http://htmlpurifier.org) | | Protection CSRF | [Slim CSRF](https://github.com/slimphp/Slim-Csrf) | | Injection de dépendances | [PHP-DI](https://php-di.org) (autowiring) | | Analyse statique | [PHPStan](https://phpstan.org) (niveau 8) | ## Développement **Prérequis :** PHP 8.4.1+ avec `pdo_sqlite`, `intl`, `fileinfo`, `gd` (WebP), `xml` (dom), Composer, Node.js 18+ ```bash git clone https://git.netig.net/netig/slim-blog cd slim-blog composer install npm install && npm run build cp .env.example .env php bin/provision.php php -S localhost:8080 -t public ``` Pour surveiller les modifications SCSS et recompiler automatiquement en développement : ```bash npm run watch ``` > `npm run watch` est réservé au développement local. En production, le CSS est compilé une fois lors du build Docker. Pour tester l'upload de fichiers, augmenter les limites PHP : ```bash php -S localhost:8080 -t public -d upload_max_filesize=6M -d post_max_size=8M ``` > Si `upload_max_filesize` est trop basse, PHP abandonne l'intégralité du corps POST — fichier > **et** tokens CSRF — ce qui provoque une erreur générique sans message explicite. ## Production **Prérequis :** Docker, Docker Compose ```bash git clone https://git.netig.net/netig/slim-blog cd slim-blog cp .env.example .env # Définir APP_ENV=production, APP_URL, ADMIN_PASSWORD et la configuration SMTP docker compose up -d --build docker compose exec app php bin/provision.php ``` > Le démarrage en production avec `ADMIN_PASSWORD=changeme123` est bloqué intentionnellement. > `--build` est superflu sur une machine vierge mais garantit une image à jour dans tous les cas. **Note sur le `.env` :** `docker-compose.yml` monte le fichier `.env` physiquement dans le conteneur (`- ./.env:/var/www/app/.env:ro`). Ce mount est nécessaire car `phpdotenv` lit un fichier sur le disque via `file_get_contents` — `env_file` seul ne suffit pas. Au premier démarrage, l'entrypoint initialise automatiquement `./data/` sur l'hôte : ``` data/ ├── public/ # assets compilés, index.php — servis par Nginx │ └── media/ # uploads utilisateurs — persistés entre redéploiements ├── database/ # migrations + app.sqlite └── var/ # cache Twig/HTMLPurifier, logs ``` Pour mettre à jour le site après un changement de code : ```bash docker compose build docker compose up -d ``` ### Logs Docker Les erreurs PHP remontent dans `docker compose logs` grâce à `error_log = /dev/stderr` dans `docker/php/php.ini`. Sans ce réglage, les erreurs des workers FPM ne sont pas transmises au process principal et n'apparaissent pas dans les logs Docker. ### Durcissement HTTP `docker/php/php.ini` désactive l'en-tête `X-Powered-By` (`expose_php = Off`) et renomme le cookie de session de `PHPSESSID` en `sid` pour ne pas exposer la stack technique. `docker/nginx/default.conf` ajoute quatre en-têtes de sécurité sur toutes les réponses : | En-tête | Valeur | Protection | |---------|--------|------------| | `X-Frame-Options` | `SAMEORIGIN` | Clickjacking | | `X-Content-Type-Options` | `nosniff` | Sniffing MIME | | `Referrer-Policy` | `strict-origin-when-cross-origin` | Fuite d'URL | | `Permissions-Policy` | `camera=(), microphone=(), geolocation=()` | APIs navigateur | ### Derrière un reverse proxy (Caddy, Nginx…) Nginx écoute sur `127.0.0.1:8888` (câblé dans `docker-compose.yml`). Configurer aussi `TRUSTED_PROXIES` si vous déployez l'application derrière un autre proxy que le Nginx Docker fourni. En stack Docker par défaut, `docker-compose.yml` force `TRUSTED_PROXIES=*` côté conteneur PHP-FPM pour faire confiance au Nginx interne. Exemple de configuration Caddy : ```caddy https://blog.exemple.com { header Strict-Transport-Security max-age=31536000; reverse_proxy localhost:8888 } ``` ## Variables d'environnement | Variable | Description | Exemple | |-------------------|--------------------------------------------------------------------------|----------------------------| | `APP_ENV` | `development` ou `production` | `production` | | `APP_URL` | URL de base (liens emails, flux RSS) — inclure le port en développement | `http://localhost:8080` | | `APP_NAME` | Nom du blog (flux RSS, emails) | `Slim Blog` | | `TIMEZONE` | Fuseau horaire PHP | `Europe/Paris` | | `TRUSTED_PROXIES` | Proxies autorisés à fournir `X-Forwarded-For` / `X-Forwarded-Proto` | `127.0.0.1,::1` ou `*` | | `ADMIN_USERNAME` | Nom d'utilisateur du compte admin | `admin` | | `ADMIN_EMAIL` | Email du compte admin | `admin@example.com` | | `ADMIN_PASSWORD` | Mot de passe admin (obligatoire en production) | *(à changer)* | | `MAIL_HOST` | Serveur SMTP | `smtp.example.com` | | `MAIL_PORT` | Port SMTP (`587` TLS, `465` SSL) | `587` | | `MAIL_USERNAME` | Identifiant SMTP | `noreply@example.com` | | `MAIL_PASSWORD` | Mot de passe SMTP | *(à renseigner)* | | `MAIL_ENCRYPTION` | `tls` ou `ssl` | `tls` | | `MAIL_FROM` | Adresse expéditeur | `noreply@example.com` | | `MAIL_FROM_NAME` | Nom expéditeur | `Slim Blog` | | `UPLOAD_MAX_SIZE` | Taille max upload en octets | `5242880` | ## Documentation - [Guide technique](docs/GUIDE.md) — bases PHP, architecture en domaines, faire évoluer le projet - [Architecture](docs/ARCHITECTURE.md) — référence concise : domaines, schéma BDD, routes, arborescence - [Installation — développement](#développement) — prérequis, lancement local, upload, SCSS - [Installation — production](#production) — Docker, reverse proxy, logs - [Variables d'environnement](#variables-denvironnement) - [Contribuer](CONTRIBUTING.md) — tests, conventions ## Licence Le code source est distribué sous licence [MIT](LICENSE). Le contenu du blog (articles publiés) est soumis à [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.fr). ## Provisioning Le provisionnement (migrations + seed admin) s'execute explicitement via `php bin/provision.php`. - Developpement local : executer `php bin/provision.php` apres `cp .env.example .env` - Docker / production : executer `docker compose exec app php bin/provision.php` apres le demarrage du conteneur Le runtime HTTP ne provisionne plus automatiquement la base. Si le schema n'est pas present, l'application echoue avec un message explicite demandant d'executer la commande de provisionnement.