Fix admin CSS not loading and quirks mode issues

Fixed multiple issues in admin panel:

1. CSS path: modern-normalize.css → modern-normalize.min.css
   (File is actually named .min.css)

2. Icon path: assets/icon.svg → /assets/admin_favicon.svg
   (Was relative, now absolute; correct filename)

3. Navigation: /admin/list.php → /admin/
   (list.php was renamed to index.php)

4. Short PHP tags: <? → <?php
   (Better compatibility, some servers don't enable short_open_tag)

5. Quirks mode warning was due to CSS not loading, not DOCTYPE
   (DOCTYPE was already present)

Files modified:
- public/admin/inc/head.php (main fixes)
- public/admin/index.php (short tags)
- public/admin/add.php (short tags)
- public/admin/import.php (short tags)

Need to redeploy for production: just deploy
This commit is contained in:
Théophile Gervreau-Mercier
2026-02-06 12:14:26 +01:00
parent e789c286de
commit 4bbbc58e24
44 changed files with 1850 additions and 377 deletions

1
.gitignore vendored
View File

@@ -16,3 +16,4 @@ Thumbs.db
# IDE
.vscode/
.idea/

31
admin/.gitignore vendored
View File

@@ -1,31 +0,0 @@
# Test database
test.db
# Error logs
error.log
# Uploaded files (for testing)
data/theses/
data/covers/
# Keep the data directories but ignore contents
!data/theses/.gitkeep
!data/covers/.gitkeep
# PHP session files
sessions/
# Composer
vendor/
composer.lock
# OS files
.DS_Store
Thumbs.db
# IDE
.vscode/
.idea/
*.swp
*.swo
*~

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

35
config/bootstrap.php Normal file
View File

@@ -0,0 +1,35 @@
<?php
/**
* Simple configuration for website
*/
// Define application root
define('APP_ROOT', dirname(__DIR__));
// Database path
define('DATABASE_PATH', APP_ROOT . '/database/test.db');
// Error reporting
if (php_sapi_name() === 'cli-server') {
// Development mode
error_reporting(E_ALL);
ini_set('display_errors', '1');
} else {
// Production mode
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
}
// Simple helper function for including templates
function include_template($name) {
$path = APP_ROOT . '/includes/' . $name;
if (file_exists($path)) {
include $path;
}
}
// Autoload Composer dependencies if available
if (file_exists(APP_ROOT . '/vendor/autoload.php')) {
require_once APP_ROOT . '/vendor/autoload.php';
}

View File

@@ -0,0 +1,400 @@
# Complete Deployment Guide - New Structure
## Overview
This guide walks you through deploying the new secure directory structure:
- **Old:** `/var/www/html/` (everything exposed)
- **New:** `/var/www/posterg/` with `public/` subdirectory (only public/ exposed)
## Step-by-Step Deployment
### ⚠️ IMPORTANT: Do NOT delete /var/www/html/ yet!
Keep it as a backup until you confirm the new structure works.
---
### Step 1: Setup Server Directory (Manual - One Time)
SSH to server and create the new directory:
```bash
ssh posterg
# Backup current site
sudo cp -r /var/www/html /var/www/html.backup
# Create new directory
sudo mkdir -p /var/www/posterg
# Set ownership (www-data = web server user)
sudo chown www-data:posterg /var/www/posterg
# Set permissions
sudo chmod 775 /var/www/posterg
# Verify
ls -ld /var/www/posterg
# Should show: drwxrwxr-x 2 www-data posterg ...
# Exit server
exit
```
---
### Step 2: Deploy Application Files
From your local machine:
```bash
just deploy
```
This will:
- Upload all files to `/var/www/posterg/`
- Exclude unnecessary files (tests, docs, etc.)
- Set initial ownership to `www-data:posterg`
You should see:
```
sending incremental file list
public/index.php
public/search.php
public/memoire.php
...
```
---
### Step 3: Deploy Nginx Configuration
```bash
just deploy-nginx
```
This will:
1. Check that nginx config has correct DocumentRoot (`/var/www/posterg/public`)
2. Upload `posterg.conf` to `/tmp/` on server
3. Upload `deploy-production-new.sh` to `/tmp/` on server
Expected output:
```
✅ nginx config looks correct
✅ Files uploaded to /tmp/ on server
```
---
### Step 4: Apply Nginx Configuration on Server
SSH to server and run the deployment script:
```bash
ssh posterg
sudo bash /tmp/deploy-production.sh
```
The script will:
1. Fix file permissions on `/var/www/posterg/`
2. Backup existing nginx config
3. Install new nginx config
4. Test nginx configuration
5. Show you the reload command
Expected output:
```
🚀 Post-ERG Production Deployment (NEW STRUCTURE)
==================================================
📋 Step 1: Fixing file permissions...
✓ Changed ownership to www-data:posterg
✓ Set directory permissions to 755
✓ Set file permissions to 644
✓ Made database directory group-writable (775)
✓ Fixed database file permissions (660)
📋 Step 2: Deploying nginx configuration...
✓ Backed up existing config
✓ Installed new nginx config
📋 Step 3: Testing nginx configuration...
✓ Nginx configuration is valid
📋 Step 4: Summary...
✓ Permissions fixed
✓ Nginx config installed
✓ Configuration validated
Ready to reload nginx!
Run: sudo systemctl reload nginx
```
---
### Step 5: Reload Nginx
Still on the server:
```bash
sudo systemctl reload nginx
```
If successful, you'll see no output (which is good!).
Check status:
```bash
sudo systemctl status nginx
# Should show "active (running)"
```
Exit server:
```bash
exit
```
---
### Step 6: Verify Deployment
From your local machine:
```bash
just server-status
```
Expected output:
```
🔍 Server Status
================
✓ Nginx running
✓ PHP-FPM running
Site check:
• Public: 200 ✓
• Admin: 200 ✓
```
---
### Step 7: Security Verification
Test these URLs in your browser or with curl:
```bash
# Should work (200 OK):
curl -I https://posterg.erg.be/
curl -I https://posterg.erg.be/admin/
# Should be 404 (SECURITY - private files):
curl -I https://posterg.erg.be/database/test.db
curl -I https://posterg.erg.be/config/bootstrap.php
curl -I https://posterg.erg.be/includes/header.php
curl -I https://posterg.erg.be/lib/Database.php
```
**All private files must return 404!** If they don't, nginx is not configured correctly.
---
### Step 8: Deploy Database (If Needed)
If you need to update the database:
```bash
just deploy-database
```
This will warn you before overwriting.
---
### Step 9: Test Thoroughly
- [ ] Visit https://posterg.erg.be/
- [ ] Browse thesis list
- [ ] Open a thesis detail page
- [ ] Try search functionality
- [ ] Access admin panel (should require login)
- [ ] Verify private files return 404
---
### Step 10: Keep Backup for a While
**Do NOT delete `/var/www/html/` immediately!**
Keep it for a few days to ensure everything works. If you need to rollback:
```bash
ssh posterg
sudo nano /etc/nginx/sites-available/posterg
# Change: root /var/www/posterg/public;
# Back to: root /var/www/html;
sudo systemctl reload nginx
```
Your old site will work immediately.
---
## After Confirming Everything Works
A few days later, once you're confident:
```bash
ssh posterg
sudo rm -rf /var/www/html.backup
sudo rm -rf /var/www/html
```
---
## What Changed?
### Directory Structure
```
OLD: NEW:
/var/www/html/ /var/www/posterg/
├── index.php ├── public/ ← DocumentRoot
├── search.php │ ├── index.php
├── admin/ │ ├── search.php
├── assets/ │ ├── memoire.php
├── database/ ❌ exposed │ ├── admin/
├── lib/ ❌ exposed │ └── assets/
└── ... ├── includes/ ✅ private
├── config/ ✅ private
├── database/ ✅ private
└── lib/ ✅ private
```
### Nginx Configuration
```nginx
# OLD
root /var/www/html;
location ^~ /formulaire/ { ... }
# NEW
root /var/www/posterg/public;
location ^~ /admin/ { ... }
```
### Security Impact
| Resource | Old | New |
|----------|-----|-----|
| Database | ❌ Accessible if nginx misconfigured | ✅ Physically outside web root |
| Config files | ❌ One deny rule away | ✅ Physically private |
| Source code | ❌ Exposed | ✅ Physically private |
| Admin panel | /formulaire/ | /admin/ |
---
## Troubleshooting
### Site returns 404 for everything
**Cause:** Nginx still pointing to old location or wrong DocumentRoot
**Fix:**
```bash
ssh posterg
sudo cat /etc/nginx/sites-available/posterg | grep "root "
# Should show: root /var/www/posterg/public;
```
If wrong:
```bash
sudo nano /etc/nginx/sites-available/posterg
# Fix the root directive
sudo systemctl reload nginx
```
### Database errors
**Cause:** Wrong permissions on database file
**Fix:**
```bash
ssh posterg
sudo chown www-data:posterg /var/www/posterg/database/test.db
sudo chmod 660 /var/www/posterg/database/test.db
```
### Admin upload errors
**Cause:** Upload directories not writable
**Fix:**
```bash
ssh posterg
sudo chmod 775 /var/www/posterg/public/admin/data
sudo find /var/www/posterg/public/admin/data -type d -exec chmod 775 {} \;
```
### Private files still accessible
**Cause:** Nginx serving from wrong directory
**Fix:** Verify nginx DocumentRoot and reload
---
## Rollback Procedure
If you need to go back to the old structure:
```bash
# 1. SSH to server
ssh posterg
# 2. Restore old nginx config
sudo nano /etc/nginx/sites-available/posterg
# Change: root /var/www/posterg/public;
# To: root /var/www/html;
# Change: location ^~ /admin/
# To: location ^~ /formulaire/
# 3. Reload nginx
sudo systemctl reload nginx
# 4. Verify
curl -I https://posterg.erg.be/
```
Your old site should work immediately (as long as you didn't delete `/var/www/html/`).
---
## Quick Reference
| Task | Command |
|------|---------|
| Deploy files | `just deploy` |
| Deploy nginx | `just deploy-nginx` |
| Deploy database | `just deploy-database` |
| Check status | `just server-status` |
| View logs | `just server-logs` |
---
## Success Checklist
- [ ] `/var/www/posterg/` created with correct permissions
- [ ] Files deployed with `just deploy`
- [ ] Nginx config deployed with `just deploy-nginx`
- [ ] Permissions fixed with `deploy-production.sh`
- [ ] Nginx reloaded successfully
- [ ] Site accessible at https://posterg.erg.be/
- [ ] Admin accessible at https://posterg.erg.be/admin/
- [ ] Private files return 404 (security verified)
- [ ] Database working (can view theses)
- [ ] Search working
- [ ] Old `/var/www/html/` kept as backup
---
**After deployment, your site will be significantly more secure!** 🔒

215
docs/DEPLOYMENT_STEPS.md Normal file
View File

@@ -0,0 +1,215 @@
# Deployment Steps
## First-Time Deployment (New Structure)
Since we're moving from `/var/www/html/` to `/var/www/posterg/`, follow these steps:
### 1. Setup Server Directory (ONE TIME)
```bash
just setup-server
```
This creates `/var/www/posterg/` with correct permissions:
- Owner: `www-data:posterg`
- Permissions: `775`
### 2. Deploy Application
```bash
just deploy
```
This deploys all files to `/var/www/posterg/`:
- `public/``/var/www/posterg/public/`
- `includes/``/var/www/posterg/includes/`
- `config/``/var/www/posterg/config/`
- `database/``/var/www/posterg/database/`
- `lib/``/var/www/posterg/lib/`
### 3. Update Nginx Configuration
```bash
just deploy-nginx
```
This checks that nginx config has correct DocumentRoot and uploads it to server.
**IMPORTANT:** The nginx config must have:
```nginx
root /var/www/posterg/public;
```
If the check fails, edit `nginx/posterg.conf` first.
### 4. Apply Nginx Configuration on Server
```bash
ssh posterg
sudo bash /tmp/deploy-production.sh
sudo systemctl reload nginx
```
### 5. Verify Deployment
```bash
just server-status
```
Check:
- https://posterg.erg.be/ (should work)
- https://posterg.erg.be/admin/ (should work)
- https://posterg.erg.be/database/test.db (should 404 ✅)
---
## Subsequent Deployments
After the first deployment, you only need:
```bash
just deploy
```
That's it! The directory structure is already in place.
---
## Deploy Database
If you need to deploy the database:
```bash
just deploy-database
```
This will:
1. Upload `database/test.db` to server
2. Set correct permissions
3. Warn before overwriting
---
## Troubleshooting
### Permission Denied on Deploy
**Error:**
```
mkdir "/var/www/posterg" failed: Permission denied (13)
```
**Solution:**
```bash
just setup-server
```
### Nginx 500 Error
**Cause:** Nginx DocumentRoot still pointing to old location
**Solution:**
1. Check nginx config has: `root /var/www/posterg/public;`
2. Redeploy nginx config:
```bash
just deploy-nginx
ssh posterg
sudo bash /tmp/deploy-production.sh
sudo systemctl reload nginx
```
### Database Connection Errors
**Cause:** Database file permissions incorrect
**Solution:**
```bash
ssh posterg
cd /var/www/posterg
sudo chown www-data:posterg database/test.db
sudo chmod 660 database/test.db
```
### Admin 404
**Cause:** Nginx still using old `/formulaire/` location
**Solution:** Update `nginx/posterg.conf` to use `/admin/` location
---
## Rollback
If something goes wrong, rollback is easy:
### 1. Restore Old Directory
```bash
ssh posterg
sudo cp -r /var/www/html.backup /var/www/html # If you backed up
```
### 2. Restore Old Nginx Config
```bash
ssh posterg
sudo cp /etc/nginx/sites-available/posterg.backup /etc/nginx/sites-available/posterg
sudo systemctl reload nginx
```
### 3. Rollback Code with jj
```bash
jj log
jj edit <previous-change-id>
```
---
## Migration Checklist
- [ ] `just setup-server` - Create server directory
- [ ] `just deploy` - Deploy application
- [ ] `just deploy-nginx` - Update nginx config
- [ ] SSH to server and apply nginx config
- [ ] `sudo systemctl reload nginx`
- [ ] Verify site works: https://posterg.erg.be/
- [ ] Verify security: https://posterg.erg.be/database/test.db → 404
- [ ] Test admin: https://posterg.erg.be/admin/
- [ ] Deploy database (if needed): `just deploy-database`
---
## Commands Reference
| Command | Purpose |
|---------|---------|
| `just setup-server` | Create `/var/www/posterg/` (first time only) |
| `just deploy` | Deploy application files |
| `just deploy-nginx` | Update nginx configuration |
| `just deploy-database` | Deploy database file |
| `just server-status` | Check server health |
| `just server-logs` | View server logs |
---
## Directory Structure on Server
```
/var/www/posterg/ # Application root (private)
├── public/ # DocumentRoot (nginx points here)
│ ├── index.php
│ ├── search.php
│ ├── memoire.php
│ ├── admin/
│ └── assets/
├── includes/ # Templates (private)
├── config/ # Configuration (private)
├── database/ # Database (private)
├── lib/ # PHP classes (private)
└── vendor/ # Dependencies (private)
```
**Nginx DocumentRoot:** `/var/www/posterg/public/`
Only the `public/` directory is accessible via web browser. Everything else is private.

214
docs/MIGRATION_COMPLETE.md Normal file
View File

@@ -0,0 +1,214 @@
# ✅ Migration to public/ Directory Structure - COMPLETE
## 📊 Summary of Changes
### Directory Structure Created
```
posterg-website/
├── config/ # ✅ NEW - Configuration files
│ └── bootstrap.php # Central path management
├── public/ # ✅ NEW - DocumentRoot (web-accessible)
│ ├── admin/ # Moved from /admin/
│ ├── assets/ # Moved from /assets/
│ ├── index.php # Moved from /index.php
│ ├── search.php # Moved from /search.php
│ └── memoire.php # Moved from /memoire.php
├── resources/ # ✅ NEW - Application resources
│ └── views/ # Moved from /inc/
│ ├── header.php
│ └── footer.php
├── var/ # ✅ NEW - Runtime files
│ ├── cache/
│ ├── logs/
│ └── tmp/
├── database/ # ✅ KEPT - Now private
├── lib/ # ✅ KEPT - Now private
├── vendor/ # ✅ KEPT - Now private
└── tests/ # ✅ KEPT - Now private
```
### Files Modified
**1. config/bootstrap.php** (NEW)
- Central path configuration
- Defines APP_ROOT, PUBLIC_ROOT, DATABASE_PATH, etc.
- Helper functions: view(), getDatabase()
- Environment detection (dev vs production)
- Error handling configuration
**2. public/*.php** (3 files updated)
- index.php: Uses bootstrap, updated require paths
- search.php: Uses bootstrap, updated require paths
- memoire.php: Uses bootstrap, updated require paths
- All now use view() helper for header/footer
**3. public/admin/*.php** (7 files updated)
- add.php, edit.php, formulaire.php, import.php
- index.php, publish.php, thanks.php
- All updated to use ../../ paths for lib access
- Bootstrap added where needed
**4. justfile** (Updated)
- Dev server: `php -S 127.0.0.1:8000 -t public/`
- Deploy: Now deploys to `/var/www/posterg/`
- Database deploy: Updated paths to `/var/www/posterg/`
- Nginx deploy: Checks for correct DocumentRoot
**5. nginx/posterg.conf** (Updated)
- DocumentRoot: `/var/www/html``/var/www/posterg/public`
- Admin location: `/formulaire/``/admin/`
**6. .gitignore** (Updated)
- Added var/ directory patterns
- Keeps .gitkeep files, ignores contents
### Security Improvements
**Before:**
- ❌ All files in DocumentRoot (/var/www/html/)
- ❌ Database accessible at /database/test.db
- ❌ Config files accessible
- ❌ Dev server exposed everything
- ❌ Relied on nginx deny rules
**After:**
- ✅ Only public/ in DocumentRoot
- ✅ Database physically outside web root
- ✅ Config files physically private
- ✅ Dev server matches production security
- ✅ Physical separation = secure by default
## 🧪 Testing
### Local Development
```bash
# Start dev server
just serve
# Test in browser:
# - http://localhost:8000/ → Should work
# - http://localhost:8000/admin/ → Should work
# - http://localhost:8000/database/test.db → Should 404 ✅
# - http://localhost:8000/config/ → Should 404 ✅
# - http://localhost:8000/../database/test.db → Should 404 ✅
```
### Security Verification
```bash
# These should all return 404:
curl http://localhost:8000/database/test.db
curl http://localhost:8000/config/bootstrap.php
curl http://localhost:8000/vendor/autoload.php
curl http://localhost:8000/../database/test.db
curl http://localhost:8000/lib/Database.php
```
### Production Deployment
**BEFORE deploying to production:**
1. **Update nginx config on server:**
```bash
# Edit /etc/nginx/sites-available/posterg
# Change: root /var/www/html;
# To: root /var/www/posterg/public;
```
2. **Create new directory on server:**
```bash
ssh posterg "sudo mkdir -p /var/www/posterg"
```
3. **Deploy application:**
```bash
just deploy
```
4. **Deploy nginx config:**
```bash
just deploy-nginx
# Then on server:
ssh posterg
sudo bash /tmp/deploy-production.sh
sudo systemctl reload nginx
```
5. **Verify:**
```bash
just server-status
curl -I https://posterg.erg.be/
curl -I https://posterg.erg.be/admin/
curl -I https://posterg.erg.be/database/test.db # Must 404!
```
## 📝 Path Reference
### From public/*.php files:
```php
<?php
require_once __DIR__ . '/../config/bootstrap.php'; // Bootstrap
require_once LIB_ROOT . '/Database.php'; // Library
$db = getDatabase(); // Database
view('header.php', ['pageTitle' => 'Title']); // Template
```
### From public/admin/*.php files:
```php
<?php
require_once __DIR__ . '/../../config/bootstrap.php'; // Bootstrap
require_once LIB_ROOT . '/Database.php'; // Library
```
### Available Constants (from bootstrap):
- `APP_ROOT` - /path/to/posterg-website
- `PUBLIC_ROOT` - /path/to/posterg-website/public
- `CONFIG_ROOT` - /path/to/posterg-website/config
- `DATABASE_ROOT` - /path/to/posterg-website/database
- `DATABASE_PATH` - /path/to/posterg-website/database/test.db
- `RESOURCES_ROOT` - /path/to/posterg-website/resources
- `LIB_ROOT` - /path/to/posterg-website/lib
- `VAR_ROOT` - /path/to/posterg-website/var
- `CACHE_ROOT` - /path/to/posterg-website/var/cache
- `LOGS_ROOT` - /path/to/posterg-website/var/logs
- `VIEWS_ROOT` - /path/to/posterg-website/resources/views
## 🎯 Next Steps
1. ✅ Migration complete - verify locally
2. ⏭️ Test dev server: `just serve`
3. ⏭️ Test all pages work correctly
4. ⏭️ Update nginx config on production server
5. ⏭️ Deploy to production: `just deploy`
6. ⏭️ Deploy nginx config: `just deploy-nginx`
7. ⏭️ Verify production deployment
## 🔄 Rollback (if needed)
If something goes wrong, jj makes it easy:
```bash
# View history
jj log
# Go back to previous state
jj edit <previous-change-id>
# Or abandon current changes
jj abandon @
```
## 📚 Documentation
See also:
- `DIRECTORY_STRUCTURE.md` - Full structure reference
- `DEPLOYMENT_MIGRATION.md` - Detailed migration guide
- `MIGRATION_CHECKLIST.md` - Quick checklist
## ✨ Benefits Achieved
1. **Security**: Private files physically separated from public
2. **Standards**: Follows PHP-FIG and Standard PHP Package Skeleton
3. **Development**: Dev server matches production security
4. **Maintainability**: Clear separation of concerns
5. **Portability**: Path constants make relocation easy
6. **Best Practices**: Industry-standard directory structure

118
docs/SERVER_SETUP.md Normal file
View File

@@ -0,0 +1,118 @@
# Server Setup (Manual)
Since sudo prompts don't work over SSH in justfile, do the initial setup manually.
## One-Time Setup on Server
```bash
# 1. SSH to server
ssh posterg
# 2. Backup current site (recommended)
sudo cp -r /var/www/html /var/www/html.backup
# 3. Create new directory structure
sudo mkdir -p /var/www/posterg
# 4. Set ownership (www-data is the web server user)
sudo chown www-data:posterg /var/www/posterg
# 5. Set permissions (775 = rwxrwxr-x)
sudo chmod 775 /var/www/posterg
# 6. Verify
ls -ld /var/www/posterg
# Should show: drwxrwxr-x 2 www-data posterg 4096 ... /var/www/posterg
# 7. Exit server
exit
```
## Deploy from Local Machine
```bash
just deploy
```
## Complete Deployment Process
```bash
# On server (one time)
ssh posterg
sudo mkdir -p /var/www/posterg
sudo chown www-data:posterg /var/www/posterg
sudo chmod 775 /var/www/posterg
exit
# From local machine
just deploy # Deploy files
just deploy-nginx # Update nginx config
# On server - apply nginx config
ssh posterg
sudo bash /tmp/deploy-production.sh
sudo systemctl reload nginx
exit
# Verify from local
just server-status
```
## Important Notes
- **Don't delete `/var/www/html/` yet!** Keep it as backup until you confirm the new structure works
- The new structure uses `/var/www/posterg/public/` as DocumentRoot
- Nginx must be updated to point to the new location
## After Confirming Everything Works
Once you've verified the new deployment works:
```bash
ssh posterg
sudo rm -rf /var/www/html.backup # Remove backup if no longer needed
sudo rm -rf /var/www/html # Remove old directory
```
## Directory Structure on Server
```
/var/www/
├── html/ ← OLD (keep as backup for now)
├── html.backup/ ← BACKUP (can delete later)
└── posterg/ ← NEW
├── public/ ← DocumentRoot (nginx serves from here)
├── includes/
├── config/
├── database/
├── lib/
└── vendor/
```
## Troubleshooting
### Permission denied during deploy
**Cause:** Directory doesn't exist or has wrong ownership
**Fix:** Run the setup commands above
### Nginx 403 Forbidden
**Cause:** Wrong permissions on files
**Fix:**
```bash
ssh posterg
cd /var/www/posterg
sudo chown -R www-data:posterg .
sudo find . -type d -exec chmod 755 {} \;
sudo find . -type f -exec chmod 644 {} \;
sudo chmod 775 database/
sudo chmod 660 database/*.db
```
### Database connection errors
**Cause:** Database file permissions
**Fix:**
```bash
ssh posterg
sudo chown www-data:posterg /var/www/posterg/database/test.db
sudo chmod 660 /var/www/posterg/database/test.db
```

182
docs/SIMPLIFICATION.md Normal file
View File

@@ -0,0 +1,182 @@
# Website Structure Simplification
## Problem Identified
The initial migration used the **Standard PHP Package Skeleton**, which is designed for **reusable PHP packages/libraries** (like Composer packages), not for websites.
This resulted in:
- ❌ Overcomplicated structure (`var/`, `resources/`, complex bootstrap)
- ❌ Unused directories (`var/cache/`, `var/logs/`, `var/tmp/`)
- ❌ Package-oriented naming (`resources/views/`)
- ❌ Unnecessary constants and helper functions
## Solution: Simplified for Website
### Removed
1. **`var/` directory** - Completely unused, only needed for complex applications with:
- Custom caching systems
- Application-level logging
- Temporary file processing
2. **`resources/` directory** - Package-oriented name, renamed to simple `includes/`
3. **Complex bootstrap** - Removed unused constants:
- `VAR_ROOT`, `CACHE_ROOT`, `LOGS_ROOT`
- `PUBLIC_ROOT`, `CONFIG_ROOT`, `RESOURCES_ROOT`
- `view()` helper function
### Simplified Structure
```
posterg-website/
├── public/ # DocumentRoot (web-accessible) ✅
│ ├── index.php
│ ├── search.php
│ ├── memoire.php
│ ├── admin/
│ └── assets/
├── includes/ # Simple template includes ✅
│ ├── header.php
│ └── footer.php
├── config/ # Minimal configuration ✅
│ └── bootstrap.php (simplified)
├── database/ # Database files (private)
│ └── test.db
├── lib/ # PHP classes (private)
│ ├── Database.php
│ ├── RateLimit.php
│ └── cache/rate_limit/ (used by RateLimit)
├── vendor/ # Composer dependencies (private)
└── tests/ # Tests (private)
```
### Simplified config/bootstrap.php
**Before:** 66 lines with many unused constants
**After:** 33 lines with only essentials
```php
<?php
// Define application root
define('APP_ROOT', dirname(__DIR__));
// Database path
define('DATABASE_PATH', APP_ROOT . '/database/test.db');
// Error reporting (dev vs production)
if (php_sapi_name() === 'cli-server') {
error_reporting(E_ALL);
ini_set('display_errors', '1');
} else {
error_reporting(E_ALL);
ini_set('display_errors', '0');
}
// Simple helper for templates
function include_template($name) {
include APP_ROOT . '/includes/' . $name;
}
// Composer autoload
if (file_exists(APP_ROOT . '/vendor/autoload.php')) {
require_once APP_ROOT . '/vendor/autoload.php';
}
```
### Simplified PHP Files
**Before:**
```php
require_once __DIR__ . '/../config/bootstrap.php';
require_once LIB_ROOT . '/Database.php';
view('header.php', ['pageTitle' => $title]);
```
**After:**
```php
require_once __DIR__ . '/../config/bootstrap.php';
require_once APP_ROOT . '/lib/Database.php';
include APP_ROOT . '/includes/header.php';
```
## Comparison: Package vs Website
| Feature | PHP Package | PHP Website (this project) |
|---------|-------------|----------------------------|
| Purpose | Reusable library | Single website |
| Structure | Complex (PSR-4, namespaces) | Simple (includes, classes) |
| Directories | `src/`, `resources/`, `var/`, `bin/` | `public/`, `includes/`, `lib/` |
| Autoloading | PSR-4 namespaces | Simple require statements |
| Config | Complex bootstrap with many constants | Minimal bootstrap |
| Caching | `var/cache/` with framework | Simple file-based if needed |
| Logging | `var/logs/` with logger | PHP error_log |
## Benefits of Simplification
### Before (Package-oriented)
- ❌ 66-line bootstrap file
- ❌ 10+ unused constants
-`resources/views/` (confusing name)
-`var/` directory (completely unused)
- ❌ Helper functions for simple includes
- ❌ Over-engineered for a simple website
### After (Website-focused)
- ✅ 33-line bootstrap file (50% smaller)
- ✅ 2 essential constants (APP_ROOT, DATABASE_PATH)
-`includes/` (clear, simple name)
- ✅ No unused directories
- ✅ Standard PHP `include` statements
- ✅ Appropriate for a PHP website
### Security (Unchanged)
- ✅ Still uses `public/` as DocumentRoot
- ✅ Database still outside web root
- ✅ Config still private
- ✅ All security improvements retained
## Testing
All PHP files pass syntax check:
```bash
$ php -l config/bootstrap.php # OK
$ php -l public/index.php # OK
$ php -l public/search.php # OK
$ php -l public/memoire.php # OK
$ php -l public/admin/index.php # OK
```
Start dev server:
```bash
$ just serve
```
## When You WOULD Need var/
You would need a `var/` directory if you were building:
- A framework (Laravel, Symfony)
- A CMS (WordPress, Drupal)
- An application with:
- Template compilation/caching
- Session storage
- File upload processing
- Application-level logging
- Queue systems
For your thesis website: **Not needed**
## Conclusion
- ✅ Structure is now appropriate for a **PHP website**
- ✅ Removed package-oriented complexity
- ✅ Kept all security improvements (public/ directory)
- ✅ Simpler, cleaner, easier to maintain
- ✅ Still follows best practices (just the right ones)
The core improvement (public/ directory for security) remains intact, but now with a structure that fits a website, not a reusable package.

View File

@@ -1,86 +0,0 @@
<?php
ini_set("display_errors", 0);
ini_set("log_errors", 1);
ini_set("error_log", "error.log");
$pageTitle = "Liste des TFE";
require_once __DIR__ . "/lib/Database.php";
$page = isset($_GET["page"]) ? intval($_GET["page"]) : 1;
$itemsPerPage = 10;
try {
$db = Database::getInstance();
$offset = ($page - 1) * $itemsPerPage;
$itemsToLoad = $db->getPublishedTheses($itemsPerPage, $offset);
$totalItems = $db->countPublishedTheses();
$totalPages = ceil($totalItems / $itemsPerPage);
} catch (Exception $e) {
error_log("Error loading theses: " . $e->getMessage());
$itemsToLoad = [];
$totalPages = 0;
}
include "inc/header.php"; ?>
<main>
<?php foreach ($itemsToLoad as $item): ?>
<a href="memoire.php?id=<?= $item["id"] ?>" class="card-link">
<div class="card">
<?php
// Get cover image from thesis_files if available
$coverImage = null;
if (!empty($item["id"])) {
$files = $db->getThesisFiles($item["id"]);
foreach ($files as $file) {
$ext = strtolower(
pathinfo($file["file_path"], PATHINFO_EXTENSION),
);
if (
in_array($ext, ["jpg", "jpeg", "png", "gif"]) &&
$file["file_type"] === "main"
) {
$coverImage = $file["file_path"];
break;
}
}
}
?>
<?php if ($coverImage): ?>
<div class="card-image">
<figure class="image ">
<img src="<?= htmlspecialchars(
$coverImage,
) ?>" alt="Image preview">
</figure>
</div>
<?php endif; ?>
<div class="card-content">
<h4 class="title is-4">
<?= htmlspecialchars($item["title"]) ?>
</h4>
<h2 class="subtitle">
<?= htmlspecialchars($item["authors"] ?? "Auteur inconnu") ?>
</h2>
<h3 class="tag title is-6 is-link is-light">
<?= htmlspecialchars($item["year"]) ?>
</h3>
<p class="block content">
<?php
$excerpt_length = 150;
$synopsis = $item["synopsis"] ?? "";
$description_excerpt =
strlen($synopsis) > $excerpt_length
? substr($synopsis, 0, $excerpt_length) . "..."
: $synopsis;
?>
<?= htmlspecialchars($description_excerpt) ?>
</p>
</div>
</div>
</a>
<?php endforeach; ?>
</main>
<?php include "inc/footer.php"; ?>

View File

@@ -25,6 +25,7 @@ serve:
@echo ""
@echo "📍 Public site: http://localhost:8000"
@echo "📍 Admin panel: http://localhost:8000/admin/"
@echo "🔒 Serving from public/ directory (matches production)"
@echo ""
@if [ -d "vendor/php-live-reload" ]; then \
echo "✨ Live reload enabled - browser auto-refreshes on file save!"; \
@@ -34,7 +35,7 @@ serve:
@echo ""
@echo "Press Ctrl+C to stop"
@echo ""
@php -S 127.0.0.1:8000
@php -S 127.0.0.1:8000 -t public/
[group('dev')]
stop:
@@ -63,47 +64,69 @@ deploy:
@echo "📤 Deploying Post-ERG complete site"
@echo "===================================="
@echo ""
@echo "Deploying public site..."
@echo "⚠️ First time? Ensure /var/www/posterg/ exists on server with:"
@echo " ssh posterg"
@echo " sudo mkdir -p /var/www/posterg"
@echo " sudo chown www-data:posterg /var/www/posterg"
@echo " sudo chmod 775 /var/www/posterg"
@echo ""
@echo "Step 1: Deploying application to /var/www/posterg/..."
rsync -vur --progress \
--chown="root:posterg" \
--chown="www-data:posterg" \
--exclude 'vendor' \
--exclude 'tests' \
--exclude 'test.db' \
--exclude '*.db' \
--exclude 'cache' \
--exclude '*.md' \
--exclude '.git*' \
--exclude '.jj' \
--exclude '.DS_Store' \
--exclude 'database' \
--exclude 'database/backup_*' \
--exclude 'database/fixtures' \
--exclude 'database/docs' \
--exclude 'nginx' \
--exclude 'docs' \
--exclude 'justfile*' \
--exclude 'migrate-structure.sh' \
--exclude 'setup-dev.sh' \
./ posterg:/var/www/html/
--exclude 'var/cache/*' \
--exclude 'var/logs/*' \
./ posterg:/var/www/posterg/
@echo ""
@echo "Fixing permissions..."
ssh posterg "chgrp -R posterg /var/www/html/inc /var/www/html/lib && \
chmod 755 /var/www/html/inc && chmod 644 /var/www/html/inc/* && \
find /var/www/html/lib -type d -exec chmod 755 {} \; && \
find /var/www/html/lib -type f -exec chmod 644 {} \;"
@echo "Step 2: Setting up directories and permissions..."
ssh posterg "cd /var/www/posterg && \
mkdir -p var/{cache,logs,tmp} && \
chown -R www-data:posterg . && \
chmod -R 755 . && \
chmod -R 775 var/ database/ && \
chmod 660 database/*.db 2>/dev/null || true"
@echo ""
@echo "✅ Deployment complete!"
@echo ""
@echo "📁 Server structure:"
@echo " • App root: /var/www/posterg/"
@echo " • DocumentRoot: /var/www/posterg/public/"
@echo ""
@echo "🔍 Verify deployment:"
@echo " • Public: https://posterg.erg.be/"
@echo " • Admin: https://posterg.erg.be/admin/"
@echo ""
@echo "⚠️ IMPORTANT: Update nginx config to point to /var/www/posterg/public/"
@echo " Run: just deploy-nginx"
[group('deploy')]
test-deploy:
deploy-database:
@echo "⚠️ Deploying test database (will overwrite remote test.db)"
@echo "Creating database directory if needed..."
ssh posterg "mkdir -p /var/www/html/database"
rsync -vur --progress ./database/test.db posterg:/var/www/html/database/test.db
ssh posterg "mkdir -p /var/www/posterg/database"
rsync -vur --progress ./database/test.db posterg:/var/www/posterg/database/test.db
@echo "Setting correct permissions..."
ssh posterg "chgrp posterg /var/www/html/database /var/www/html/database/test.db && chmod 775 /var/www/html/database && chmod 660 /var/www/html/database/test.db"
ssh posterg "chown www-data:posterg /var/www/posterg/database /var/www/posterg/database/test.db && chmod 775 /var/www/posterg/database && chmod 660 /var/www/posterg/database/test.db"
@echo "✅ Test database deployed and configured"
# Legacy alias
[group('deploy')]
test-deploy:
@just deploy-database
# ============================================================================
# Testing
# ============================================================================
@@ -230,13 +253,28 @@ deploy-test-db:
[group('server')]
deploy-nginx:
@echo "🔧 Deploying nginx configuration..."
@echo ""
@echo "⚠️ IMPORTANT: Checking nginx config has correct DocumentRoot..."
@if ! grep -q "/var/www/posterg/public" nginx/posterg.conf 2>/dev/null; then \
echo "❌ ERROR: nginx/posterg.conf must contain '/var/www/posterg/public'"; \
echo " Current DocumentRoot needs updating!"; \
echo ""; \
echo " Edit nginx/posterg.conf and change:"; \
echo " root /var/www/html;"; \
echo " To:"; \
echo " root /var/www/posterg/public;"; \
exit 1; \
fi
@echo "✅ nginx config looks correct"
@echo ""
rsync -vur --progress ./nginx/posterg.conf posterg:/tmp/posterg.conf
rsync -vur --progress ./nginx/deploy-production.sh posterg:/tmp/deploy-production.sh
rsync -vur --progress ./nginx/deploy-production-new.sh posterg:/tmp/deploy-production.sh
@echo "✅ Files uploaded to /tmp/ on server"
@echo ""
@echo "Next steps on the server:"
@echo " ssh posterg"
@echo " sudo bash /tmp/deploy-production.sh"
@echo " (This will apply config and show reload command)"
[group('server')]
deploy-admin-tools:

View File

@@ -1,205 +0,0 @@
# Default recipe - show available commands
default:
@just --list
# ============================================================================
# Deploy Group
# ============================================================================
# Note: Regular deploy recipes exclude test.db and all *.db files by default
# Use test-deploy explicitly to deploy the test database
[group('deploy')]
deploy-public:
rsync -vur --progress --exclude 'test.db' --exclude '*.db' --exclude 'tests/' --exclude 'cache/' --exclude '*.md' --exclude 'run-tests.php' ./apps/public/ posterg:/var/www/html/
rsync -vur --progress --exclude 'test.db' ./shared/ posterg:/var/www/html/shared/
@echo "Fixing shared library paths for production..."
ssh posterg "cd /var/www/html && find . -maxdepth 1 -name '*.php' -type f -exec sed -i \"s|__DIR__ \. '/\.\./\.\./shared/|__DIR__ . '/shared/|g\" {} \;"
@echo "Fixing permissions..."
ssh posterg "chgrp -R posterg /var/www/html/inc && chmod 755 /var/www/html/inc && chmod 644 /var/www/html/inc/*"
@echo "✓ Deployment complete"
[group('deploy')]
deploy-admin:
rsync -vur --progress --exclude 'test.db' --exclude '*.db' --exclude 'cache/' --exclude '*.md' ./apps/admin/ posterg:/var/www/html/admin/
rsync -vur --progress --exclude 'test.db' ./shared/ posterg:/var/www/html/shared/
@echo "Fixing shared library paths for production (admin)..."
ssh posterg "cd /var/www/html/formulaire && find . -maxdepth 1 -name '*.php' -type f -exec sed -i \"s|__DIR__ \. '/\.\./\.\./shared/|__DIR__ . '/../shared/|g\" {} \;"
@echo "✓ Admin paths fixed"
[group('deploy')]
deploy: deploy-public deploy-admin
@echo ""
@echo "✅ Deployment complete (test.db excluded)"
@echo "To deploy test database, run: just test-deploy"
[group('deploy')]
deploy-database:
@echo "Deploying database directory (excludes test.db by default)..."
rsync -vur --progress --exclude 'test.db' --exclude '*.db-journal' ./database/ posterg:/var/www/html/database/
@echo "✅ Database directory deployed (schema, fixtures, docs only)"
[group('deploy')]
test-deploy:
@echo "⚠️ Deploying test database (will overwrite remote test.db)"
@echo "Creating database directory if needed..."
ssh posterg "mkdir -p /var/www/html/database"
rsync -vur --progress ./database/test.db posterg:/var/www/html/database/test.db
@echo "Setting correct permissions..."
ssh posterg "chgrp posterg /var/www/html/database /var/www/html/database/test.db && chmod 775 /var/www/html/database && chmod 660 /var/www/html/database/test.db"
@echo "✅ Test database deployed and configured"
[group('deploy')]
deploy-nginx:
@echo "🚀 Deploying production nginx configuration..."
rsync -vur --progress ./nginx/posterg.conf posterg:/tmp/posterg.conf
rsync -vur --progress ./nginx/deploy-production.sh posterg:/tmp/deploy-production.sh
@echo "✅ Files uploaded to server"
@echo ""
@echo "Next steps on the server:"
@echo " ssh posterg"
@echo " sudo bash /tmp/deploy-production.sh"
@echo ""
@echo "This will:"
@echo " • Fix file permissions (posterg group)"
@echo " • Install nginx configuration"
@echo " • Set up admin password (if needed)"
@echo " • Test and reload nginx"
[group('deploy')]
deploy-admin-tools:
@echo "📤 Uploading admin user management tools..."
rsync -vur --progress ./nginx/manage-admin-users.sh posterg:/tmp/manage-admin-users.sh
@echo "✅ Script uploaded"
@echo ""
@echo "To manage admin users on the server:"
@echo " ssh posterg"
@echo " sudo bash /tmp/manage-admin-users.sh"
# ============================================================================
# Public Site Development
# ============================================================================
[group('public-dev')]
serve-public:
@echo "Starting public site on http://localhost:8002"
@echo "Press Ctrl+C to stop"
@cd apps/public && php -S 127.0.0.1:8002
[group('public-dev')]
test-public:
@echo "Testing public site..."
@cd apps/public && php test_db.php
[group('public-dev')]
test-public-all:
@echo "Running all public site tests..."
@cd apps/public && php run-tests.php
[group('public-dev')]
stats-public:
@echo "=== Public Database Statistics ==="
@sqlite3 database/test.db "SELECT COUNT(*) || ' total theses' FROM theses;"
@sqlite3 database/test.db "SELECT COUNT(*) || ' published theses' FROM theses WHERE is_published = 1;"
@sqlite3 database/test.db "SELECT COUNT(*) || ' authors' FROM authors;"
@sqlite3 database/test.db "SELECT COUNT(*) || ' keywords' FROM keywords;"
[group('public-dev')]
recent-public:
@echo "=== Recent Published Theses ==="
@sqlite3 -column -header database/test.db "SELECT id, title, year, authors FROM v_theses_public ORDER BY year DESC, title LIMIT 10;"
[group('public-dev')]
check-public:
@echo "Checking public site PHP syntax..."
@cd apps/public && find . -name "*.php" -not -path "./vendor/*" -not -path "./tests/*" -exec php -l {} \; | grep -v "No syntax errors"
@echo "✓ All files have valid syntax"
[group('public-dev')]
logs-public:
@if [ -f apps/public/error.log ]; then tail -n 50 apps/public/error.log; else echo "No error log found"; fi
# ============================================================================
# Admin Panel Development
# ============================================================================
[group('admin-dev')]
init-test-db:
@echo "Creating test database from schema..."
@sqlite3 database/test.db < database/schema.sql
@echo "✓ Test database created"
@sqlite3 database/test.db "SELECT COUNT(*) || ' tables created' FROM sqlite_master WHERE type='table';"
@sqlite3 database/test.db "SELECT COUNT(*) || ' orientations loaded' FROM orientations;"
@sqlite3 database/test.db "SELECT COUNT(*) || ' AP programs loaded' FROM ap_programs;"
[group('admin-dev')]
serve-admin: init-test-db
@echo "Starting admin panel on http://localhost:3000"
@echo "Press Ctrl+C to stop"
@cd apps/admin && php -S 127.0.0.1:3000
[group('admin-dev')]
serve-admin-only:
@echo "Starting admin panel on http://localhost:3000"
@echo "Press Ctrl+C to stop"
@cd apps/admin && php -S 127.0.0.1:3000
[group('admin-dev')]
cleanup-admin:
@echo "Cleaning up admin test files..."
@rm -f database/test.db
@rm -f apps/admin/error.log
@rm -rf apps/admin/data/theses/*
@rm -rf apps/admin/data/covers/*
@echo "✓ Cleanup complete"
[group('admin-dev')]
reset-admin: cleanup-admin init-test-db
@echo "✓ Admin test environment reset"
[group('admin-dev')]
stats-admin:
@echo "=== Admin Database Statistics ==="
@sqlite3 database/test.db "SELECT COUNT(*) || ' theses' FROM theses;"
@sqlite3 database/test.db "SELECT COUNT(*) || ' authors' FROM authors;"
@sqlite3 database/test.db "SELECT COUNT(*) || ' supervisors' FROM supervisors;"
@sqlite3 database/test.db "SELECT COUNT(*) || ' keywords' FROM keywords;"
@sqlite3 database/test.db "SELECT COUNT(*) || ' files uploaded' FROM thesis_files;"
[group('admin-dev')]
recent-admin:
@echo "=== Recent Submissions ==="
@sqlite3 -column -header database/test.db "SELECT identifier, title, year, submitted_at FROM theses ORDER BY submitted_at DESC LIMIT 5;"
[group('admin-dev')]
setup-dirs:
@mkdir -p apps/admin/data/theses
@mkdir -p apps/admin/data/covers
@mkdir -p apps/admin/data/yaml
@touch apps/admin/data/theses/.gitkeep
@touch apps/admin/data/covers/.gitkeep
@echo "✓ Data directories created"
[group('admin-dev')]
dev-admin: setup-dirs init-test-db serve-admin
# ============================================================================
# Database Operations
# ============================================================================
[group('database')]
query-db:
@sqlite3 database/test.db
[group('database')]
show-thesis id:
@sqlite3 -column -header database/test.db "SELECT * FROM v_theses_full WHERE id = {{id}};"
[group('database')]
dump-db:
@sqlite3 database/test.db .dump > database/backup_$(date +%Y%m%d_%H%M%S).sql
@echo "✓ Database dumped to database/backup_$(date +%Y%m%d_%H%M%S).sql"
[group('database')]
create-fixtures:
@echo "Creating test database with fixtures..."
@php database/fixtures/CreateTestDatabase.php

View File

@@ -1 +1 @@
[1770322829]
[1770377487]

105
nginx/deploy-production-new.sh Executable file
View File

@@ -0,0 +1,105 @@
#!/bin/bash
# Deploy production nginx configuration for Post-ERG (NEW STRUCTURE)
# This script applies the nginx config for /var/www/posterg/public/ structure
set -e
echo "🚀 Post-ERG Production Deployment (NEW STRUCTURE)"
echo "=================================================="
echo ""
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}Error: This script must be run as root (use sudo)${NC}"
exit 1
fi
echo "📋 Step 1: Fixing file permissions..."
echo "--------------------------------------"
# Change ownership to www-data:posterg
chown -R www-data:posterg /var/www/posterg/
echo "✓ Changed ownership to www-data:posterg"
# Set directory permissions (755)
find /var/www/posterg -type d -exec chmod 755 {} \;
echo "✓ Set directory permissions to 755"
# Set file permissions (644)
find /var/www/posterg -type f -exec chmod 644 {} \;
echo "✓ Set file permissions to 644"
# Make database directory writable by group
if [ -d "/var/www/posterg/database" ]; then
chmod 775 /var/www/posterg/database
echo "✓ Made database directory group-writable (775)"
fi
# Fix database file permissions
if [ -f "/var/www/posterg/database/test.db" ]; then
chmod 660 /var/www/posterg/database/test.db
chown www-data:posterg /var/www/posterg/database/test.db
echo "✓ Fixed database file permissions (660)"
fi
# Make admin upload directories writable by group
if [ -d "/var/www/posterg/public/admin/data" ]; then
find /var/www/posterg/public/admin/data -type d -exec chmod 775 {} \;
echo "✓ Made admin upload directories group-writable"
fi
echo ""
echo "📋 Step 2: Deploying nginx configuration..."
echo "--------------------------------------"
# Backup existing config
if [ -f "/etc/nginx/sites-available/posterg" ]; then
cp /etc/nginx/sites-available/posterg /etc/nginx/sites-available/posterg.backup.$(date +%Y%m%d_%H%M%S)
echo "✓ Backed up existing config"
fi
# Copy new config
if [ -f "/tmp/posterg.conf" ]; then
cp /tmp/posterg.conf /etc/nginx/sites-available/posterg
echo "✓ Installed new nginx config"
else
echo -e "${RED}Error: /tmp/posterg.conf not found${NC}"
echo "Run 'just deploy-nginx' first"
exit 1
fi
# Test nginx configuration
echo ""
echo "📋 Step 3: Testing nginx configuration..."
echo "--------------------------------------"
if nginx -t; then
echo -e "${GREEN}✓ Nginx configuration is valid${NC}"
else
echo -e "${RED}✗ Nginx configuration has errors!${NC}"
echo "Restoring backup..."
cp /etc/nginx/sites-available/posterg.backup.$(date +%Y%m%d_%H%M%S | tail -1) /etc/nginx/sites-available/posterg
exit 1
fi
echo ""
echo "📋 Step 4: Summary..."
echo "--------------------------------------"
echo -e "${GREEN}✓ Permissions fixed${NC}"
echo -e "${GREEN}✓ Nginx config installed${NC}"
echo -e "${GREEN}✓ Configuration validated${NC}"
echo ""
echo -e "${YELLOW}Ready to reload nginx!${NC}"
echo ""
echo "Run: ${GREEN}sudo systemctl reload nginx${NC}"
echo ""
echo "After reload, verify:"
echo " • https://posterg.erg.be/"
echo " • https://posterg.erg.be/admin/"
echo " • https://posterg.erg.be/database/test.db (should 404)"

View File

@@ -14,7 +14,7 @@ server {
server_name posterg.erg.be www.posterg.erg.be;
root /var/www/html;
root /var/www/posterg/public;
# Add index.php to the list
index index.php index.html index.htm;
@@ -65,7 +65,7 @@ server {
}
# Admin panel - password protected
location ^~ /formulaire/ {
location ^~ /admin/ {
# HTTP Basic Authentication
auth_basic "Admin Access - Post-ERG";
auth_basic_user_file /etc/nginx/.htpasswd-posterg;
@@ -74,7 +74,7 @@ server {
limit_req zone=admin burst=5 nodelay;
# Allow access to public assets without authentication
location ~ ^/formulaire/(css|js|images)/ {
location ~ ^/admin/(css|js|images)/ {
auth_basic off;
}

View File

@@ -1,4 +1,7 @@
<?php
// Bootstrap application
require_once __DIR__ . "/../../config/bootstrap.php";
// Start session and generate CSRF token
session_start();
if (empty($_SESSION["csrf_token"])) {
@@ -8,7 +11,7 @@ if (empty($_SESSION["csrf_token"])) {
$pageTitle = "Ajout de TFE";
// Load database helper
require_once __DIR__ . '/../lib/Database.php';
require_once __DIR__ . '/../../lib/Database.php';
try {
$db = new Database();
@@ -52,7 +55,7 @@ function wasSelected($key, $value)
return $formData[$key] == $value;
}
?>
<? include "inc/head.php"?>
<?php include "inc/head.php"?>
<main>
<?php if ($error): ?>
<div class="error-message" style="background: #fee; border: 2px solid #c00; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; color: #c00;">
@@ -243,4 +246,4 @@ function wasSelected($key, $value)
</form>
</main>
<? include "inc/footer.php"?>
<?php include "inc/footer.php"?>

View File

@@ -1,4 +1,7 @@
<?php
// Bootstrap application
require_once __DIR__ . "/../../config/bootstrap.php";
// Edit thesis page
session_start();
@@ -7,7 +10,7 @@ if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
require_once __DIR__ . '/../lib/Database.php';
require_once __DIR__ . '/../../lib/Database.php';
$thesisId = isset($_GET['id']) ? intval($_GET['id']) : 0;
$error = null;

View File

@@ -1,4 +1,7 @@
<?php // formulaire.php
// Bootstrap application
require_once __DIR__ . "/../../config/bootstrap.php";
// Configure error reporting
ini_set('display_errors', 0);
@@ -18,7 +21,7 @@ if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) ||
// Log the content of the $_FILES array
error_log("FILES array: " . print_r($_FILES, true));
require_once __DIR__ . '/../lib/Database.php';
require_once __DIR__ . '/../../lib/Database.php';
// Helper function to sanitize string input
function sanitize_string($input) {

View File

@@ -1,4 +1,7 @@
<?php
// Bootstrap application
require_once __DIR__ . "/../../config/bootstrap.php";
// CSV Import page for Post-ERG thesis database
// This page allows importing thesis data from CSV files
@@ -9,7 +12,7 @@ if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
require_once __DIR__ . '/../lib/Database.php';
require_once __DIR__ . '/../../lib/Database.php';
$pageTitle = "Import";
@@ -349,4 +352,4 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) {
<p>Voir: <code>../db/Database_TFE_test.csv</code></p>
</main>
<? include "inc/footer.php" ?>
<?php include "inc/footer.php" ?>

View File

@@ -5,18 +5,18 @@
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><? echo $pageTitle ?></title>
<link rel="stylesheet" href="/assets/modern-normalize.css">
<title><?php echo $pageTitle ?></title>
<link rel="stylesheet" href="/assets/modern-normalize.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
<link rel="stylesheet" href="/assets/admin.css">
<link rel="shortcut icon" href="assets/icon.svg" type="image/svg">
<link rel="shortcut icon" href="/assets/admin_favicon.svg" type="image/svg+xml">
</head>
<body>
<header>
<h1><? echo $pageTitle ?></h1>
<h1><?php echo $pageTitle ?></h1>
<nav style="margin-top: 1rem;">
<a href="/admin/list.php" style="font-size: 0.9em;"><button>📋 Liste des TFE</button></a>
<a href="/admin/" style="font-size: 0.9em;"><button>📋 Liste des TFE</button></a>
<a href="/admin/import.php" style="font-size: 0.9em;"><button>📥 Importer CSV</button></a>
</nav>
</header>

View File

@@ -1,4 +1,7 @@
<?php
// Bootstrap application
require_once __DIR__ . "/../../config/bootstrap.php";
// List all theses in the database
session_start();
@@ -8,9 +11,8 @@ if (empty($_SESSION['csrf_token'])) {
}
$pageTitle = "Liste des TFE";
$pageMenu
require_once __DIR__ . '/../lib/Database.php';
require_once __DIR__ . '/../../lib/Database.php';
try {
$db = new Database();
@@ -74,7 +76,7 @@ try {
}
?>
<? include "inc/head.php" ?>
<?php include "inc/head.php" ?>
<script>
function toggleAll(source) {
const checkboxes = document.querySelectorAll('input[name="selected_theses[]"]');
@@ -282,4 +284,4 @@ try {
<?php endif; ?>
</main>
<? include "inc/footer.php" ?>
<?php include "inc/footer.php" ?>

View File

@@ -1,10 +1,13 @@
<?php
// Bootstrap application
require_once __DIR__ . "/../../config/bootstrap.php";
/**
* Handle publish/unpublish actions for theses
*/
session_start();
require_once __DIR__ . '/../lib/Database.php';
require_once __DIR__ . '/../../lib/Database.php';
// Verify CSRF token
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {

View File

@@ -1,10 +1,13 @@
<?php
// Bootstrap application
require_once __DIR__ . "/../../config/bootstrap.php";
// Configure error reporting
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', 'error.log');
require __DIR__ . '/../lib/Database.php';
require __DIR__ . '/../../lib/Database.php';
// Security: Validate thesis ID parameter
$thesisId = null;

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

57
public/index.php Normal file
View File

@@ -0,0 +1,57 @@
<?php
// Load configuration
require_once __DIR__ . '/../config/bootstrap.php';
require_once APP_ROOT . '/lib/Database.php';
$pageTitle = "Liste des TFE";
$page = isset($_GET["page"]) ? intval($_GET["page"]) : 1;
$itemsPerPage = 10;
try {
$db = Database::getInstance();
$offset = ($page - 1) * $itemsPerPage;
$itemsToLoad = $db->getPublishedTheses($itemsPerPage, $offset);
$totalItems = $db->countPublishedTheses();
$totalPages = ceil($totalItems / $itemsPerPage);
} catch (Exception $e) {
error_log("Error loading theses: " . $e->getMessage());
$itemsToLoad = [];
$totalPages = 0;
}
include APP_ROOT . '/includes/header.php';
?>
<main>
<?php foreach ($itemsToLoad as $item): ?>
<a href="memoire.php?id=<?= $item["id"] ?>" class="card-link">
<div class="card">
<div class="card-content">
<h2 class="title"><?= htmlspecialchars($item["title"]) ?></h2>
<p class="authors"><?= htmlspecialchars($item["authors"]) ?></p>
<p class="year"><?= htmlspecialchars($item["year"]) ?></p>
</div>
</div>
</a>
<?php endforeach; ?>
<?php if (empty($itemsToLoad)): ?>
<p>Aucun mémoire trouvé.</p>
<?php endif; ?>
</main>
<?php if ($totalPages > 1): ?>
<nav class="pagination">
<?php if ($page > 1): ?>
<a href="?page=<?= $page - 1 ?>" class="pagination-previous">Précédent</a>
<?php endif; ?>
<?php if ($page < $totalPages): ?>
<a href="?page=<?= $page + 1 ?>" class="pagination-next">Suivant</a>
<?php endif; ?>
<span class="pagination-info">Page <?= $page ?> sur <?= $totalPages ?></span>
</nav>
<?php endif; ?>
<?php include APP_ROOT . '/includes/footer.php'; ?>

View File

@@ -1,11 +1,9 @@
<?php
// Disable displaying errors, log errors to a file named 'error.log'
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', 'error.log');
// Bootstrap application
require_once __DIR__ . '/../config/bootstrap.php';
// Load required libraries and classes
require_once __DIR__ . '/lib/Database.php';
require_once APP_ROOT . '/lib/Database.php';
// Check if an id parameter is provided in the URL
if (isset($_GET['id'])) {
@@ -31,7 +29,7 @@ if (isset($_GET['id'])) {
}
// Include the header template
include 'inc/header.php'; ?>
include APP_ROOT . '/includes/header.php'; ?>
<main>
<div class="item">
<div class="card-content">
@@ -153,4 +151,4 @@ include 'inc/header.php'; ?>
</main>
<!-- Include the footer template -->
<?php include 'inc/footer.php'; ?>
<?php include APP_ROOT . '/includes/footer.php'; ?>

View File

@@ -1,10 +1,9 @@
<?php
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', 'error.log');
// Bootstrap application
require_once __DIR__ . '/../config/bootstrap.php';
require_once APP_ROOT . '/lib/Database.php';
require_once APP_ROOT . '/lib/RateLimit.php';
require_once __DIR__ . '/lib/Database.php';
require_once __DIR__ . '/lib/RateLimit.php';
// Rate limiting: 30 requests per minute
$rateLimit = new RateLimit(30, 60);
@@ -17,7 +16,7 @@ if (!$rateLimit->check()) {
$rateLimit->sendHeaders();
// Display error page
include 'inc/header.php';
include APP_ROOT . '/includes/header.php';
echo '<section class="section">';
echo ' <div class="container">';
echo ' <div class="notification is-danger">';
@@ -27,7 +26,7 @@ if (!$rateLimit->check()) {
echo ' </div>';
echo ' </div>';
echo '</section>';
include 'inc/footer.php';
include APP_ROOT . '/includes/footer.php';
exit;
}
@@ -123,7 +122,7 @@ try {
$languages = [];
}
include 'inc/header.php'; ?>
include APP_ROOT . '/includes/header.php'; ?>
<section class="section">
<div class="container">
@@ -416,4 +415,4 @@ include 'inc/header.php'; ?>
</div>
</section>
<?php include 'inc/footer.php'; ?>
<?php include APP_ROOT . '/includes/footer.php'; ?>

View File

@@ -0,0 +1,180 @@
#!/bin/bash
# Deploy production nginx configuration and fix permissions for Post-ERG
set -e
echo "🚀 Post-ERG Production Deployment"
echo "=================================="
echo ""
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}Error: This script must be run as root (use sudo)${NC}"
exit 1
fi
echo "📋 Step 1: Fixing file permissions..."
echo "--------------------------------------"
# Change group to posterg (www-data is member of this group)
chown -R theophile:posterg /var/www/html/
echo "✓ Changed group to posterg"
# Set directory permissions (755 - readable/executable by everyone)
find /var/www/html -type d -exec chmod 755 {} \;
echo "✓ Set directory permissions to 755"
# Set file permissions (640 - owner read/write, group read)
find /var/www/html -type f -exec chmod 640 {} \;
echo "✓ Set file permissions to 640"
# Make upload directories writable by group (for www-data to write)
if [ -d "/var/www/html/formulaire/data/theses" ]; then
chmod 775 /var/www/html/formulaire/data/theses
chmod 775 /var/www/html/formulaire/data/covers
echo "✓ Set upload directories to 775"
fi
# Protect database if it exists
if [ -f "/var/www/html/database/posterg.db" ]; then
chmod 660 /var/www/html/database/posterg.db
chown www-data:posterg /var/www/html/database/posterg.db
echo "✓ Protected database file"
fi
echo ""
echo "📋 Step 2: Checking prerequisites..."
echo "--------------------------------------"
# Check if htpasswd is available
if ! command -v htpasswd &>/dev/null; then
echo -e "${YELLOW}⚠️ htpasswd not found, installing apache2-utils...${NC}"
apt-get update -qq
apt-get install -y apache2-utils
echo -e "${GREEN}✓ apache2-utils installed${NC}"
fi
# Check if htpasswd file exists
if [ ! -f "/etc/nginx/.htpasswd-posterg" ]; then
echo -e "${YELLOW}⚠️ Warning: /etc/nginx/.htpasswd-posterg not found${NC}"
echo " Creating it now..."
echo ""
echo "Please enter admin username:"
read -r ADMIN_USER
htpasswd -c /etc/nginx/.htpasswd-posterg "$ADMIN_USER"
echo -e "${GREEN}✓ Password file created${NC}"
echo ""
else
echo "✓ Password file exists"
fi
# Check if config file was uploaded
if [ ! -f "/tmp/posterg.conf" ]; then
echo -e "${RED}✗ Error: /tmp/posterg.conf not found${NC}"
echo "Please upload it first: rsync -vur ./nginx/posterg-production.conf posterg:/tmp/posterg.conf"
exit 1
fi
echo ""
echo "📋 Step 3: Installing nginx configuration..."
echo "--------------------------------------"
# Backup existing config if it exists
if [ -f "/etc/nginx/sites-available/posterg" ]; then
cp /etc/nginx/sites-available/posterg /etc/nginx/sites-available/posterg.backup.$(date +%Y%m%d_%H%M%S)
echo "✓ Backed up existing config"
fi
# Copy new configuration
cp /tmp/posterg.conf /etc/nginx/sites-available/posterg
echo "✓ Installed configuration to /etc/nginx/sites-available/posterg"
# Create symlink
if [ ! -L "/etc/nginx/sites-enabled/posterg" ]; then
ln -s /etc/nginx/sites-available/posterg /etc/nginx/sites-enabled/posterg
echo "✓ Created symlink in sites-enabled"
else
echo "✓ Symlink already exists"
fi
# Remove default site
if [ -L "/etc/nginx/sites-enabled/default" ]; then
rm /etc/nginx/sites-enabled/default
echo "✓ Disabled default site"
fi
echo ""
echo "📋 Step 4: Testing nginx configuration..."
echo "--------------------------------------"
if nginx -t; then
echo -e "${GREEN}✓ Nginx configuration is valid${NC}"
else
echo -e "${RED}✗ Nginx configuration has errors!${NC}"
echo "Restoring backup..."
if ls /etc/nginx/sites-available/posterg.backup* 1>/dev/null 2>&1; then
BACKUP=$(ls -t /etc/nginx/sites-available/posterg.backup* | head -1)
cp "$BACKUP" /etc/nginx/sites-available/posterg
echo "Configuration restored from backup"
fi
exit 1
fi
echo ""
echo "📋 Step 5: Reloading nginx..."
echo "--------------------------------------"
if systemctl reload nginx; then
echo -e "${GREEN}✓ Nginx reloaded successfully${NC}"
else
echo -e "${RED}✗ Failed to reload nginx${NC}"
exit 1
fi
echo ""
echo "📋 Step 6: Verifying services..."
echo "--------------------------------------"
# Check PHP-FPM
if systemctl is-active --quiet php8.4-fpm; then
echo -e "${GREEN}✓ PHP 8.4-FPM is running${NC}"
else
echo -e "${YELLOW}⚠️ PHP-FPM is not running, starting it...${NC}"
systemctl start php8.4-fpm
systemctl enable php8.4-fpm
echo -e "${GREEN}✓ PHP-FPM started${NC}"
fi
# Check nginx
if systemctl is-active --quiet nginx; then
echo -e "${GREEN}✓ Nginx is running${NC}"
else
echo -e "${RED}✗ Nginx is not running!${NC}"
exit 1
fi
echo ""
echo "═══════════════════════════════════════"
echo -e "${GREEN}✅ Deployment Complete!${NC}"
echo "═══════════════════════════════════════"
echo ""
echo "🧪 Quick Tests:"
echo " • Test public site: curl -I http://localhost/"
echo " • Test admin panel: curl -I http://localhost/formulaire/"
echo " • Test PHP: curl http://localhost/index.php"
echo ""
echo "📊 View logs:"
echo " • Access log: tail -f /var/log/nginx/posterg_access.log"
echo " • Error log: tail -f /var/log/nginx/posterg_error.log"
echo ""
echo "🔒 Security Checks:"
echo " • Database blocked: curl -I http://localhost/database/posterg.db"
echo " • MD files blocked: curl -I http://localhost/README.md"
echo " • Shared blocked: curl -I http://localhost/shared/Database.php"
echo ""

View File

@@ -0,0 +1,34 @@
#!/bin/bash
# Install PHP SQLite extension
echo "🔧 Installing PHP SQLite extension..."
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo "Error: This script must be run as root (use sudo)"
exit 1
fi
# Detect PHP version
PHP_VERSION=$(php -r "echo PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION;")
echo "Detected PHP version: $PHP_VERSION"
# Install SQLite extension
echo "Installing php${PHP_VERSION}-sqlite3..."
apt-get update -qq
apt-get install -y php${PHP_VERSION}-sqlite3
# Restart PHP-FPM
echo "Restarting PHP-FPM..."
systemctl restart php${PHP_VERSION}-fpm
# Verify installation
if php -m | grep -q sqlite3; then
echo "✅ SQLite extension installed successfully"
echo ""
echo "Installed extensions:"
php -m | grep -i sqlite
else
echo "❌ Failed to install SQLite extension"
exit 1
fi

View File

@@ -0,0 +1,199 @@
#!/bin/bash
# Manage admin users for Post-ERG nginx basic authentication
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
PASSWORD_FILE="/etc/nginx/.htpasswd-posterg"
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}Error: This script must be run as root (use sudo)${NC}"
exit 1
fi
# Check if htpasswd is available
if ! command -v htpasswd &>/dev/null; then
echo -e "${YELLOW}Installing apache2-utils...${NC}"
apt-get update -qq
apt-get install -y apache2-utils
fi
show_menu() {
echo ""
echo -e "${BLUE}════════════════════════════════════════${NC}"
echo -e "${BLUE} Post-ERG Admin User Management${NC}"
echo -e "${BLUE}════════════════════════════════════════${NC}"
echo ""
echo "1. List all users"
echo "2. Add new user"
echo "3. Change user password"
echo "4. Delete user"
echo "5. Reset all (create new password file)"
echo "6. Exit"
echo ""
echo -n "Choose an option [1-6]: "
}
list_users() {
echo ""
if [ ! -f "$PASSWORD_FILE" ]; then
echo -e "${YELLOW}No password file found.${NC}"
return
fi
echo -e "${GREEN}Current admin users:${NC}"
echo "────────────────────────"
cut -d: -f1 "$PASSWORD_FILE" | nl
echo ""
}
add_user() {
echo ""
echo -n "Enter new username: "
read -r USERNAME
if [ -z "$USERNAME" ]; then
echo -e "${RED}Username cannot be empty${NC}"
return
fi
# Check if user already exists
if [ -f "$PASSWORD_FILE" ] && grep -q "^${USERNAME}:" "$PASSWORD_FILE"; then
echo -e "${YELLOW}User '$USERNAME' already exists. Use option 3 to change password.${NC}"
return
fi
# Add user (use -c only if file doesn't exist)
if [ ! -f "$PASSWORD_FILE" ]; then
htpasswd -c "$PASSWORD_FILE" "$USERNAME"
else
htpasswd "$PASSWORD_FILE" "$USERNAME"
fi
echo -e "${GREEN}✓ User '$USERNAME' added successfully${NC}"
}
change_password() {
list_users
echo -n "Enter username to change password: "
read -r USERNAME
if [ -z "$USERNAME" ]; then
echo -e "${RED}Username cannot be empty${NC}"
return
fi
if [ ! -f "$PASSWORD_FILE" ]; then
echo -e "${RED}Password file not found${NC}"
return
fi
if ! grep -q "^${USERNAME}:" "$PASSWORD_FILE"; then
echo -e "${RED}User '$USERNAME' not found${NC}"
return
fi
htpasswd "$PASSWORD_FILE" "$USERNAME"
echo -e "${GREEN}✓ Password changed for user '$USERNAME'${NC}"
}
delete_user() {
list_users
echo -n "Enter username to delete: "
read -r USERNAME
if [ -z "$USERNAME" ]; then
echo -e "${RED}Username cannot be empty${NC}"
return
fi
if [ ! -f "$PASSWORD_FILE" ]; then
echo -e "${RED}Password file not found${NC}"
return
fi
if ! grep -q "^${USERNAME}:" "$PASSWORD_FILE"; then
echo -e "${RED}User '$USERNAME' not found${NC}"
return
fi
echo -n "Are you sure you want to delete user '$USERNAME'? [y/N] "
read -r CONFIRM
if [ "$CONFIRM" = "y" ] || [ "$CONFIRM" = "Y" ]; then
htpasswd -D "$PASSWORD_FILE" "$USERNAME"
echo -e "${GREEN}✓ User '$USERNAME' deleted${NC}"
else
echo "Cancelled"
fi
}
reset_all() {
echo ""
echo -e "${YELLOW}WARNING: This will delete ALL existing users!${NC}"
echo -n "Are you sure? [y/N] "
read -r CONFIRM
if [ "$CONFIRM" != "y" ] && [ "$CONFIRM" != "Y" ]; then
echo "Cancelled"
return
fi
# Backup existing file
if [ -f "$PASSWORD_FILE" ]; then
BACKUP="${PASSWORD_FILE}.backup.$(date +%Y%m%d_%H%M%S)"
cp "$PASSWORD_FILE" "$BACKUP"
echo -e "${GREEN}✓ Backed up to: $BACKUP${NC}"
fi
echo ""
echo -n "Enter new username: "
read -r USERNAME
if [ -z "$USERNAME" ]; then
echo -e "${RED}Username cannot be empty${NC}"
return
fi
htpasswd -c "$PASSWORD_FILE" "$USERNAME"
echo -e "${GREEN}✓ Password file reset with user '$USERNAME'${NC}"
}
# Main loop
while true; do
show_menu
read -r CHOICE
case $CHOICE in
1)
list_users
;;
2)
add_user
;;
3)
change_password
;;
4)
delete_user
;;
5)
reset_all
;;
6)
echo ""
echo "Goodbye!"
exit 0
;;
*)
echo -e "${RED}Invalid option${NC}"
;;
esac
done