refactor: reorganize to standard PHP structure

- Moved /lib → /src (PHP source code)
- Moved /includes → /public/includes (main site templates)
- Admin section remains self-contained in /public/admin with its own /inc
- Updated all require/include paths across codebase
- Updated config/bootstrap.php, justfile, tests, docs
- All tests passing 

Structure now follows PHP best practices:
  /config      - Configuration files
  /database    - SQLite database + schema
  /docs        - Documentation (intact)
  /nginx       - Server config (intact)
  /public      - Web-accessible files (entry point)
    /admin     - Self-contained admin interface
    /assets    - CSS, fonts, icons
    /includes  - Main site templates (header/footer)
  /scripts     - Deployment scripts (intact)
  /src         - PHP source classes (Database, AdminAuth, RateLimit)
  /tests       - Test suites
This commit is contained in:
Théophile Gervreau-Mercier
2026-02-12 12:11:16 +01:00
parent 0b650cd3e7
commit 0e4921583e
26 changed files with 40 additions and 42 deletions

View File

@@ -25,7 +25,7 @@ if (php_sapi_name() === 'cli-server') {
// Simple helper function for including templates // Simple helper function for including templates
function include_template($name) { function include_template($name) {
$path = APP_ROOT . '/includes/' . $name; $path = APP_ROOT . '/public/includes/' . $name;
if (file_exists($path)) { if (file_exists($path)) {
include $path; include $path;
} }

View File

@@ -25,7 +25,7 @@ This deploys all files to `/var/www/posterg/`:
- `includes/``/var/www/posterg/includes/` - `includes/``/var/www/posterg/includes/`
- `config/``/var/www/posterg/config/` - `config/``/var/www/posterg/config/`
- `database/``/var/www/posterg/database/` - `database/``/var/www/posterg/database/`
- `lib/``/var/www/posterg/lib/` - `src/``/var/www/posterg/lib/`
### 3. Update Nginx Configuration ### 3. Update Nginx Configuration

View File

@@ -130,7 +130,7 @@ just fixtures
### Create a New Page ### Create a New Page
1. Create `newpage.php` in root 1. Create `newpage.php` in root
2. Add `require_once __DIR__ . '/lib/Database.php';` 2. Add `require_once __DIR__ . '/src/Database.php';`
3. Include header: `include 'inc/header.php';` 3. Include header: `include 'inc/header.php';`
4. Add your content 4. Add your content
5. Include footer: `include 'inc/footer.php';` 5. Include footer: `include 'inc/footer.php';`
@@ -138,7 +138,7 @@ just fixtures
Example: Example:
```php ```php
<?php <?php
require_once __DIR__ . '/lib/Database.php'; require_once __DIR__ . '/src/Database.php';
include 'inc/header.php'; include 'inc/header.php';
?> ?>
@@ -154,7 +154,7 @@ include 'inc/header.php';
### Add a Database Function ### Add a Database Function
1. Edit `lib/Database.php` 1. Edit `src/Database.php`
2. Add your method to the `Database` class 2. Add your method to the `Database` class
3. Write a test in `tests/Unit/DatabaseTest.php` 3. Write a test in `tests/Unit/DatabaseTest.php`
4. Run tests: `just test-unit` 4. Run tests: `just test-unit`
@@ -215,7 +215,7 @@ See `tests/README.md` for complete testing guide.
**Quick example:** **Quick example:**
```php ```php
<?php <?php
require_once __DIR__ . '/../../lib/Database.php'; require_once __DIR__ . '/../../src/Database.php';
echo "My Test\n"; echo "My Test\n";
echo "=======\n\n"; echo "=======\n\n";
@@ -243,7 +243,7 @@ just deploy
This deploys: This deploys:
- Public site (root PHP files) - Public site (root PHP files)
- Admin panel (`admin/`) - Admin panel (`admin/`)
- Shared libraries (`lib/`) - Shared libraries (`src/`)
**Note:** `vendor/` is automatically excluded from deployment. **Note:** `vendor/` is automatically excluded from deployment.

View File

@@ -66,7 +66,7 @@ git commit -m "Pre-migration checkpoint"
This will: This will:
- Move `apps/public/*` to root - Move `apps/public/*` to root
- Move `apps/admin/` to `admin/` - Move `apps/admin/` to `admin/`
- Rename `shared/` to `lib/` - Rename `shared/` to `src/`
- Update all `require` paths automatically - Update all `require` paths automatically
- Remove `apps/` directory - Remove `apps/` directory
- Update `.gitignore` - Update `.gitignore`
@@ -149,9 +149,9 @@ just deploy-admin
| `apps/public/assets/` | `assets/` | | `apps/public/assets/` | `assets/` |
| `apps/public/inc/` | `inc/` | | `apps/public/inc/` | `inc/` |
| `apps/admin/` | `admin/` | | `apps/admin/` | `admin/` |
| `shared/Database.php` | `lib/Database.php` | | `shared/Database.php` | `src/Database.php` |
| `shared/config.php` | `lib/config.php` | | `shared/config.php` | `src/config.php` |
| `shared/cache/` | `lib/cache/` | | `shared/cache/` | `src/cache/` |
### Path Updates ### Path Updates

View File

@@ -96,9 +96,9 @@ echo "vendor/" >> .gitignore
| `apps/public/inc/header.php` | `inc/header.php` | | `apps/public/inc/header.php` | `inc/header.php` |
| `apps/public/assets/posterg.css` | `assets/posterg.css` | | `apps/public/assets/posterg.css` | `assets/posterg.css` |
| `apps/admin/index.php` | `admin/index.php` | | `apps/admin/index.php` | `admin/index.php` |
| `shared/Database.php` | `lib/Database.php` | | `shared/Database.php` | `src/Database.php` |
| `shared/config.php` | `lib/config.php` | | `shared/config.php` | `src/config.php` |
| `shared/cache/` | `lib/cache/` | | `shared/cache/` | `src/cache/` |
## Deployment Changes ## Deployment Changes

View File

@@ -138,7 +138,7 @@ and serve files through a dedicated PHP endpoint that validates access rights.
### 5. Rate Limiter Vulnerable to IP Spoofing ### 5. Rate Limiter Vulnerable to IP Spoofing
**File:** `lib/RateLimit.php` — method `getClientIdentifier()` **File:** `src/RateLimit.php` — method `getClientIdentifier()`
```php ```php
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
@@ -373,7 +373,7 @@ define('DATABASE_PATH', APP_ROOT . '/database/test.db');
``` ```
This constant is never used anywhere. `Database.php` uses `getDatabasePath()` from This constant is never used anywhere. `Database.php` uses `getDatabasePath()` from
`lib/config.php`. The duplicate creates confusion about which configuration is `src/config.php`. The duplicate creates confusion about which configuration is
authoritative and could lead to future bugs if someone uses the wrong one. authoritative and could lead to future bugs if someone uses the wrong one.
**Fix:** Remove the `DATABASE_PATH` define from `bootstrap.php`. **Fix:** Remove the `DATABASE_PATH` define from `bootstrap.php`.

View File

@@ -111,7 +111,7 @@ include APP_ROOT . '/includes/header.php';
|---------|-------------|----------------------------| |---------|-------------|----------------------------|
| Purpose | Reusable library | Single website | | Purpose | Reusable library | Single website |
| Structure | Complex (PSR-4, namespaces) | Simple (includes, classes) | | Structure | Complex (PSR-4, namespaces) | Simple (includes, classes) |
| Directories | `src/`, `resources/`, `var/`, `bin/` | `public/`, `includes/`, `lib/` | | Directories | `src/`, `resources/`, `var/`, `bin/` | `public/`, `includes/`, `src/` |
| Autoloading | PSR-4 namespaces | Simple require statements | | Autoloading | PSR-4 namespaces | Simple require statements |
| Config | Complex bootstrap with many constants | Minimal bootstrap | | Config | Complex bootstrap with many constants | Minimal bootstrap |
| Caching | `var/cache/` with framework | Simple file-based if needed | | Caching | `var/cache/` with framework | Simple file-based if needed |

View File

@@ -14,7 +14,7 @@
| 1 | No HTTPS — admin credentials exposed in transit | 🔴 CRITICAL | **TLS is terminated upstream by the reverse proxy** (outside nginx). nginx.conf does not need to handle TLS directly. | | 1 | No HTTPS — admin credentials exposed in transit | 🔴 CRITICAL | **TLS is terminated upstream by the reverse proxy** (outside nginx). nginx.conf does not need to handle TLS directly. |
| 3 | Uploaded files stored inside the webroot | 🟠 HIGH | Storage moved to `STORAGE_ROOT` (`/var/www/posterg/storage/`), defined in `config/bootstrap.php`. `formulaire.php` updated. | | 3 | Uploaded files stored inside the webroot | 🟠 HIGH | Storage moved to `STORAGE_ROOT` (`/var/www/posterg/storage/`), defined in `config/bootstrap.php`. `formulaire.php` updated. |
| 4 | File path mismatch — media files broken and insecure | 🟠 HIGH | DB paths are now storage-relative (`theses/YEAR/ID/file`, `covers/file`). New controller `public/media.php` serves files safely. `memoire.php` and `search.php` updated to use `/media.php?path=…`. Cover recording in `formulaire.php` fixed (was never inserted into DB). | | 4 | File path mismatch — media files broken and insecure | 🟠 HIGH | DB paths are now storage-relative (`theses/YEAR/ID/file`, `covers/file`). New controller `public/media.php` serves files safely. `memoire.php` and `search.php` updated to use `/media.php?path=…`. Cover recording in `formulaire.php` fixed (was never inserted into DB). |
| 5 | Rate limiter bypassed by IP spoofing (`X-Forwarded-For`) | 🟠 HIGH | `lib/RateLimit.php` `getClientIdentifier()` now uses `REMOTE_ADDR` only. | | 5 | Rate limiter bypassed by IP spoofing (`X-Forwarded-For`) | 🟠 HIGH | `src/RateLimit.php` `getClientIdentifier()` now uses `REMOTE_ADDR` only. |
| 6 | `.htaccess` security rules silently ignored by nginx | 🟠 HIGH | All rules ported to `nginx/posterg.conf` (CSP to `/admin/` block, `.log` denial, `autoindex off`). See `nginx/HTACCESS_TO_NGINX.md`. | | 6 | `.htaccess` security rules silently ignored by nginx | 🟠 HIGH | All rules ported to `nginx/posterg.conf` (CSP to `/admin/` block, `.log` denial, `autoindex off`). See `nginx/HTACCESS_TO_NGINX.md`. |
| 13 | Deprecated `X-XSS-Protection` header | 🔵 LOW | **Removed** from `nginx/posterg.conf`; see `nginx/SECURITY_HEADERS.md` for rationale. | | 13 | Deprecated `X-XSS-Protection` header | 🔵 LOW | **Removed** from `nginx/posterg.conf`; see `nginx/SECURITY_HEADERS.md` for rationale. |
@@ -49,7 +49,7 @@
| # | Issue | Severity | Resolution | | # | Issue | Severity | Resolution |
|---|-------|----------|------------| |---|-------|----------|------------|
| 2 | No PHP-level authentication in admin panel | 🔴 CRITICAL | `lib/AdminAuth.php` implements a session guard with `password_verify` + `session_regenerate_id`. All admin PHP files now call `AdminAuth::requireLogin()` instead of bare `session_start()`. Credentials stored in gitignored `config/admin_credentials.php` (define `ADMIN_PASSWORD_HASH`). No-op when constant is absent (dev / cli-server). Also resolves item #8 (session cookie hardening via `session_set_cookie_params` before `session_start`). See `nginx/PHP_AUTH_LAYER.md`. | | 2 | No PHP-level authentication in admin panel | 🔴 CRITICAL | `src/AdminAuth.php` implements a session guard with `password_verify` + `session_regenerate_id`. All admin PHP files now call `AdminAuth::requireLogin()` instead of bare `session_start()`. Credentials stored in gitignored `config/admin_credentials.php` (define `ADMIN_PASSWORD_HASH`). No-op when constant is absent (dev / cli-server). Also resolves item #8 (session cookie hardening via `session_set_cookie_params` before `session_start`). See `nginx/PHP_AUTH_LAYER.md`. |
| 8 | Session cookies not hardened (`Secure`, `HttpOnly`, `SameSite` missing) | 🟡 MEDIUM | **Resolved as part of item #2.** `AdminAuth::startSession()` sets `HttpOnly=true`, `SameSite=Strict`, `Secure=true` (off on cli-server), `Path=/admin`, `Lifetime=0` before every `session_start()`. | | 8 | Session cookies not hardened (`Secure`, `HttpOnly`, `SameSite` missing) | 🟡 MEDIUM | **Resolved as part of item #2.** `AdminAuth::startSession()` sets `HttpOnly=true`, `SameSite=Strict`, `Secure=true` (off on cli-server), `Path=/admin`, `Lifetime=0` before every `session_start()`. |
--- ---

View File

@@ -160,7 +160,7 @@ syntax:
@echo "======================" @echo "======================"
@find . -maxdepth 1 -name "*.php" -not -path "./vendor/*" -exec php -l {} \; | grep -v "No syntax errors" @find . -maxdepth 1 -name "*.php" -not -path "./vendor/*" -exec php -l {} \; | grep -v "No syntax errors"
@find admin/ -name "*.php" -exec php -l {} \; 2>/dev/null | grep -v "No syntax errors" || true @find admin/ -name "*.php" -exec php -l {} \; 2>/dev/null | grep -v "No syntax errors" || true
@find lib/ -name "*.php" -exec php -l {} \; | grep -v "No syntax errors" @find src/ -name "*.php" -exec php -l {} \; | grep -v "No syntax errors"
@echo "✅ All PHP files have valid syntax" @echo "✅ All PHP files have valid syntax"
# ============================================================================ # ============================================================================
@@ -315,7 +315,7 @@ clean:
@echo "🧹 Cleaning up development files..." @echo "🧹 Cleaning up development files..."
@rm -f error.log @rm -f error.log
@rm -f admin/error.log @rm -f admin/error.log
@rm -rf lib/cache/rate_limit/* @rm -rf src/cache/rate_limit/*
@rm -f /tmp/posterg-*.log @rm -f /tmp/posterg-*.log
@rm -f /tmp/posterg-*.pid @rm -f /tmp/posterg-*.pid
@echo "✓ Cleanup complete" @echo "✓ Cleanup complete"
@@ -326,7 +326,7 @@ setup-dirs:
@mkdir -p admin/data/theses @mkdir -p admin/data/theses
@mkdir -p admin/data/covers @mkdir -p admin/data/covers
@mkdir -p admin/data/yaml @mkdir -p admin/data/yaml
@mkdir -p lib/cache/rate_limit @mkdir -p src/cache/rate_limit
@touch admin/data/theses/.gitkeep @touch admin/data/theses/.gitkeep
@touch admin/data/covers/.gitkeep @touch admin/data/covers/.gitkeep
@echo "✓ Directories created" @echo "✓ Directories created"

View File

@@ -1 +0,0 @@
[1770319036]

View File

@@ -1 +0,0 @@
[1770377487]

View File

@@ -1,7 +1,7 @@
<?php <?php
// Load configuration // Load configuration
require_once __DIR__ . '/../config/bootstrap.php'; require_once __DIR__ . '/../config/bootstrap.php';
require_once APP_ROOT . '/lib/Database.php'; require_once APP_ROOT . '/src/Database.php';
$pageTitle = "Liste des TFE"; $pageTitle = "Liste des TFE";
$page = isset($_GET["page"]) ? intval($_GET["page"]) : 1; $page = isset($_GET["page"]) ? intval($_GET["page"]) : 1;
@@ -19,7 +19,7 @@ try {
$totalPages = 0; $totalPages = 0;
} }
include APP_ROOT . '/includes/header.php'; include APP_ROOT . '/public/includes/header.php';
?> ?>
<main> <main>
@@ -54,4 +54,4 @@ include APP_ROOT . '/includes/header.php';
</nav> </nav>
<?php endif; ?> <?php endif; ?>
<?php include APP_ROOT . '/includes/footer.php'; ?> <?php include APP_ROOT . '/public/includes/footer.php'; ?>

View File

@@ -18,8 +18,7 @@ $root = dirname(__DIR__);
$watchDirs = [ $watchDirs = [
$root . '/public', $root . '/public',
$root . '/includes', $root . '/src',
$root . '/lib',
$root . '/config', $root . '/config',
]; ];

View File

@@ -3,7 +3,7 @@
require_once __DIR__ . '/../config/bootstrap.php'; require_once __DIR__ . '/../config/bootstrap.php';
// Load required libraries and classes // Load required libraries and classes
require_once APP_ROOT . '/lib/Database.php'; require_once APP_ROOT . '/src/Database.php';
// Check if an id parameter is provided in the URL // Check if an id parameter is provided in the URL
if (isset($_GET['id'])) { if (isset($_GET['id'])) {
@@ -29,7 +29,7 @@ if (isset($_GET['id'])) {
} }
// Include the header template // Include the header template
include APP_ROOT . '/includes/header.php'; ?> include APP_ROOT . '/public/includes/header.php'; ?>
<main> <main>
<div class="item"> <div class="item">
<div class="card-content"> <div class="card-content">
@@ -151,4 +151,4 @@ include APP_ROOT . '/includes/header.php'; ?>
</main> </main>
<!-- Include the footer template --> <!-- Include the footer template -->
<?php include APP_ROOT . '/includes/footer.php'; ?> <?php include APP_ROOT . '/public/includes/footer.php'; ?>

View File

@@ -1,8 +1,8 @@
<?php <?php
// Bootstrap application // Bootstrap application
require_once __DIR__ . '/../config/bootstrap.php'; require_once __DIR__ . '/../config/bootstrap.php';
require_once APP_ROOT . '/lib/Database.php'; require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/lib/RateLimit.php'; require_once APP_ROOT . '/src/RateLimit.php';
// Rate limiting: 30 requests per minute // Rate limiting: 30 requests per minute
@@ -16,7 +16,7 @@ if (!$rateLimit->check()) {
$rateLimit->sendHeaders(); $rateLimit->sendHeaders();
// Display error page // Display error page
include APP_ROOT . '/includes/header.php'; include APP_ROOT . '/public/includes/header.php';
echo '<section class="section">'; echo '<section class="section">';
echo ' <div class="container">'; echo ' <div class="container">';
echo ' <div class="notification is-danger">'; echo ' <div class="notification is-danger">';
@@ -26,7 +26,7 @@ if (!$rateLimit->check()) {
echo ' </div>'; echo ' </div>';
echo ' </div>'; echo ' </div>';
echo '</section>'; echo '</section>';
include APP_ROOT . '/includes/footer.php'; include APP_ROOT . '/public/includes/footer.php';
exit; exit;
} }
@@ -122,7 +122,7 @@ try {
$languages = []; $languages = [];
} }
include APP_ROOT . '/includes/header.php'; ?> include APP_ROOT . '/public/includes/header.php'; ?>
<section class="section"> <section class="section">
<div class="container"> <div class="container">
@@ -415,4 +415,4 @@ include APP_ROOT . '/includes/header.php'; ?>
</div> </div>
</section> </section>
<?php include APP_ROOT . '/includes/footer.php'; ?> <?php include APP_ROOT . '/public/includes/footer.php'; ?>

View File

@@ -0,0 +1 @@
[1770894664]

View File

@@ -4,7 +4,7 @@
* Tests search queries and results * Tests search queries and results
*/ */
require_once __DIR__ . '/../../lib/Database.php'; require_once __DIR__ . '/../../src/Database.php';
echo "Search Functionality Test\n"; echo "Search Functionality Test\n";
echo "=========================\n\n"; echo "=========================\n\n";

View File

@@ -4,7 +4,7 @@
* Tests SQL injection protection and input sanitization * Tests SQL injection protection and input sanitization
*/ */
require_once __DIR__ . '/../../lib/Database.php'; require_once __DIR__ . '/../../src/Database.php';
echo "Security Test Suite\n"; echo "Security Test Suite\n";
echo "===================\n\n"; echo "===================\n\n";

View File

@@ -4,7 +4,7 @@
* Tests basic database connectivity and query functionality * Tests basic database connectivity and query functionality
*/ */
require_once __DIR__ . '/../../lib/Database.php'; require_once __DIR__ . '/../../src/Database.php';
echo "Database Connection Test\n"; echo "Database Connection Test\n";
echo "========================\n\n"; echo "========================\n\n";

View File

@@ -4,7 +4,7 @@
* Tests rate limiting functionality * Tests rate limiting functionality
*/ */
require_once __DIR__ . '/../../lib/RateLimit.php'; require_once __DIR__ . '/../../src/RateLimit.php';
echo "Rate Limit Test\n"; echo "Rate Limit Test\n";
echo "===============\n\n"; echo "===============\n\n";