mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 19:19:19 +02:00
Refactor admin panel and add migration documentation
- Add comprehensive migration guides (DEPLOYMENT_MIGRATION.md, DIRECTORY_STRUCTURE.md, MIGRATION_CHECKLIST.md) - Refactor admin panel: split add.php, create reusable header/footer - Update styles: admin.css, common.css, main.css - Improve public pages: index.php, memoire.php - Reorganize database documentation into database/docs/ - Update .gitignore and justfile This prepares for migration to public/ directory structure
This commit is contained in:
20
.gitignore
vendored
20
.gitignore
vendored
@@ -1,27 +1,13 @@
|
|||||||
|
# Vendor directory (third-party code)
|
||||||
vendor/
|
vendor/
|
||||||
compose.lock
|
compose.lock
|
||||||
|
|
||||||
### Test databases ###
|
### Test databases ###
|
||||||
formulaire/test.db
|
database/test.db
|
||||||
db/test.db
|
|
||||||
|
|
||||||
### Data et Mémoire###
|
|
||||||
formulaire/data/yaml/*
|
|
||||||
formulaire/data/content/*
|
|
||||||
formulaire/data/cover/*
|
|
||||||
formulaire/data/theses/*
|
|
||||||
formulaire/data/covers/*
|
|
||||||
|
|
||||||
front-backend/data/yaml/*
|
|
||||||
front-backend/data/content/*
|
|
||||||
front-backend/data/cover/*
|
|
||||||
|
|
||||||
### Logs ###
|
### Logs ###
|
||||||
formulaire/error.log
|
formulaire/error.log
|
||||||
|
lib/cache/rate_limit/
|
||||||
|
|
||||||
# Vendor directory (third-party code)
|
|
||||||
vendor/
|
|
||||||
|
|
||||||
# OS files
|
# OS files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
571
DEPLOYMENT_MIGRATION.md
Normal file
571
DEPLOYMENT_MIGRATION.md
Normal file
@@ -0,0 +1,571 @@
|
|||||||
|
# Deployment & Dev Server Migration Guide
|
||||||
|
|
||||||
|
Analysis of current `justfile` and required changes for the new directory structure.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 Current Setup Analysis
|
||||||
|
|
||||||
|
### Current Dev Server (❌ INCORRECT)
|
||||||
|
```bash
|
||||||
|
# From justfile line 33
|
||||||
|
php -S 127.0.0.1:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problems:**
|
||||||
|
- Serves from project root (all files accessible via web)
|
||||||
|
- Exposes sensitive files: `database/`, `tests/`, `vendor/`, config files
|
||||||
|
- Doesn't match production DocumentRoot configuration
|
||||||
|
- Security risk: `.env`, database files, source code all accessible
|
||||||
|
|
||||||
|
### Current Deployment (❌ INCORRECT)
|
||||||
|
```bash
|
||||||
|
# From justfile lines 56-75
|
||||||
|
rsync -vur --progress \
|
||||||
|
--exclude 'vendor' --exclude 'tests' --exclude '*.db' ... \
|
||||||
|
./ posterg:/var/www/html/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problems:**
|
||||||
|
- Deploys entire project to DocumentRoot
|
||||||
|
- Relies on exclusions to hide sensitive files (error-prone)
|
||||||
|
- Wrong structure: `/var/www/html/` should only contain public files
|
||||||
|
- Private files (`src/`, `config/`) are still in DocumentRoot
|
||||||
|
- Nginx serves from `/var/www/html/` exposing everything not excluded
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Required Changes
|
||||||
|
|
||||||
|
### 1. New Directory Structure on Server
|
||||||
|
|
||||||
|
**Before (current):**
|
||||||
|
```
|
||||||
|
/var/www/html/ # DocumentRoot ❌
|
||||||
|
├── index.php # Public
|
||||||
|
├── search.php # Public
|
||||||
|
├── admin/ # Public (protected by nginx)
|
||||||
|
├── assets/ # Public
|
||||||
|
├── inc/ # ❌ EXPOSED (config files!)
|
||||||
|
├── lib/ # ❌ EXPOSED (source code!)
|
||||||
|
├── database/ # ❌ EXPOSED (database files!)
|
||||||
|
└── vendor/ # ❌ (excluded but in wrong place)
|
||||||
|
```
|
||||||
|
|
||||||
|
**After (recommended):**
|
||||||
|
```
|
||||||
|
/var/www/posterg/ # Application root (private)
|
||||||
|
├── public/ # DocumentRoot ✅ (only this exposed)
|
||||||
|
│ ├── index.php
|
||||||
|
│ ├── search.php
|
||||||
|
│ ├── memoire.php
|
||||||
|
│ ├── admin/
|
||||||
|
│ └── assets/
|
||||||
|
├── src/ # ✅ PRIVATE
|
||||||
|
├── config/ # ✅ PRIVATE
|
||||||
|
├── database/ # ✅ PRIVATE
|
||||||
|
├── vendor/ # ✅ PRIVATE
|
||||||
|
├── var/ # ✅ PRIVATE (cache, logs)
|
||||||
|
└── lib/ # ✅ PRIVATE
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Updated Justfile
|
||||||
|
|
||||||
|
### Change 1: Dev Server
|
||||||
|
|
||||||
|
**Current:**
|
||||||
|
```just
|
||||||
|
[group('dev')]
|
||||||
|
serve:
|
||||||
|
@echo "🚀 Starting Post-ERG development server"
|
||||||
|
@echo "📍 Public site: http://localhost:8000"
|
||||||
|
@echo "📍 Admin panel: http://localhost:8000/admin/"
|
||||||
|
@php -S 127.0.0.1:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
**New:**
|
||||||
|
```just
|
||||||
|
[group('dev')]
|
||||||
|
serve:
|
||||||
|
@echo "🚀 Starting Post-ERG development server"
|
||||||
|
@echo "========================================"
|
||||||
|
@echo ""
|
||||||
|
@echo "📍 Public site: http://localhost:8000"
|
||||||
|
@echo "📍 Admin panel: http://localhost:8000/admin/"
|
||||||
|
@echo ""
|
||||||
|
@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!"; \
|
||||||
|
else \
|
||||||
|
echo "💡 Tip: Run 'just setup' to enable live reload"; \
|
||||||
|
fi
|
||||||
|
@echo ""
|
||||||
|
@echo "Press Ctrl+C to stop"
|
||||||
|
@echo ""
|
||||||
|
@php -S 127.0.0.1:8000 -t public/
|
||||||
|
|
||||||
|
# Alternative: If you need router script for URL rewriting
|
||||||
|
[group('dev')]
|
||||||
|
serve-router:
|
||||||
|
@echo "🚀 Starting with router script (for clean URLs)"
|
||||||
|
@php -S 127.0.0.1:8000 -t public/ public/router.php
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key change:** `-t public/` flag tells PHP server to use `public/` as DocumentRoot
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Change 2: Deployment Strategy
|
||||||
|
|
||||||
|
**Current (❌):**
|
||||||
|
```just
|
||||||
|
deploy:
|
||||||
|
rsync -vur --progress \
|
||||||
|
--exclude 'vendor' --exclude 'tests' ... \
|
||||||
|
./ posterg:/var/www/html/
|
||||||
|
```
|
||||||
|
|
||||||
|
**New (✅) - Two-Step Deployment:**
|
||||||
|
|
||||||
|
```just
|
||||||
|
[group('deploy')]
|
||||||
|
deploy:
|
||||||
|
@echo "📤 Deploying Post-ERG site"
|
||||||
|
@echo "=========================="
|
||||||
|
@echo ""
|
||||||
|
@echo "Step 1: Deploying application files..."
|
||||||
|
|
||||||
|
# Deploy entire application to private directory
|
||||||
|
rsync -vur --progress \
|
||||||
|
--exclude '.git*' \
|
||||||
|
--exclude '.jj' \
|
||||||
|
--exclude 'tests/' \
|
||||||
|
--exclude 'docs/' \
|
||||||
|
--exclude '*.md' \
|
||||||
|
--exclude 'justfile*' \
|
||||||
|
--exclude 'setup-dev.sh' \
|
||||||
|
--exclude 'migrate-structure.sh' \
|
||||||
|
--exclude 'database/test.db' \
|
||||||
|
--exclude 'database/backup_*' \
|
||||||
|
--exclude 'database/fixtures/' \
|
||||||
|
--exclude 'var/cache/*' \
|
||||||
|
--exclude 'var/logs/*' \
|
||||||
|
--exclude '.DS_Store' \
|
||||||
|
./ posterg:/var/www/posterg/
|
||||||
|
|
||||||
|
@echo ""
|
||||||
|
@echo "Step 2: Setting up directory structure..."
|
||||||
|
|
||||||
|
# Create necessary directories on server
|
||||||
|
ssh posterg "mkdir -p /var/www/posterg/var/{cache,logs,tmp} && \
|
||||||
|
chown -R www-data:posterg /var/www/posterg/var && \
|
||||||
|
chmod -R 775 /var/www/posterg/var"
|
||||||
|
|
||||||
|
@echo ""
|
||||||
|
@echo "Step 3: Setting permissions..."
|
||||||
|
|
||||||
|
# Set correct ownership and permissions
|
||||||
|
ssh posterg "cd /var/www/posterg && \
|
||||||
|
chown -R www-data:posterg . && \
|
||||||
|
find . -type d -exec chmod 755 {} \; && \
|
||||||
|
find . -type f -exec chmod 644 {} \; && \
|
||||||
|
chmod -R 775 var/ && \
|
||||||
|
chmod -R 775 database/ && \
|
||||||
|
chmod 660 database/*.db"
|
||||||
|
|
||||||
|
@echo ""
|
||||||
|
@echo "✅ Deployment complete!"
|
||||||
|
@echo ""
|
||||||
|
@echo "🔍 Verify deployment:"
|
||||||
|
@echo " • Public: https://posterg.erg.be/"
|
||||||
|
@echo " • Admin: https://posterg.erg.be/admin/"
|
||||||
|
@echo ""
|
||||||
|
@echo "📁 Server structure:"
|
||||||
|
@echo " • App root: /var/www/posterg/"
|
||||||
|
@echo " • DocumentRoot: /var/www/posterg/public/"
|
||||||
|
|
||||||
|
# Quick deploy - only public files (faster for frontend changes)
|
||||||
|
[group('deploy')]
|
||||||
|
deploy-public:
|
||||||
|
@echo "⚡ Quick deploy: public files only"
|
||||||
|
rsync -vur --progress \
|
||||||
|
--delete \
|
||||||
|
./public/ posterg:/var/www/posterg/public/
|
||||||
|
@echo "✅ Public files updated"
|
||||||
|
|
||||||
|
# Deploy only code (no assets)
|
||||||
|
[group('deploy')]
|
||||||
|
deploy-code:
|
||||||
|
@echo "⚡ Deploying PHP code only"
|
||||||
|
rsync -vur --progress \
|
||||||
|
--include='**.php' \
|
||||||
|
--include='**/' \
|
||||||
|
--exclude='*' \
|
||||||
|
./ posterg:/var/www/posterg/
|
||||||
|
@echo "✅ Code updated"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Change 3: Database Deployment
|
||||||
|
|
||||||
|
**Current:**
|
||||||
|
```just
|
||||||
|
test-deploy:
|
||||||
|
ssh posterg "mkdir -p /var/www/html/database"
|
||||||
|
rsync -vur --progress ./database/test.db posterg:/var/www/html/database/test.db
|
||||||
|
```
|
||||||
|
|
||||||
|
**New:**
|
||||||
|
```just
|
||||||
|
[group('deploy')]
|
||||||
|
deploy-database:
|
||||||
|
@echo "📊 Deploying database..."
|
||||||
|
@echo "⚠️ This will overwrite the remote database!"
|
||||||
|
@read -p "Continue? [y/N] " -n 1 -r; \
|
||||||
|
echo; \
|
||||||
|
if [[ $$REPLY =~ ^[Yy]$$ ]]; then \
|
||||||
|
ssh posterg "mkdir -p /var/www/posterg/database" && \
|
||||||
|
rsync -vur --progress ./database/test.db posterg:/var/www/posterg/database/ && \
|
||||||
|
ssh posterg "chown www-data:posterg /var/www/posterg/database/test.db && \
|
||||||
|
chmod 660 /var/www/posterg/database/test.db" && \
|
||||||
|
echo "✅ Database deployed"; \
|
||||||
|
else \
|
||||||
|
echo "❌ Cancelled"; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Backup remote database before deploying
|
||||||
|
[group('deploy')]
|
||||||
|
backup-remote-db:
|
||||||
|
@echo "💾 Backing up remote database..."
|
||||||
|
@ssh posterg "sqlite3 /var/www/posterg/database/test.db .dump" > database/remote_backup_$(date +%Y%m%d_%H%M%S).sql
|
||||||
|
@echo "✅ Remote database backed up locally"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Change 4: Updated Nginx Deployment
|
||||||
|
|
||||||
|
**Current nginx configuration probably points to:**
|
||||||
|
```nginx
|
||||||
|
root /var/www/html;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Should change to:**
|
||||||
|
```nginx
|
||||||
|
root /var/www/posterg/public;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Updated recipe:**
|
||||||
|
```just
|
||||||
|
[group('server')]
|
||||||
|
deploy-nginx:
|
||||||
|
@echo "🔧 Deploying nginx configuration..."
|
||||||
|
@echo ""
|
||||||
|
@echo "⚠️ IMPORTANT: Nginx config must point to /var/www/posterg/public"
|
||||||
|
@echo ""
|
||||||
|
|
||||||
|
# Check if nginx config has correct DocumentRoot
|
||||||
|
@if ! grep -q "/var/www/posterg/public" nginx/posterg.conf 2>/dev/null; then \
|
||||||
|
echo "❌ ERROR: nginx/posterg.conf doesn't contain '/var/www/posterg/public'"; \
|
||||||
|
echo " Update DocumentRoot before deploying!"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
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 /tmp/ on server"
|
||||||
|
@echo ""
|
||||||
|
@echo "Next steps on the server:"
|
||||||
|
@echo " ssh posterg"
|
||||||
|
@echo " sudo bash /tmp/deploy-production.sh"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Nginx Configuration Changes
|
||||||
|
|
||||||
|
### Update `nginx/posterg.conf`
|
||||||
|
|
||||||
|
**Find and replace:**
|
||||||
|
```nginx
|
||||||
|
# OLD
|
||||||
|
root /var/www/html;
|
||||||
|
|
||||||
|
# NEW
|
||||||
|
root /var/www/posterg/public;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Complete example:**
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name posterg.erg.be;
|
||||||
|
|
||||||
|
# Redirect to HTTPS
|
||||||
|
return 301 https://$server_name$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
listen [::]:443 ssl http2;
|
||||||
|
server_name posterg.erg.be;
|
||||||
|
|
||||||
|
# NEW DocumentRoot - only public directory
|
||||||
|
root /var/www/posterg/public;
|
||||||
|
index index.php index.html;
|
||||||
|
|
||||||
|
# SSL configuration
|
||||||
|
ssl_certificate /etc/letsencrypt/live/posterg.erg.be/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/posterg.erg.be/privkey.pem;
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
access_log /var/log/nginx/posterg_access.log;
|
||||||
|
error_log /var/log/nginx/posterg_error.log;
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
|
||||||
|
# Deny access to sensitive files (should already be outside public/)
|
||||||
|
location ~ /\. {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ /database/ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Admin area - basic auth
|
||||||
|
location /admin/ {
|
||||||
|
auth_basic "Admin Access";
|
||||||
|
auth_basic_user_file /etc/nginx/.htpasswd;
|
||||||
|
|
||||||
|
location ~ \.php$ {
|
||||||
|
include snippets/fastcgi-php.conf;
|
||||||
|
fastcgi_pass unix:/var/run/php/php8.4-fpm.sock;
|
||||||
|
# IMPORTANT: Set correct SCRIPT_FILENAME
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# PHP files
|
||||||
|
location ~ \.php$ {
|
||||||
|
include snippets/fastcgi-php.conf;
|
||||||
|
fastcgi_pass unix:/var/run/php/php8.4-fpm.sock;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Static files
|
||||||
|
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deny access to uploaded files execution
|
||||||
|
location ~ ^/uploads/.*\.php$ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Migration Steps
|
||||||
|
|
||||||
|
### Step 1: Local Development
|
||||||
|
```bash
|
||||||
|
# 1. Create new structure
|
||||||
|
mkdir -p public/{admin,assets}
|
||||||
|
mkdir -p src config var/{cache,logs,tmp}
|
||||||
|
|
||||||
|
# 2. Move public files
|
||||||
|
mv index.php search.php memoire.php public/
|
||||||
|
mv assets/* public/assets/
|
||||||
|
mv admin/* public/admin/
|
||||||
|
|
||||||
|
# 3. Move private files
|
||||||
|
mv inc/* config/ # or src/ depending on content
|
||||||
|
mv lib src/lib
|
||||||
|
|
||||||
|
# 4. Update paths in PHP files (see PATHS_UPDATE.md below)
|
||||||
|
|
||||||
|
# 5. Test local dev server
|
||||||
|
just serve
|
||||||
|
# Opens http://localhost:8000
|
||||||
|
# Verify that sensitive files return 404:
|
||||||
|
# http://localhost:8000/database/test.db → 404
|
||||||
|
# http://localhost:8000/config/ → 404
|
||||||
|
# http://localhost:8000/src/ → 404
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Update Nginx Config
|
||||||
|
```bash
|
||||||
|
# Edit nginx/posterg.conf
|
||||||
|
# Change: root /var/www/html;
|
||||||
|
# To: root /var/www/posterg/public;
|
||||||
|
|
||||||
|
# Validate locally
|
||||||
|
nginx -t -c nginx/posterg.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Deploy
|
||||||
|
```bash
|
||||||
|
# 1. Backup current production
|
||||||
|
ssh posterg "tar -czf /tmp/posterg-backup-$(date +%Y%m%d).tar.gz /var/www/html"
|
||||||
|
|
||||||
|
# 2. Create new directory structure
|
||||||
|
ssh posterg "mkdir -p /var/www/posterg"
|
||||||
|
|
||||||
|
# 3. Deploy application
|
||||||
|
just deploy
|
||||||
|
|
||||||
|
# 4. Deploy nginx config
|
||||||
|
just deploy-nginx
|
||||||
|
|
||||||
|
# 5. On server: activate new config
|
||||||
|
ssh posterg
|
||||||
|
sudo bash /tmp/deploy-production.sh
|
||||||
|
|
||||||
|
# 6. Reload nginx
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
|
||||||
|
# 7. Test
|
||||||
|
just server-status
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Testing Checklist
|
||||||
|
|
||||||
|
### Local Testing
|
||||||
|
- [ ] `just serve` starts on port 8000
|
||||||
|
- [ ] Public site loads: http://localhost:8000
|
||||||
|
- [ ] Admin loads: http://localhost:8000/admin/
|
||||||
|
- [ ] Assets load (CSS, images, JS)
|
||||||
|
- [ ] Database files NOT accessible via browser
|
||||||
|
- [ ] Config files NOT accessible via browser
|
||||||
|
- [ ] Tests still pass: `just test`
|
||||||
|
|
||||||
|
### Production Testing
|
||||||
|
- [ ] HTTPS works: https://posterg.erg.be/
|
||||||
|
- [ ] Admin login works
|
||||||
|
- [ ] Search functionality works
|
||||||
|
- [ ] Database connections work
|
||||||
|
- [ ] File uploads work (if applicable)
|
||||||
|
- [ ] Logs written to `/var/www/posterg/var/logs/`
|
||||||
|
- [ ] Sensitive URLs return 404:
|
||||||
|
- https://posterg.erg.be/database/test.db
|
||||||
|
- https://posterg.erg.be/config/
|
||||||
|
- https://posterg.erg.be/src/
|
||||||
|
- https://posterg.erg.be/vendor/
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Path Changes Summary
|
||||||
|
|
||||||
|
| File Type | Current Path | New Path |
|
||||||
|
|-----------|-------------|----------|
|
||||||
|
| Public index | `/index.php` | `/public/index.php` |
|
||||||
|
| Admin panel | `/admin/` | `/public/admin/` |
|
||||||
|
| Assets | `/assets/` | `/public/assets/` |
|
||||||
|
| Config | `/inc/` | `/config/` or `/src/` |
|
||||||
|
| Libraries | `/lib/` | `/src/lib/` |
|
||||||
|
| Database | `/database/` | `/database/` (stays) |
|
||||||
|
| Vendor | `/vendor/` | `/vendor/` (stays) |
|
||||||
|
| Tests | `/tests/` | `/tests/` (stays) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔒 Security Improvements
|
||||||
|
|
||||||
|
### Before
|
||||||
|
- ❌ All files in DocumentRoot
|
||||||
|
- ❌ Relies on nginx deny rules
|
||||||
|
- ❌ One misconfiguration = full exposure
|
||||||
|
- ❌ Database accessible if nginx fails
|
||||||
|
|
||||||
|
### After
|
||||||
|
- ✅ Only `public/` in DocumentRoot
|
||||||
|
- ✅ Physical separation of public/private
|
||||||
|
- ✅ Nginx misconfiguration = site down (not exposed)
|
||||||
|
- ✅ Database physically unreachable via web
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 Tips
|
||||||
|
|
||||||
|
### Router Script (for clean URLs)
|
||||||
|
Create `public/router.php` for development:
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
// Development router script
|
||||||
|
if (php_sapi_name() === 'cli-server') {
|
||||||
|
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||||
|
|
||||||
|
// Serve static files directly
|
||||||
|
if (file_exists(__DIR__ . $path) && is_file(__DIR__ . $path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route everything else to index.php
|
||||||
|
require __DIR__ . '/index.php';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Detection
|
||||||
|
In your code, detect environment:
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
// config/bootstrap.php
|
||||||
|
define('IS_DEV', php_sapi_name() === 'cli-server');
|
||||||
|
define('APP_ROOT', dirname(__DIR__)); // /var/www/posterg
|
||||||
|
define('PUBLIC_ROOT', APP_ROOT . '/public');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use Relative Requires
|
||||||
|
```php
|
||||||
|
// Before (brittle)
|
||||||
|
require_once '/var/www/html/inc/config.php';
|
||||||
|
|
||||||
|
// After (portable)
|
||||||
|
require_once __DIR__ . '/../config/app.php';
|
||||||
|
// or
|
||||||
|
require_once APP_ROOT . '/config/app.php';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🆘 Troubleshooting
|
||||||
|
|
||||||
|
### Issue: 404 on all pages after deploy
|
||||||
|
**Cause:** Nginx DocumentRoot not updated
|
||||||
|
**Fix:** Check nginx config has `root /var/www/posterg/public;`
|
||||||
|
|
||||||
|
### Issue: PHP includes fail
|
||||||
|
**Cause:** Hardcoded paths or wrong APP_ROOT
|
||||||
|
**Fix:** Use `__DIR__` or define APP_ROOT constant
|
||||||
|
|
||||||
|
### Issue: Database connection fails
|
||||||
|
**Cause:** Path to database file wrong
|
||||||
|
**Fix:** Update path from `database/test.db` to `../database/test.db` (from public/)
|
||||||
|
|
||||||
|
### Issue: Can't write to cache/logs
|
||||||
|
**Cause:** Wrong permissions on var/ directory
|
||||||
|
**Fix:** `sudo chown -R www-data:posterg /var/www/posterg/var && chmod -R 775 /var/www/posterg/var`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Create `PATHS_UPDATE.md` - document all PHP file path changes needed
|
||||||
|
2. Create `public/router.php` - for cleaner dev URLs
|
||||||
|
3. Update all `require`/`include` statements to use relative paths
|
||||||
|
4. Test migration on staging/local first
|
||||||
|
5. Schedule production deployment during low-traffic period
|
||||||
246
DIRECTORY_STRUCTURE.md
Normal file
246
DIRECTORY_STRUCTURE.md
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
# Recommended Directory Structure
|
||||||
|
|
||||||
|
Based on the **Standard PHP Package Skeleton** (researched by Paul M. Jones from thousands of GitHub projects).
|
||||||
|
|
||||||
|
## Directory Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
posterg-website/
|
||||||
|
├── public/ # DocumentRoot - publicly accessible files
|
||||||
|
│ ├── index.php # Front controller
|
||||||
|
│ ├── assets/ # Public assets (CSS, JS, images)
|
||||||
|
│ │ ├── css/
|
||||||
|
│ │ ├── js/
|
||||||
|
│ │ └── images/
|
||||||
|
│ └── .htaccess # Apache/nginx rules
|
||||||
|
│
|
||||||
|
├── src/ # Application source code (private)
|
||||||
|
│ ├── Controller/ # Controllers
|
||||||
|
│ ├── Model/ # Models
|
||||||
|
│ ├── View/ # Views/templates
|
||||||
|
│ ├── Service/ # Business logic services
|
||||||
|
│ ├── Repository/ # Data access layer
|
||||||
|
│ └── Middleware/ # Middleware components
|
||||||
|
│
|
||||||
|
├── config/ # Configuration files (private)
|
||||||
|
│ ├── app.php
|
||||||
|
│ ├── database.php
|
||||||
|
│ ├── routes.php
|
||||||
|
│ └── .env.example # Environment variables template
|
||||||
|
│
|
||||||
|
├── database/ # Database-related files
|
||||||
|
│ ├── migrations/ # Database migrations
|
||||||
|
│ ├── seeds/ # Database seeders
|
||||||
|
│ └── schema.sql # Database schema
|
||||||
|
│
|
||||||
|
├── tests/ # Unit and integration tests
|
||||||
|
│ ├── Unit/
|
||||||
|
│ ├── Integration/
|
||||||
|
│ └── bootstrap.php
|
||||||
|
│
|
||||||
|
├── vendor/ # Third-party dependencies (Composer)
|
||||||
|
│ └── autoload.php
|
||||||
|
│
|
||||||
|
├── bin/ # Executable scripts
|
||||||
|
│ └── console # CLI commands
|
||||||
|
│
|
||||||
|
├── var/ # Variable/temporary files (private)
|
||||||
|
│ ├── cache/ # Application cache
|
||||||
|
│ ├── logs/ # Log files
|
||||||
|
│ └── tmp/ # Temporary files
|
||||||
|
│
|
||||||
|
├── docs/ # Documentation
|
||||||
|
│ └── *.md
|
||||||
|
│
|
||||||
|
├── scripts/ # Build/deployment scripts
|
||||||
|
│ └── deploy.sh
|
||||||
|
│
|
||||||
|
├── resources/ # Non-PHP resources (private)
|
||||||
|
│ ├── views/ # Template files
|
||||||
|
│ ├── lang/ # Translations
|
||||||
|
│ └── emails/ # Email templates
|
||||||
|
│
|
||||||
|
├── lib/ # Internal libraries (if not using src/)
|
||||||
|
│
|
||||||
|
├── .git/ # Git repository
|
||||||
|
├── .gitignore
|
||||||
|
├── composer.json # Composer dependencies
|
||||||
|
├── composer.lock
|
||||||
|
├── phpunit.xml # PHPUnit configuration
|
||||||
|
├── README.md # Project documentation
|
||||||
|
└── LICENSE # License file
|
||||||
|
```
|
||||||
|
|
||||||
|
## Directory Purposes
|
||||||
|
|
||||||
|
### **public/** (PUBLIC - DocumentRoot points here)
|
||||||
|
- **Only directory accessible via web browser**
|
||||||
|
- Contains: front controller (index.php), assets (CSS/JS/images)
|
||||||
|
- Web server DocumentRoot should point to this directory
|
||||||
|
- Security: No sensitive files here
|
||||||
|
|
||||||
|
### **src/** (PRIVATE)
|
||||||
|
- Application source code
|
||||||
|
- All classes following PSR-4 autoloading
|
||||||
|
- Organized by responsibility (Controller, Model, Service, etc.)
|
||||||
|
- Not accessible from the web
|
||||||
|
|
||||||
|
### **config/** (PRIVATE)
|
||||||
|
- Configuration files
|
||||||
|
- Database credentials, API keys, app settings
|
||||||
|
- `.env` file for environment-specific configuration
|
||||||
|
- Never committed sensitive values (use `.env.example`)
|
||||||
|
|
||||||
|
### **database/** (PRIVATE)
|
||||||
|
- Database migrations, seeds, schema definitions
|
||||||
|
- Version-controlled database structure
|
||||||
|
|
||||||
|
### **tests/** (PRIVATE)
|
||||||
|
- PHPUnit tests
|
||||||
|
- Organized by test type (Unit, Integration, Functional)
|
||||||
|
- Mirror the `src/` structure
|
||||||
|
|
||||||
|
### **vendor/** (PRIVATE)
|
||||||
|
- Composer dependencies
|
||||||
|
- Auto-generated, excluded from version control
|
||||||
|
- Contains `autoload.php` for autoloading
|
||||||
|
|
||||||
|
### **bin/** (PRIVATE)
|
||||||
|
- Executable scripts and CLI commands
|
||||||
|
- Make scripts executable: `chmod +x bin/*`
|
||||||
|
|
||||||
|
### **var/** (PRIVATE)
|
||||||
|
- Runtime-generated files
|
||||||
|
- cache/, logs/, tmp/
|
||||||
|
- Typically .gitignored (except .gitkeep files)
|
||||||
|
- Needs write permissions
|
||||||
|
|
||||||
|
### **docs/** (PRIVATE)
|
||||||
|
- Project documentation
|
||||||
|
- API docs, guides, architecture decisions
|
||||||
|
|
||||||
|
### **scripts/** (PRIVATE)
|
||||||
|
- Build, deployment, maintenance scripts
|
||||||
|
- Not part of the application runtime
|
||||||
|
|
||||||
|
### **resources/** (PRIVATE)
|
||||||
|
- Non-PHP application resources
|
||||||
|
- Templates, translations, email layouts
|
||||||
|
|
||||||
|
## Migration from Current Structure
|
||||||
|
|
||||||
|
Your current structure → Recommended structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
Current → Recommended
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
index.php → public/index.php
|
||||||
|
memoire.php → public/memoire.php OR src/Controller/
|
||||||
|
search.php → public/search.php OR src/Controller/
|
||||||
|
assets/ → public/assets/
|
||||||
|
inc/ → src/ OR config/
|
||||||
|
admin/ → src/Admin/ OR public/admin/
|
||||||
|
database/ → database/ (keep as-is)
|
||||||
|
tests/ → tests/ (keep as-is)
|
||||||
|
vendor/ → vendor/ (keep as-is)
|
||||||
|
lib/ → lib/ OR src/
|
||||||
|
docs/ → docs/ (keep as-is)
|
||||||
|
scripts/ → scripts/ (keep as-is)
|
||||||
|
nginx/ → scripts/nginx/ OR config/nginx/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Best Practices
|
||||||
|
|
||||||
|
1. **DocumentRoot = public/**
|
||||||
|
- Configure web server to serve only from `public/`
|
||||||
|
- All other directories are above DocumentRoot
|
||||||
|
|
||||||
|
2. **Sensitive Files**
|
||||||
|
- Keep `.env`, config files outside `public/`
|
||||||
|
- Never commit passwords, API keys
|
||||||
|
- Use `.env.example` for templates
|
||||||
|
|
||||||
|
3. **Permissions**
|
||||||
|
```bash
|
||||||
|
# Private directories (not writable by web server)
|
||||||
|
chmod 755 src/ config/ database/ tests/
|
||||||
|
|
||||||
|
# Writable by web server
|
||||||
|
chmod 775 var/cache/ var/logs/ var/tmp/
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **.gitignore**
|
||||||
|
```
|
||||||
|
/vendor/
|
||||||
|
/var/cache/*
|
||||||
|
/var/logs/*
|
||||||
|
/var/tmp/*
|
||||||
|
/.env
|
||||||
|
composer.lock
|
||||||
|
```
|
||||||
|
|
||||||
|
## Web Server Configuration
|
||||||
|
|
||||||
|
### Nginx
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
root /path/to/posterg-website/public;
|
||||||
|
index index.php;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ \.php$ {
|
||||||
|
fastcgi_pass unix:/var/run/php/php-fpm.sock;
|
||||||
|
fastcgi_index index.php;
|
||||||
|
include fastcgi_params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Apache (.htaccess in public/)
|
||||||
|
```apache
|
||||||
|
<IfModule mod_rewrite.c>
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^ index.php [L]
|
||||||
|
</IfModule>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Composer Configuration
|
||||||
|
|
||||||
|
Update `composer.json` to use PSR-4 autoloading:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"App\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Tests\\": "tests/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Create `public/` directory
|
||||||
|
2. Move web-accessible files to `public/`
|
||||||
|
3. Organize classes into `src/` with namespaces
|
||||||
|
4. Move configuration to `config/`
|
||||||
|
5. Update web server DocumentRoot
|
||||||
|
6. Update paths in application code
|
||||||
|
7. Run `composer dump-autoload`
|
||||||
|
8. Test the application
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Standard PHP Package Skeleton](https://github.com/php-pds/skeleton)
|
||||||
|
- [PSR-4 Autoloading](https://www.php-fig.org/psr/psr-4/)
|
||||||
|
- [Composer Documentation](https://getcomposer.org/doc/)
|
||||||
381
MIGRATION_CHECKLIST.md
Normal file
381
MIGRATION_CHECKLIST.md
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
# Migration Checklist - Public Directory Structure
|
||||||
|
|
||||||
|
Quick reference for migrating from current flat structure to secure public/ structure.
|
||||||
|
|
||||||
|
## 📋 Summary of Required Changes
|
||||||
|
|
||||||
|
### 1. Dev Server (justfile) ⚡
|
||||||
|
**Line 51** - Change from:
|
||||||
|
```just
|
||||||
|
@php -S 127.0.0.1:8000
|
||||||
|
```
|
||||||
|
To:
|
||||||
|
```just
|
||||||
|
@php -S 127.0.0.1:8000 -t public/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Deployment (justfile) 📤
|
||||||
|
**Lines 56-75** - Replace entire `deploy` recipe with two-step deployment:
|
||||||
|
- Deploy app to `/var/www/posterg/` (not `/var/www/html/`)
|
||||||
|
- Nginx serves from `/var/www/posterg/public/`
|
||||||
|
|
||||||
|
See `DEPLOYMENT_MIGRATION.md` for complete updated recipe.
|
||||||
|
|
||||||
|
### 3. Nginx Configuration 🔧
|
||||||
|
**nginx/posterg.conf line 14** - Change from:
|
||||||
|
```nginx
|
||||||
|
root /var/www/html;
|
||||||
|
```
|
||||||
|
To:
|
||||||
|
```nginx
|
||||||
|
root /var/www/posterg/public;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Also update admin location** (line 58) - Change from:
|
||||||
|
```nginx
|
||||||
|
location ^~ /formulaire/ {
|
||||||
|
```
|
||||||
|
To:
|
||||||
|
```nginx
|
||||||
|
location ^~ /admin/ {
|
||||||
|
```
|
||||||
|
(Since you're moving admin/ to public/admin/)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Quick Migration (Local Dev)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Update justfile serve command
|
||||||
|
sed -i 's/@php -S 127.0.0.1:8000/@php -S 127.0.0.1:8000 -t public\//' justfile
|
||||||
|
|
||||||
|
# 2. Test new dev server
|
||||||
|
just serve
|
||||||
|
# Visit http://localhost:8000
|
||||||
|
# Verify http://localhost:8000/database/test.db returns 404
|
||||||
|
|
||||||
|
# 3. If it works, you're ready for production migration
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 File Movements (Do This After Testing)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create new structure
|
||||||
|
mkdir -p public/{admin,assets}
|
||||||
|
mkdir -p src config var/{cache,logs,tmp}
|
||||||
|
|
||||||
|
# Move public files
|
||||||
|
mv index.php search.php memoire.php public/
|
||||||
|
mv admin/* public/admin/ && rmdir admin
|
||||||
|
mv assets/* public/assets/ && rmdir assets
|
||||||
|
|
||||||
|
# Move private files
|
||||||
|
mv inc/* config/ && rmdir inc
|
||||||
|
# OR if inc/ contains classes:
|
||||||
|
# mv inc/* src/ && rmdir inc
|
||||||
|
|
||||||
|
# Keep these as-is
|
||||||
|
# database/ (already private)
|
||||||
|
# vendor/ (already private)
|
||||||
|
# tests/ (already private)
|
||||||
|
# lib/ (decide if it goes to src/lib or stays)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Critical Changes Required
|
||||||
|
|
||||||
|
### In nginx/posterg.conf:
|
||||||
|
|
||||||
|
1. **Line 14** - DocumentRoot
|
||||||
|
```nginx
|
||||||
|
# BEFORE
|
||||||
|
root /var/www/html;
|
||||||
|
|
||||||
|
# AFTER
|
||||||
|
root /var/www/posterg/public;
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Line 58-76** - Admin location (already at `/formulaire/`, might want `/admin/`)
|
||||||
|
```nginx
|
||||||
|
# BEFORE
|
||||||
|
location ^~ /formulaire/ {
|
||||||
|
auth_basic "Admin Access - Post-ERG";
|
||||||
|
auth_basic_user_file /etc/nginx/.htpasswd-posterg;
|
||||||
|
# ... rest of config
|
||||||
|
}
|
||||||
|
|
||||||
|
# AFTER (if renaming to /admin/)
|
||||||
|
location ^~ /admin/ {
|
||||||
|
auth_basic "Admin Access - Post-ERG";
|
||||||
|
auth_basic_user_file /etc/nginx/.htpasswd-posterg;
|
||||||
|
# ... rest of config
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Remove/update deny rules** (lines 48-60) - These become redundant!
|
||||||
|
```nginx
|
||||||
|
# BEFORE - needed because everything in DocumentRoot
|
||||||
|
location ^~ /database/ { deny all; }
|
||||||
|
location ^~ /shared/ { deny all; }
|
||||||
|
location ^~ /data/ { deny all; }
|
||||||
|
|
||||||
|
# AFTER - can remove! They're already outside public/
|
||||||
|
# But keep as defense-in-depth:
|
||||||
|
location ^~ /database/ { deny all; } # Will never match, but safe
|
||||||
|
```
|
||||||
|
|
||||||
|
### In justfile:
|
||||||
|
|
||||||
|
**Complete replacement for lines 40-76:**
|
||||||
|
|
||||||
|
```just
|
||||||
|
[group('dev')]
|
||||||
|
serve:
|
||||||
|
@echo "🚀 Starting Post-ERG development server"
|
||||||
|
@echo "========================================"
|
||||||
|
@echo ""
|
||||||
|
@echo "📍 Public site: http://localhost:8000"
|
||||||
|
@echo "📍 Admin panel: http://localhost:8000/admin/"
|
||||||
|
@echo ""
|
||||||
|
@echo "🔒 Serving from public/ directory (matches production)"
|
||||||
|
@if [ -d "vendor/php-live-reload" ]; then \
|
||||||
|
echo "✨ Live reload enabled"; \
|
||||||
|
else \
|
||||||
|
echo "💡 Tip: Run 'just setup' to enable live reload"; \
|
||||||
|
fi
|
||||||
|
@echo ""
|
||||||
|
@echo "Press Ctrl+C to stop"
|
||||||
|
@echo ""
|
||||||
|
@php -S 127.0.0.1:8000 -t public/
|
||||||
|
|
||||||
|
[group('deploy')]
|
||||||
|
deploy:
|
||||||
|
@echo "📤 Deploying Post-ERG complete site"
|
||||||
|
@echo "===================================="
|
||||||
|
@echo ""
|
||||||
|
@echo "Deploying to /var/www/posterg/..."
|
||||||
|
rsync -vur --progress \
|
||||||
|
--chown="www-data:posterg" \
|
||||||
|
--exclude 'vendor' \
|
||||||
|
--exclude 'tests' \
|
||||||
|
--exclude 'test.db' \
|
||||||
|
--exclude '*.md' \
|
||||||
|
--exclude '.git*' \
|
||||||
|
--exclude '.jj' \
|
||||||
|
--exclude '.DS_Store' \
|
||||||
|
--exclude 'justfile*' \
|
||||||
|
--exclude 'setup-dev.sh' \
|
||||||
|
--exclude 'database/backup_*' \
|
||||||
|
--exclude 'database/fixtures' \
|
||||||
|
--exclude 'var/cache/*' \
|
||||||
|
--exclude 'var/logs/*' \
|
||||||
|
./ posterg:/var/www/posterg/
|
||||||
|
@echo ""
|
||||||
|
@echo "Setting up directories and permissions..."
|
||||||
|
ssh posterg "cd /var/www/posterg && \
|
||||||
|
mkdir -p var/{cache,logs,tmp} && \
|
||||||
|
chown -R www-data:posterg var/ database/ && \
|
||||||
|
chmod -R 775 var/ database/ && \
|
||||||
|
chmod 660 database/*.db"
|
||||||
|
@echo ""
|
||||||
|
@echo "✅ Deployment complete!"
|
||||||
|
@echo ""
|
||||||
|
@echo "🔍 Verify:"
|
||||||
|
@echo " • Public: https://posterg.erg.be/"
|
||||||
|
@echo " • Admin: https://posterg.erg.be/admin/"
|
||||||
|
|
||||||
|
[group('deploy')]
|
||||||
|
test-deploy:
|
||||||
|
@echo "⚠️ Deploying test database"
|
||||||
|
ssh posterg "mkdir -p /var/www/posterg/database"
|
||||||
|
rsync -vur --progress ./database/test.db posterg:/var/www/posterg/database/
|
||||||
|
ssh posterg "chown www-data:posterg /var/www/posterg/database/test.db && \
|
||||||
|
chmod 660 /var/www/posterg/database/test.db"
|
||||||
|
@echo "✅ Test database deployed"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Testing Steps
|
||||||
|
|
||||||
|
### 1. Test Local Dev Server
|
||||||
|
```bash
|
||||||
|
# Start server with new -t public/ flag
|
||||||
|
just serve
|
||||||
|
|
||||||
|
# In another terminal:
|
||||||
|
curl http://localhost:8000/ # ✅ Should work
|
||||||
|
curl http://localhost:8000/admin/ # ✅ Should work (after moving)
|
||||||
|
curl http://localhost:8000/database/test.db # ❌ Should 404
|
||||||
|
curl http://localhost:8000/config/ # ❌ Should 404
|
||||||
|
curl http://localhost:8000/vendor/ # ❌ Should 404
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Test After File Migration
|
||||||
|
```bash
|
||||||
|
# After moving files to public/
|
||||||
|
just serve
|
||||||
|
|
||||||
|
# Test again
|
||||||
|
curl http://localhost:8000/ # ✅ index.php serves
|
||||||
|
curl http://localhost:8000/search.php # ✅ works
|
||||||
|
curl http://localhost:8000/admin/ # ✅ works
|
||||||
|
curl http://localhost:8000/assets/css/style.css # ✅ works
|
||||||
|
|
||||||
|
# Verify old paths don't work
|
||||||
|
curl http://localhost:8000/../database/test.db # ❌ 404
|
||||||
|
curl http://localhost:8000/../config/ # ❌ 404
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Test Production Deployment
|
||||||
|
```bash
|
||||||
|
# After deploying to server
|
||||||
|
just server-status
|
||||||
|
|
||||||
|
# Manual checks
|
||||||
|
curl -I https://posterg.erg.be/
|
||||||
|
curl -I https://posterg.erg.be/admin/
|
||||||
|
curl -I https://posterg.erg.be/database/test.db # Must be 404!
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 PHP Path Updates Needed
|
||||||
|
|
||||||
|
After moving to public/, update PHP includes:
|
||||||
|
|
||||||
|
**Before (from root):**
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
require_once 'inc/config.php';
|
||||||
|
require_once 'lib/Database.php';
|
||||||
|
require_once 'database/test.db';
|
||||||
|
```
|
||||||
|
|
||||||
|
**After (from public/):**
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../config/config.php';
|
||||||
|
require_once __DIR__ . '/../src/lib/Database.php';
|
||||||
|
$db = new PDO('sqlite:' . __DIR__ . '/../database/test.db');
|
||||||
|
```
|
||||||
|
|
||||||
|
**Or use a bootstrap:**
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
// public/index.php
|
||||||
|
require_once __DIR__ . '/../config/bootstrap.php';
|
||||||
|
|
||||||
|
// config/bootstrap.php
|
||||||
|
define('APP_ROOT', dirname(__DIR__));
|
||||||
|
define('PUBLIC_ROOT', APP_ROOT . '/public');
|
||||||
|
define('DATABASE_PATH', APP_ROOT . '/database/test.db');
|
||||||
|
|
||||||
|
require_once APP_ROOT . '/vendor/autoload.php';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 What to Check in Your Code
|
||||||
|
|
||||||
|
Run these searches to find hardcoded paths:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Find absolute paths
|
||||||
|
grep -r "/var/www/html" ./*.php
|
||||||
|
grep -r "/var/www/html" ./admin/*.php
|
||||||
|
|
||||||
|
# Find relative paths that need updating
|
||||||
|
grep -r "require.*inc/" ./*.php
|
||||||
|
grep -r "require.*lib/" ./*.php
|
||||||
|
grep -r "require.*database/" ./*.php
|
||||||
|
|
||||||
|
# Find database connections
|
||||||
|
grep -r "\.db" ./*.php
|
||||||
|
|
||||||
|
# Find asset references
|
||||||
|
grep -r "src=\"assets/" ./*.php
|
||||||
|
grep -r "href=\"assets/" ./*.php
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚡ Quick Start (Minimal Changes First)
|
||||||
|
|
||||||
|
If you want to test with minimal changes:
|
||||||
|
|
||||||
|
**1. Only change dev server:**
|
||||||
|
```bash
|
||||||
|
# Edit justfile line 51
|
||||||
|
@php -S 127.0.0.1:8000 -t public/
|
||||||
|
|
||||||
|
# Create public/ with symlinks (temporary test)
|
||||||
|
mkdir public
|
||||||
|
ln -s ../index.php public/index.php
|
||||||
|
ln -s ../search.php public/search.php
|
||||||
|
ln -s ../admin public/admin
|
||||||
|
ln -s ../assets public/assets
|
||||||
|
|
||||||
|
# Test
|
||||||
|
just serve
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. If it works, do full migration**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🆘 Rollback Plan
|
||||||
|
|
||||||
|
If production deployment fails:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On server
|
||||||
|
sudo systemctl stop nginx
|
||||||
|
|
||||||
|
# Restore old config
|
||||||
|
sudo cp /etc/nginx/sites-available/posterg.backup /etc/nginx/sites-available/posterg
|
||||||
|
sudo nginx -t
|
||||||
|
sudo systemctl start nginx
|
||||||
|
|
||||||
|
# Or restore files
|
||||||
|
sudo rm -rf /var/www/posterg
|
||||||
|
sudo mv /var/www/html.backup /var/www/html
|
||||||
|
```
|
||||||
|
|
||||||
|
**Always backup before deploying:**
|
||||||
|
```bash
|
||||||
|
# Before migration
|
||||||
|
ssh posterg "sudo cp -r /var/www/html /var/www/html.backup"
|
||||||
|
ssh posterg "sudo cp /etc/nginx/sites-available/posterg /etc/nginx/sites-available/posterg.backup"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Benefits After Migration
|
||||||
|
|
||||||
|
| Before | After |
|
||||||
|
|--------|-------|
|
||||||
|
| ❌ All files in DocumentRoot | ✅ Only public/ in DocumentRoot |
|
||||||
|
| ❌ Database accessible if nginx misconfigured | ✅ Database physically unreachable |
|
||||||
|
| ❌ Config files one deny rule away | ✅ Config files outside web root |
|
||||||
|
| ❌ 20+ deny/exclude rules needed | ✅ Physical separation, minimal rules |
|
||||||
|
| ❌ Dev server exposes everything | ✅ Dev matches production security |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next: Check Your Current Paths
|
||||||
|
|
||||||
|
Run this to see what paths need updating:
|
||||||
|
```bash
|
||||||
|
# Find all require/include statements
|
||||||
|
find . -name "*.php" -not -path "./vendor/*" -not -path "./tests/*" \
|
||||||
|
-exec grep -H "require\|include" {} \; > paths-audit.txt
|
||||||
|
|
||||||
|
# Review paths-audit.txt and update paths
|
||||||
|
cat paths-audit.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
See **DEPLOYMENT_MIGRATION.md** for complete implementation details.
|
||||||
246
admin/add.php
Normal file
246
admin/add.php
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
<?php
|
||||||
|
// Start session and generate CSRF token
|
||||||
|
session_start();
|
||||||
|
if (empty($_SESSION["csrf_token"])) {
|
||||||
|
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
|
||||||
|
}
|
||||||
|
|
||||||
|
$pageTitle = "Ajout de TFE";
|
||||||
|
|
||||||
|
// Load database helper
|
||||||
|
require_once __DIR__ . '/../lib/Database.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = new Database();
|
||||||
|
$orientations = $db->getAllOrientations();
|
||||||
|
$apPrograms = $db->getAllAPPrograms();
|
||||||
|
$finalityTypes = $db->getAllFinalityTypes();
|
||||||
|
$languages = $db->getAllLanguages();
|
||||||
|
$formatTypes = $db->getAllFormatTypes();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Failed to load form data: " . $e->getMessage());
|
||||||
|
die("Erreur lors du chargement du formulaire. Veuillez réessayer plus tard.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get error message and preserved form data from session (if redirected back from error)
|
||||||
|
$error = isset($_SESSION["form_error"]) ? $_SESSION["form_error"] : null;
|
||||||
|
$formData = isset($_SESSION["form_data"]) ? $_SESSION["form_data"] : [];
|
||||||
|
|
||||||
|
// Clear session data after retrieving
|
||||||
|
unset($_SESSION["form_error"]);
|
||||||
|
unset($_SESSION["form_data"]);
|
||||||
|
|
||||||
|
// Helper function to get old form value
|
||||||
|
function old($key, $default = "")
|
||||||
|
{
|
||||||
|
global $formData;
|
||||||
|
return isset($formData[$key])
|
||||||
|
? htmlspecialchars($formData[$key])
|
||||||
|
: $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to check if value was previously selected
|
||||||
|
function wasSelected($key, $value)
|
||||||
|
{
|
||||||
|
global $formData;
|
||||||
|
if (!isset($formData[$key])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (is_array($formData[$key])) {
|
||||||
|
return in_array($value, $formData[$key]);
|
||||||
|
}
|
||||||
|
return $formData[$key] == $value;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<? 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;">
|
||||||
|
<strong>⚠️ Erreur:</strong> <?php echo htmlspecialchars($error); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="formulaire.php" method="post" enctype="multipart/form-data">
|
||||||
|
<!-- CSRF Protection -->
|
||||||
|
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars(
|
||||||
|
$_SESSION["csrf_token"],
|
||||||
|
); ?>">
|
||||||
|
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Informations de base</legend>
|
||||||
|
<label for="auteurice">Nom/Prénom/Pseudo *</label>
|
||||||
|
<input type="text" id="auteurice" name="auteurice" placeholder="Nom de l'auteur·ice" value="<?php echo old(
|
||||||
|
"auteurice",
|
||||||
|
); ?>" required>
|
||||||
|
|
||||||
|
<label for="mail">Contact (email, site web, insta, ...)</label>
|
||||||
|
<input type="text" id="mail" name="mail" placeholder="votre.email@example.com ou @instagram" value="<?php echo old(
|
||||||
|
"mail",
|
||||||
|
); ?>">
|
||||||
|
<label for="année">Année diplômante *</label>
|
||||||
|
<input type="number" id="année" name="année" min="2000" max="<?php echo date(
|
||||||
|
"Y",
|
||||||
|
) + 1; ?>" placeholder="<?php echo date(
|
||||||
|
"Y",
|
||||||
|
); ?>" value="<?php echo old("année"); ?>" required>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Informations académiques</legend>
|
||||||
|
<label for="orientation">Orientation principale *</label>
|
||||||
|
<select id="orientation" name="orientation" required>
|
||||||
|
<option value="">-- Sélectionner une orientation --</option>
|
||||||
|
<?php foreach ($orientations as $orientation): ?>
|
||||||
|
<option value="<?php echo htmlspecialchars(
|
||||||
|
$orientation["id"],
|
||||||
|
); ?>" <?php echo wasSelected(
|
||||||
|
"orientation",
|
||||||
|
$orientation["id"],
|
||||||
|
)
|
||||||
|
? "selected"
|
||||||
|
: ""; ?>>
|
||||||
|
<?php echo htmlspecialchars(
|
||||||
|
$orientation["name"],
|
||||||
|
); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label for="ap">Atelier Pratique (AP) *</label>
|
||||||
|
<select id="ap" name="ap" required>
|
||||||
|
<option value="">-- Sélectionner un AP --</option>
|
||||||
|
<?php foreach ($apPrograms as $ap): ?>
|
||||||
|
<option value="<?php echo htmlspecialchars(
|
||||||
|
$ap["id"],
|
||||||
|
); ?>" <?php echo wasSelected("ap", $ap["id"])
|
||||||
|
? "selected"
|
||||||
|
: ""; ?>>
|
||||||
|
<?php echo htmlspecialchars($ap["name"]); ?>
|
||||||
|
<?php if (
|
||||||
|
$ap["code"]
|
||||||
|
): ?> (<?php echo htmlspecialchars(
|
||||||
|
$ap["code"],
|
||||||
|
); ?>)<?php endif; ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label for="finality">Finalité du master *</label>
|
||||||
|
<select id="finality" name="finality" required>
|
||||||
|
<option value="">-- Sélectionner une finalité --</option>
|
||||||
|
<?php foreach ($finalityTypes as $finality): ?>
|
||||||
|
<option value="<?php echo htmlspecialchars(
|
||||||
|
$finality["id"],
|
||||||
|
); ?>" <?php echo wasSelected(
|
||||||
|
"finality",
|
||||||
|
$finality["id"],
|
||||||
|
)
|
||||||
|
? "selected"
|
||||||
|
: ""; ?>>
|
||||||
|
<?php echo htmlspecialchars($finality["name"]); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label for="promoteurice">Promoteur·ice(s)</label>
|
||||||
|
<input type="text" id="promoteurice" name="promoteurice" placeholder="Nom du/de la promoteur·ice (si plusieurs, séparer par des virgules)" value="<?php echo old(
|
||||||
|
"promoteurice",
|
||||||
|
); ?>">
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>À propos du TFE</legend>
|
||||||
|
<label for="titre">Titre du mémoire *</label>
|
||||||
|
<input type="text" id="titre" name="titre" placeholder="Titre de votre TFE" value="<?php echo old(
|
||||||
|
"titre",
|
||||||
|
); ?>" required>
|
||||||
|
|
||||||
|
<label for="subtitle">Sous-titre (si applicable)</label>
|
||||||
|
<input type="text" id="subtitle" name="subtitle" placeholder="Sous-titre de votre TFE" value="<?php echo old(
|
||||||
|
"subtitle",
|
||||||
|
); ?>">
|
||||||
|
<label for="synopsis">Synopsis (environ 200 mots) *</label>
|
||||||
|
<textarea id="synopsis" name="synopsis" rows="8" placeholder="Décrivez votre TFE en quelques paragraphes..." required><?php echo old(
|
||||||
|
"synopsis",
|
||||||
|
); ?></textarea>
|
||||||
|
<label for="problématique">Problématique</label>
|
||||||
|
<textarea id="problématique" name="problématique" rows="4" placeholder="La problématique principale de votre mémoire..."><?php echo old(
|
||||||
|
"problématique",
|
||||||
|
); ?></textarea>
|
||||||
|
<label>Langue(s) du TFE * (sélection multiple possible)</label>
|
||||||
|
<ul style="list-style: none;">
|
||||||
|
<?php foreach ($languages as $language): ?>
|
||||||
|
<li>
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" name="languages[]" value="<?php echo htmlspecialchars(
|
||||||
|
$language["id"],
|
||||||
|
); ?>" <?php echo wasSelected(
|
||||||
|
"languages",
|
||||||
|
$language["id"],
|
||||||
|
)
|
||||||
|
? "checked"
|
||||||
|
: ""; ?>>
|
||||||
|
<?php echo htmlspecialchars($language["name"]); ?>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<label>Format(s) (sélection multiple possible)</label>
|
||||||
|
<ul style="list-style: none;">
|
||||||
|
<?php foreach ($formatTypes as $format): ?>
|
||||||
|
<li>
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" name="formats[]" value="<?php echo htmlspecialchars(
|
||||||
|
$format["id"],
|
||||||
|
); ?>" <?php echo wasSelected(
|
||||||
|
"formats",
|
||||||
|
$format["id"],
|
||||||
|
)
|
||||||
|
? "checked"
|
||||||
|
: ""; ?>>
|
||||||
|
<?php echo htmlspecialchars($format["name"]); ?>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<label for="tag">Mots-clés (max 10, séparés par des virgules)</label>
|
||||||
|
<input type="text" id="tag" name="tag" placeholder="typographie, photographie, outils libre, post-colonial..." value="<?php echo old(
|
||||||
|
"tag",
|
||||||
|
); ?>">
|
||||||
|
<small>Séparez les mots-clés par des virgules. Maximum 10 mots-clés.</small>
|
||||||
|
<label for="duration_info">Durée/Taille (si applicable)</label>
|
||||||
|
<input type="text" id="duration_info" name="duration_info" placeholder="Ex: 68 minutes, 128 pages, 78 pages + 15 minutes" value="<?php echo old(
|
||||||
|
"duration_info",
|
||||||
|
); ?>">
|
||||||
|
<small>Indiquez la durée (en minutes) ou le nombre de pages de votre TFE.</small>
|
||||||
|
<label for="lien">Lien vers un site web ou ressource en ligne</label>
|
||||||
|
<input type="url" id="lien" name="lien" placeholder="https://monmemoire.erg.be/..." value="<?php echo old(
|
||||||
|
"lien",
|
||||||
|
); ?>">
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Fichiers</legend>
|
||||||
|
<label for="couverture">Importer une image de couverture</label>
|
||||||
|
<small>Formats acceptés : JPG, PNG. Taille max : 10MB.</small>
|
||||||
|
<input type="file" id="couverture" name="couverture" accept="image/jpeg,image/png">
|
||||||
|
|
||||||
|
<label for="files">Importer le TFE et les fichiers annexes</label>
|
||||||
|
<small>Formats acceptés : PDF, JPG, PNG, MP4, ZIP. Taille max par fichier : 50MB.</small>
|
||||||
|
<small>Si vous voulez importer un dossier, créez une archive ZIP.</small>
|
||||||
|
<input type="file" id="files" name="files[]" multiple accept=".pdf,.jpg,.jpeg,.png,.mp4,.zip">
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<input type="submit" name="go" value="Soumettre mon TFE">
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<? include "inc/footer.php"?>
|
||||||
@@ -11,6 +11,8 @@ if (empty($_SESSION['csrf_token'])) {
|
|||||||
|
|
||||||
require_once __DIR__ . '/../lib/Database.php';
|
require_once __DIR__ . '/../lib/Database.php';
|
||||||
|
|
||||||
|
$pageTitle = "Import";
|
||||||
|
|
||||||
$message = '';
|
$message = '';
|
||||||
$errors = [];
|
$errors = [];
|
||||||
$importedCount = 0;
|
$importedCount = 0;
|
||||||
@@ -249,7 +251,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) {
|
|||||||
$db->commit();
|
$db->commit();
|
||||||
$importedCount++;
|
$importedCount++;
|
||||||
$importResults[] = "✓ Ligne $lineNumber: \"$title\" importé (ID: $thesisId)";
|
$importResults[] = "✓ Ligne $lineNumber: \"$title\" importé (ID: $thesisId)";
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$db->rollback();
|
$db->rollback();
|
||||||
$skippedCount++;
|
$skippedCount++;
|
||||||
@@ -261,7 +262,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) {
|
|||||||
fclose($handle);
|
fclose($handle);
|
||||||
|
|
||||||
$message = "Import terminé : $importedCount TFE importés, $skippedCount ignorés.";
|
$message = "Import terminé : $importedCount TFE importés, $skippedCount ignorés.";
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$errors[] = $e->getMessage();
|
$errors[] = $e->getMessage();
|
||||||
error_log("CSV import error: " . $e->getMessage());
|
error_log("CSV import error: " . $e->getMessage());
|
||||||
@@ -272,26 +272,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) {
|
|||||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
// <title>Import CSV - Post-ERG</title>
|
||||||
<html lang="fr">
|
// <header>
|
||||||
<head>
|
// <h1>Import CSV - Post-ERG</h1>
|
||||||
<meta charset="UTF-8">
|
// <nav>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
// <a href="index.php">← Nouveau TFE</a> |
|
||||||
<title>Import CSV - Post-ERG</title>
|
// <a href="list.php">📋 Liste des TFE</a>
|
||||||
<link rel="stylesheet" href="assets/normalize.css">
|
// </nav>
|
||||||
<link rel="stylesheet" href="https://raw.githack.com/waldyrious/downstyler/master/downstyler.css" />
|
// </header>
|
||||||
<link rel="shortcut icon" href="assets/icon.svg" type="image/svg">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<h1>Import CSV - Post-ERG</h1>
|
|
||||||
<nav>
|
|
||||||
<a href="index.php">← Nouveau TFE</a> |
|
|
||||||
<a href="list.php">📋 Liste des TFE</a>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<h2>Importer des TFE depuis un fichier CSV</h2>
|
<h2>Importer des TFE depuis un fichier CSV</h2>
|
||||||
|
|
||||||
<?php if (!empty($errors)): ?>
|
<?php if (!empty($errors)): ?>
|
||||||
@@ -357,10 +347,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) {
|
|||||||
|
|
||||||
<h3>Exemple de fichier CSV</h3>
|
<h3>Exemple de fichier CSV</h3>
|
||||||
<p>Voir: <code>../db/Database_TFE_test.csv</code></p>
|
<p>Voir: <code>../db/Database_TFE_test.csv</code></p>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer>
|
<? include "inc/footer.php" ?>
|
||||||
<p>Import CSV - Post-ERG Database</p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
6
admin/inc/footer.php
Normal file
6
admin/inc/footer.php
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<footer>
|
||||||
|
<p>Formulaire fait avec ❤ en PHP et <a href="https://watercss.kognise.dev/">Water.css</a>.</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
22
admin/inc/head.php
Normal file
22
admin/inc/head.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<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">
|
||||||
|
<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">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1><? 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/import.php" style="font-size: 0.9em;"><button>📥 Importer CSV</button></a>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
539
admin/index.php
539
admin/index.php
@@ -1,300 +1,285 @@
|
|||||||
<?php
|
<?php
|
||||||
// Start session and generate CSRF token
|
// List all theses in the database
|
||||||
session_start();
|
session_start();
|
||||||
if (empty($_SESSION["csrf_token"])) {
|
|
||||||
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
|
// Generate CSRF token
|
||||||
|
if (empty($_SESSION['csrf_token'])) {
|
||||||
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load database helper
|
$pageTitle = "Liste des TFE";
|
||||||
|
$pageMenu
|
||||||
|
|
||||||
require_once __DIR__ . '/../lib/Database.php';
|
require_once __DIR__ . '/../lib/Database.php';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$db = new Database();
|
$db = new Database();
|
||||||
|
$pdo = $db->getPDO();
|
||||||
|
|
||||||
|
// Get filter parameters
|
||||||
|
$searchQuery = isset($_GET['search']) ? trim($_GET['search']) : '';
|
||||||
|
$yearFilter = isset($_GET['year']) ? intval($_GET['year']) : null;
|
||||||
|
$orientationFilter = isset($_GET['orientation']) ? intval($_GET['orientation']) : null;
|
||||||
|
|
||||||
|
// Build query
|
||||||
|
$sql = "SELECT
|
||||||
|
t.id, t.identifier, t.title, t.subtitle, t.year,
|
||||||
|
o.name as orientation,
|
||||||
|
ap.name as ap_program,
|
||||||
|
GROUP_CONCAT(DISTINCT a.name) as authors,
|
||||||
|
t.submitted_at,
|
||||||
|
t.is_published
|
||||||
|
FROM theses t
|
||||||
|
LEFT JOIN orientations o ON t.orientation_id = o.id
|
||||||
|
LEFT JOIN ap_programs ap ON t.ap_program_id = ap.id
|
||||||
|
LEFT JOIN thesis_authors ta ON t.id = ta.thesis_id
|
||||||
|
LEFT JOIN authors a ON ta.author_id = a.id
|
||||||
|
WHERE 1=1";
|
||||||
|
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
if ($searchQuery) {
|
||||||
|
$sql .= " AND (t.title LIKE ? OR t.subtitle LIKE ? OR a.name LIKE ?)";
|
||||||
|
$searchParam = "%$searchQuery%";
|
||||||
|
$params[] = $searchParam;
|
||||||
|
$params[] = $searchParam;
|
||||||
|
$params[] = $searchParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($yearFilter) {
|
||||||
|
$sql .= " AND t.year = ?";
|
||||||
|
$params[] = $yearFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($orientationFilter) {
|
||||||
|
$sql .= " AND t.orientation_id = ?";
|
||||||
|
$params[] = $orientationFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " GROUP BY t.id ORDER BY t.year DESC, t.submitted_at DESC";
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
$theses = $stmt->fetchAll();
|
||||||
|
|
||||||
|
// Get unique years for filter
|
||||||
|
$yearsStmt = $pdo->query("SELECT DISTINCT year FROM theses ORDER BY year DESC");
|
||||||
|
$years = $yearsStmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
// Get orientations for filter
|
||||||
$orientations = $db->getAllOrientations();
|
$orientations = $db->getAllOrientations();
|
||||||
$apPrograms = $db->getAllAPPrograms();
|
|
||||||
$finalityTypes = $db->getAllFinalityTypes();
|
|
||||||
$languages = $db->getAllLanguages();
|
|
||||||
$formatTypes = $db->getAllFormatTypes();
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
error_log("Failed to load form data: " . $e->getMessage());
|
error_log("Error loading theses list: " . $e->getMessage());
|
||||||
die("Erreur lors du chargement du formulaire. Veuillez réessayer plus tard.");
|
die("Erreur lors du chargement de la liste.");
|
||||||
}
|
|
||||||
|
|
||||||
// Get error message and preserved form data from session (if redirected back from error)
|
|
||||||
$error = isset($_SESSION["form_error"]) ? $_SESSION["form_error"] : null;
|
|
||||||
$formData = isset($_SESSION["form_data"]) ? $_SESSION["form_data"] : [];
|
|
||||||
|
|
||||||
// Clear session data after retrieving
|
|
||||||
unset($_SESSION["form_error"]);
|
|
||||||
unset($_SESSION["form_data"]);
|
|
||||||
|
|
||||||
// Helper function to get old form value
|
|
||||||
function old($key, $default = "")
|
|
||||||
{
|
|
||||||
global $formData;
|
|
||||||
return isset($formData[$key])
|
|
||||||
? htmlspecialchars($formData[$key])
|
|
||||||
: $default;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to check if value was previously selected
|
|
||||||
function wasSelected($key, $value)
|
|
||||||
{
|
|
||||||
global $formData;
|
|
||||||
if (!isset($formData[$key])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (is_array($formData[$key])) {
|
|
||||||
return in_array($value, $formData[$key]);
|
|
||||||
}
|
|
||||||
return $formData[$key] == $value;
|
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
<? include "inc/head.php" ?>
|
||||||
<meta charset="UTF-8">
|
<script>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
function toggleAll(source) {
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
const checkboxes = document.querySelectorAll('input[name="selected_theses[]"]');
|
||||||
<title>Formulaire</title>
|
checkboxes.forEach(checkbox => {
|
||||||
<link rel="stylesheet" href="/assets/modern-normalize.css">
|
checkbox.checked = source.checked;
|
||||||
<link rel="stylesheet" href="https://raw.githack.com/waldyrious/downstyler/master/downstyler.css" />
|
});
|
||||||
<link rel="shortcut icon" href="assets/icon.svg" type="image/svg">
|
updateBulkActionsVisibility();
|
||||||
</head>
|
}
|
||||||
|
|
||||||
<body>
|
function updateBulkActionsVisibility() {
|
||||||
<header>
|
const checkboxes = document.querySelectorAll('input[name="selected_theses[]"]:checked');
|
||||||
<h1>Formulaire Posterg</h1>
|
const bulkActions = document.getElementById('bulk-actions');
|
||||||
<nav style="margin-top: 1rem;">
|
const selectedCount = document.getElementById('selected-count');
|
||||||
<a href="list.php" style="font-size: 0.9em;">📋 Liste des TFE</a> |
|
|
||||||
<a href="import.php" style="font-size: 0.9em;">📥 Importer CSV</a>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
if (checkboxes.length > 0) {
|
||||||
<?php if ($error): ?>
|
bulkActions.style.display = 'flex';
|
||||||
<div class="error-message" style="background: #fee; border: 2px solid #c00; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; color: #c00;">
|
selectedCount.textContent = checkboxes.length;
|
||||||
<strong>⚠️ Erreur:</strong> <?php echo htmlspecialchars($error); ?>
|
} else {
|
||||||
|
bulkActions.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function bulkAction(action) {
|
||||||
|
const checkboxes = document.querySelectorAll('input[name="selected_theses[]"]:checked');
|
||||||
|
if (checkboxes.length === 0) {
|
||||||
|
alert('Veuillez sélectionner au moins un TFE.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionText = action === 'publish' ? 'publier' : 'dépublier';
|
||||||
|
if (!confirm(`Voulez-vous vraiment ${actionText} ${checkboxes.length} TFE(s) ?`)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set action
|
||||||
|
document.getElementById('bulk-action-input').value = action;
|
||||||
|
|
||||||
|
// Copy selected thesis IDs to hidden form
|
||||||
|
const bulkCheckboxesContainer = document.getElementById('bulk-checkboxes');
|
||||||
|
bulkCheckboxesContainer.innerHTML = '';
|
||||||
|
checkboxes.forEach(checkbox => {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'hidden';
|
||||||
|
input.name = 'selected_theses[]';
|
||||||
|
input.value = checkbox.value;
|
||||||
|
bulkCheckboxesContainer.appendChild(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Submit the form
|
||||||
|
document.getElementById('bulk-form').submit();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Add change listeners to all checkboxes
|
||||||
|
document.querySelectorAll('input[name="selected_theses[]"]').forEach(checkbox => {
|
||||||
|
checkbox.addEventListener('change', updateBulkActionsVisibility);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<?php if (isset($_SESSION['error'])): ?>
|
||||||
|
<div style="background: #fee; border: 2px solid #c00; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; color: #c00;">
|
||||||
|
<strong>⚠️ Erreur:</strong> <?php echo htmlspecialchars($_SESSION['error']);
|
||||||
|
unset($_SESSION['error']); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<form action="formulaire.php" method="post" enctype="multipart/form-data">
|
<?php if (isset($_SESSION['success'])): ?>
|
||||||
<!-- CSRF Protection -->
|
<div style="background: #efe; border: 2px solid #0a0; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; color: #0a0;">
|
||||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars(
|
<strong>✓ <?php echo htmlspecialchars($_SESSION['success']);
|
||||||
$_SESSION["csrf_token"],
|
unset($_SESSION['success']); ?></strong>
|
||||||
); ?>">
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<h2>Informations de base</h2>
|
<div id="bulk-actions" class="bulk-actions" style="display: none;">
|
||||||
|
<strong><span id="selected-count">0</span> TFE(s) sélectionné(s)</strong>
|
||||||
|
<div class="bulk-actions-buttons">
|
||||||
|
<button type="button" class="btn-bulk-publish" onclick="bulkAction('publish')">Publier la sélection</button>
|
||||||
|
<button type="button" class="btn-bulk-unpublish" onclick="bulkAction('unpublish')">Dépublier la sélection</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<fieldset>
|
<form id="bulk-form" method="post" action="publish.php" style="display: none;">
|
||||||
<label for="auteurice">Nom/Prénom/Pseudo *</label>
|
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
||||||
<input type="text" id="auteurice" name="auteurice" placeholder="Nom de l'auteur·ice" value="<?php echo old(
|
<input type="hidden" id="bulk-action-input" name="action" value="">
|
||||||
"auteurice",
|
<input type="hidden" name="bulk" value="1">
|
||||||
); ?>" required>
|
<div id="bulk-checkboxes"></div>
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<label for="mail">Contact (email, site web, insta, ...)</label>
|
|
||||||
<input type="text" id="mail" name="mail" placeholder="votre.email@example.com ou @instagram" value="<?php echo old(
|
|
||||||
"mail",
|
|
||||||
); ?>">
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<label for="année">Année diplômante *</label>
|
|
||||||
<input type="number" id="année" name="année" min="2000" max="<?php echo date(
|
|
||||||
"Y",
|
|
||||||
) + 1; ?>" placeholder="<?php echo date(
|
|
||||||
"Y",
|
|
||||||
); ?>" value="<?php echo old("année"); ?>" required>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<h2>Informations académiques</h2>
|
|
||||||
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<label for="orientation">Orientation principale *</label>
|
|
||||||
<select id="orientation" name="orientation" required>
|
|
||||||
<option value="">-- Sélectionner une orientation --</option>
|
|
||||||
<?php foreach ($orientations as $orientation): ?>
|
|
||||||
<option value="<?php echo htmlspecialchars(
|
|
||||||
$orientation["id"],
|
|
||||||
); ?>" <?php echo wasSelected(
|
|
||||||
"orientation",
|
|
||||||
$orientation["id"],
|
|
||||||
)
|
|
||||||
? "selected"
|
|
||||||
: ""; ?>>
|
|
||||||
<?php echo htmlspecialchars(
|
|
||||||
$orientation["name"],
|
|
||||||
); ?>
|
|
||||||
</option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<label for="ap">Atelier Pratique (AP) *</label>
|
|
||||||
<select id="ap" name="ap" required>
|
|
||||||
<option value="">-- Sélectionner un AP --</option>
|
|
||||||
<?php foreach ($apPrograms as $ap): ?>
|
|
||||||
<option value="<?php echo htmlspecialchars(
|
|
||||||
$ap["id"],
|
|
||||||
); ?>" <?php echo wasSelected("ap", $ap["id"])
|
|
||||||
? "selected"
|
|
||||||
: ""; ?>>
|
|
||||||
<?php echo htmlspecialchars($ap["name"]); ?>
|
|
||||||
<?php if (
|
|
||||||
$ap["code"]
|
|
||||||
): ?> (<?php echo htmlspecialchars(
|
|
||||||
$ap["code"],
|
|
||||||
); ?>)<?php endif; ?>
|
|
||||||
</option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<label for="finality">Finalité du master *</label>
|
|
||||||
<select id="finality" name="finality" required>
|
|
||||||
<option value="">-- Sélectionner une finalité --</option>
|
|
||||||
<?php foreach ($finalityTypes as $finality): ?>
|
|
||||||
<option value="<?php echo htmlspecialchars(
|
|
||||||
$finality["id"],
|
|
||||||
); ?>" <?php echo wasSelected(
|
|
||||||
"finality",
|
|
||||||
$finality["id"],
|
|
||||||
)
|
|
||||||
? "selected"
|
|
||||||
: ""; ?>>
|
|
||||||
<?php echo htmlspecialchars($finality["name"]); ?>
|
|
||||||
</option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<label for="promoteurice">Promoteur·ice(s)</label>
|
|
||||||
<input type="text" id="promoteurice" name="promoteurice" placeholder="Nom du/de la promoteur·ice (si plusieurs, séparer par des virgules)" value="<?php echo old(
|
|
||||||
"promoteurice",
|
|
||||||
); ?>">
|
|
||||||
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<h2>À propos du TFE</h2>
|
|
||||||
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<label for="titre">Titre du mémoire *</label>
|
|
||||||
<input type="text" id="titre" name="titre" placeholder="Titre de votre TFE" value="<?php echo old(
|
|
||||||
"titre",
|
|
||||||
); ?>" required>
|
|
||||||
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<label for="subtitle">Sous-titre (si applicable)</label>
|
|
||||||
<input type="text" id="subtitle" name="subtitle" placeholder="Sous-titre de votre TFE" value="<?php echo old(
|
|
||||||
"subtitle",
|
|
||||||
); ?>">
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<label for="synopsis">Synopsis (environ 200 mots) *</label>
|
|
||||||
<textarea id="synopsis" name="synopsis" rows="8" placeholder="Décrivez votre TFE en quelques paragraphes..." required><?php echo old(
|
|
||||||
"synopsis",
|
|
||||||
); ?></textarea>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<label for="problématique">Problématique</label>
|
|
||||||
<textarea id="problématique" name="problématique" rows="4" placeholder="La problématique principale de votre mémoire..."><?php echo old(
|
|
||||||
"problématique",
|
|
||||||
); ?></textarea>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<label>Langue(s) du TFE * (sélection multiple possible)</label>
|
|
||||||
<?php foreach ($languages as $language): ?>
|
|
||||||
<label class="checkbox-label">
|
|
||||||
<input type="checkbox" name="languages[]" value="<?php echo htmlspecialchars(
|
|
||||||
$language["id"],
|
|
||||||
); ?>" <?php echo wasSelected(
|
|
||||||
"languages",
|
|
||||||
$language["id"],
|
|
||||||
)
|
|
||||||
? "checked"
|
|
||||||
: ""; ?>>
|
|
||||||
<?php echo htmlspecialchars($language["name"]); ?>
|
|
||||||
</label>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<label>Format(s) (sélection multiple possible)</label>
|
|
||||||
<?php foreach ($formatTypes as $format): ?>
|
|
||||||
<label class="checkbox-label">
|
|
||||||
<input type="checkbox" name="formats[]" value="<?php echo htmlspecialchars(
|
|
||||||
$format["id"],
|
|
||||||
); ?>" <?php echo wasSelected(
|
|
||||||
"formats",
|
|
||||||
$format["id"],
|
|
||||||
)
|
|
||||||
? "checked"
|
|
||||||
: ""; ?>>
|
|
||||||
<?php echo htmlspecialchars($format["name"]); ?>
|
|
||||||
</label>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<label for="tag">Mots-clés (max 10, séparés par des virgules)</label>
|
|
||||||
<input type="text" id="tag" name="tag" placeholder="typographie, photographie, outils libre, post-colonial..." value="<?php echo old(
|
|
||||||
"tag",
|
|
||||||
); ?>">
|
|
||||||
<small>Séparez les mots-clés par des virgules. Maximum 10 mots-clés.</small>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<label for="duration_info">Durée/Taille (si applicable)</label>
|
|
||||||
<input type="text" id="duration_info" name="duration_info" placeholder="Ex: 68 minutes, 128 pages, 78 pages + 15 minutes" value="<?php echo old(
|
|
||||||
"duration_info",
|
|
||||||
); ?>">
|
|
||||||
<small>Indiquez la durée (en minutes) ou le nombre de pages de votre TFE.</small>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<label for="lien">Lien vers un site web ou ressource en ligne</label>
|
|
||||||
<input type="url" id="lien" name="lien" placeholder="https://monmemoire.erg.be/..." value="<?php echo old(
|
|
||||||
"lien",
|
|
||||||
); ?>">
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<h2>Fichiers</h2>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<label for="couverture">Importer une image de couverture</label>
|
|
||||||
<small>Formats acceptés : JPG, PNG. Taille max : 10MB.</small>
|
|
||||||
<input type="file" id="couverture" name="couverture" accept="image/jpeg,image/png">
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<label for="files">Importer le TFE et les fichiers annexes</label>
|
|
||||||
<small>Formats acceptés : PDF, JPG, PNG, MP4, ZIP. Taille max par fichier : 50MB.</small>
|
|
||||||
<small>Si vous voulez importer un dossier, créez une archive ZIP.</small>
|
|
||||||
<input type="file" id="files" name="files[]" multiple accept=".pdf,.jpg,.jpeg,.png,.mp4,.zip">
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<br>
|
|
||||||
<input type="submit" name="go" value="Soumettre mon TFE">
|
|
||||||
</form>
|
</form>
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
<div class="stats">
|
||||||
<p>Formulaire fait avec ❤ en PHP et <a href="https://github.com/kevquirk/simple.css">SimpleCSS</a>.</p>
|
<div class="stat-card">
|
||||||
</footer>
|
<div class="stat-number"><?php echo count($theses); ?></div>
|
||||||
</body>
|
<div class="stat-label">TFE total</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-number"><?php echo count(array_filter($theses, fn($t) => $t['is_published'])); ?></div>
|
||||||
|
<div class="stat-label">Publiés</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-number"><?php echo count(array_filter($theses, fn($t) => !$t['is_published'])); ?></div>
|
||||||
|
<div class="stat-label">En attente</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</html>
|
<div class="filters">
|
||||||
|
<form method="get" action="list.php">
|
||||||
|
<fieldset>
|
||||||
|
<label for="search">Rechercher</label>
|
||||||
|
<input type="text" id="search" name="search" placeholder="Titre, auteur..." value="<?php echo htmlspecialchars($searchQuery); ?>">
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<label for="year">Année</label>
|
||||||
|
<select id="year" name="year">
|
||||||
|
<option value="">Toutes</option>
|
||||||
|
<?php foreach ($years as $year): ?>
|
||||||
|
<option value="<?php echo $year; ?>" <?php echo $yearFilter == $year ? 'selected' : ''; ?>>
|
||||||
|
<?php echo $year; ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<label for="orientation">Orientation</label>
|
||||||
|
<select id="orientation" name="orientation">
|
||||||
|
<option value="">Toutes</option>
|
||||||
|
<?php foreach ($orientations as $orientation): ?>
|
||||||
|
<option value="<?php echo $orientation['id']; ?>" <?php echo $orientationFilter == $orientation['id'] ? 'selected' : ''; ?>>
|
||||||
|
<?php echo htmlspecialchars($orientation['name']); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<button type="submit">Filtrer</button>
|
||||||
|
<?php if ($searchQuery || $yearFilter || $orientationFilter): ?>
|
||||||
|
<a href="list.php">Réinitialiser</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (empty($theses)): ?>
|
||||||
|
<p>Aucun TFE trouvé.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<table class="thesis-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><input type="checkbox" class="select-all-checkbox" onchange="toggleAll(this)" title="Tout sélectionner"></th>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Titre</th>
|
||||||
|
<th>Auteur(s)</th>
|
||||||
|
<th>Année</th>
|
||||||
|
<th>Orientation</th>
|
||||||
|
<th>AP</th>
|
||||||
|
<th>Statut</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($theses as $thesis): ?>
|
||||||
|
<tr>
|
||||||
|
<td><input type="checkbox" class="select-checkbox" name="selected_theses[]" value="<?php echo $thesis['id']; ?>"></td>
|
||||||
|
<td><?php echo htmlspecialchars($thesis['identifier'] ?? $thesis['id']); ?></td>
|
||||||
|
<td>
|
||||||
|
<div class="thesis-title"><?php echo htmlspecialchars($thesis['title']); ?></div>
|
||||||
|
<?php if ($thesis['subtitle']): ?>
|
||||||
|
<div class="thesis-subtitle"><?php echo htmlspecialchars($thesis['subtitle']); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><?php echo htmlspecialchars($thesis['authors'] ?? 'N/A'); ?></td>
|
||||||
|
<td><?php echo $thesis['year']; ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($thesis['orientation'] ?? 'N/A'); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($thesis['ap_program'] ?? 'N/A'); ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if ($thesis['is_published']): ?>
|
||||||
|
<span class="status-badge status-published">Publié</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="status-badge status-pending">En attente</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="actions">
|
||||||
|
<a href="thanks.php?id=<?php echo $thesis['id']; ?>" class="btn btn-view">Voir</a>
|
||||||
|
<a href="edit.php?id=<?php echo $thesis['id']; ?>" class="btn btn-edit">Éditer</a>
|
||||||
|
<form method="post" action="publish.php" class="publish-form">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
||||||
|
<input type="hidden" name="thesis_id" value="<?php echo $thesis['id']; ?>">
|
||||||
|
<?php if ($thesis['is_published']): ?>
|
||||||
|
<input type="hidden" name="action" value="unpublish">
|
||||||
|
<button type="submit" class="btn btn-unpublish" onclick="return confirm('Retirer ce TFE de la publication ?');">Dépublier</button>
|
||||||
|
<?php else: ?>
|
||||||
|
<input type="hidden" name="action" value="publish">
|
||||||
|
<button type="submit" class="btn btn-publish">Publier</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php endif; ?>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<? include "inc/footer.php" ?>
|
||||||
|
|||||||
451
admin/list.php
451
admin/list.php
@@ -1,451 +0,0 @@
|
|||||||
<?php
|
|
||||||
// List all theses in the database
|
|
||||||
session_start();
|
|
||||||
|
|
||||||
// Generate CSRF token
|
|
||||||
if (empty($_SESSION['csrf_token'])) {
|
|
||||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
|
||||||
}
|
|
||||||
|
|
||||||
require_once __DIR__ . '/../lib/Database.php';
|
|
||||||
|
|
||||||
try {
|
|
||||||
$db = new Database();
|
|
||||||
$pdo = $db->getPDO();
|
|
||||||
|
|
||||||
// Get filter parameters
|
|
||||||
$searchQuery = isset($_GET['search']) ? trim($_GET['search']) : '';
|
|
||||||
$yearFilter = isset($_GET['year']) ? intval($_GET['year']) : null;
|
|
||||||
$orientationFilter = isset($_GET['orientation']) ? intval($_GET['orientation']) : null;
|
|
||||||
|
|
||||||
// Build query
|
|
||||||
$sql = "SELECT
|
|
||||||
t.id, t.identifier, t.title, t.subtitle, t.year,
|
|
||||||
o.name as orientation,
|
|
||||||
ap.name as ap_program,
|
|
||||||
GROUP_CONCAT(DISTINCT a.name) as authors,
|
|
||||||
t.submitted_at,
|
|
||||||
t.is_published
|
|
||||||
FROM theses t
|
|
||||||
LEFT JOIN orientations o ON t.orientation_id = o.id
|
|
||||||
LEFT JOIN ap_programs ap ON t.ap_program_id = ap.id
|
|
||||||
LEFT JOIN thesis_authors ta ON t.id = ta.thesis_id
|
|
||||||
LEFT JOIN authors a ON ta.author_id = a.id
|
|
||||||
WHERE 1=1";
|
|
||||||
|
|
||||||
$params = [];
|
|
||||||
|
|
||||||
if ($searchQuery) {
|
|
||||||
$sql .= " AND (t.title LIKE ? OR t.subtitle LIKE ? OR a.name LIKE ?)";
|
|
||||||
$searchParam = "%$searchQuery%";
|
|
||||||
$params[] = $searchParam;
|
|
||||||
$params[] = $searchParam;
|
|
||||||
$params[] = $searchParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($yearFilter) {
|
|
||||||
$sql .= " AND t.year = ?";
|
|
||||||
$params[] = $yearFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($orientationFilter) {
|
|
||||||
$sql .= " AND t.orientation_id = ?";
|
|
||||||
$params[] = $orientationFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sql .= " GROUP BY t.id ORDER BY t.year DESC, t.submitted_at DESC";
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$stmt->execute($params);
|
|
||||||
$theses = $stmt->fetchAll();
|
|
||||||
|
|
||||||
// Get unique years for filter
|
|
||||||
$yearsStmt = $pdo->query("SELECT DISTINCT year FROM theses ORDER BY year DESC");
|
|
||||||
$years = $yearsStmt->fetchAll(PDO::FETCH_COLUMN);
|
|
||||||
|
|
||||||
// Get orientations for filter
|
|
||||||
$orientations = $db->getAllOrientations();
|
|
||||||
|
|
||||||
} catch (Exception $e) {
|
|
||||||
error_log("Error loading theses list: " . $e->getMessage());
|
|
||||||
die("Erreur lors du chargement de la liste.");
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="fr">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Liste des TFE - Post-ERG</title>
|
|
||||||
<link rel="stylesheet" href="assets/normalize.css">
|
|
||||||
<link rel="stylesheet" href="https://raw.githack.com/waldyrious/downstyler/master/downstyler.css" />
|
|
||||||
<link rel="shortcut icon" href="assets/icon.svg" type="image/svg">
|
|
||||||
<style>
|
|
||||||
.filters {
|
|
||||||
background: #f5f5f5;
|
|
||||||
padding: 1rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.filters form {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: end;
|
|
||||||
}
|
|
||||||
.filters fieldset {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
.thesis-table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
.thesis-table th,
|
|
||||||
.thesis-table td {
|
|
||||||
padding: 0.75rem;
|
|
||||||
text-align: left;
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
.thesis-table th {
|
|
||||||
background: #f0f0f0;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.thesis-table tr:hover {
|
|
||||||
background: #f9f9f9;
|
|
||||||
}
|
|
||||||
.thesis-title {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.thesis-subtitle {
|
|
||||||
font-style: italic;
|
|
||||||
color: #666;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
.status-badge {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-size: 0.85em;
|
|
||||||
}
|
|
||||||
.status-pending {
|
|
||||||
background: #ffd700;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
.status-published {
|
|
||||||
background: #90ee90;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
.btn {
|
|
||||||
padding: 0.35rem 0.75rem;
|
|
||||||
border-radius: 3px;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 0.9em;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.btn-view {
|
|
||||||
background: #4a90e2;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.btn-edit {
|
|
||||||
background: #f39c12;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.btn-publish {
|
|
||||||
background: #27ae60;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.btn-unpublish {
|
|
||||||
background: #95a5a6;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.publish-form {
|
|
||||||
display: inline;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.stats {
|
|
||||||
display: flex;
|
|
||||||
gap: 2rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
.stat-card {
|
|
||||||
background: #f5f5f5;
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
min-width: 150px;
|
|
||||||
}
|
|
||||||
.stat-number {
|
|
||||||
font-size: 2em;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #4a90e2;
|
|
||||||
}
|
|
||||||
.stat-label {
|
|
||||||
color: #666;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
.bulk-actions {
|
|
||||||
background: #f5f5f5;
|
|
||||||
padding: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.bulk-actions-buttons {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
.btn-bulk-publish {
|
|
||||||
background: #27ae60;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
.btn-bulk-unpublish {
|
|
||||||
background: #95a5a6;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
.select-checkbox {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.select-all-checkbox {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script>
|
|
||||||
function toggleAll(source) {
|
|
||||||
const checkboxes = document.querySelectorAll('input[name="selected_theses[]"]');
|
|
||||||
checkboxes.forEach(checkbox => {
|
|
||||||
checkbox.checked = source.checked;
|
|
||||||
});
|
|
||||||
updateBulkActionsVisibility();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateBulkActionsVisibility() {
|
|
||||||
const checkboxes = document.querySelectorAll('input[name="selected_theses[]"]:checked');
|
|
||||||
const bulkActions = document.getElementById('bulk-actions');
|
|
||||||
const selectedCount = document.getElementById('selected-count');
|
|
||||||
|
|
||||||
if (checkboxes.length > 0) {
|
|
||||||
bulkActions.style.display = 'flex';
|
|
||||||
selectedCount.textContent = checkboxes.length;
|
|
||||||
} else {
|
|
||||||
bulkActions.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function bulkAction(action) {
|
|
||||||
const checkboxes = document.querySelectorAll('input[name="selected_theses[]"]:checked');
|
|
||||||
if (checkboxes.length === 0) {
|
|
||||||
alert('Veuillez sélectionner au moins un TFE.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const actionText = action === 'publish' ? 'publier' : 'dépublier';
|
|
||||||
if (!confirm(`Voulez-vous vraiment ${actionText} ${checkboxes.length} TFE(s) ?`)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set action
|
|
||||||
document.getElementById('bulk-action-input').value = action;
|
|
||||||
|
|
||||||
// Copy selected thesis IDs to hidden form
|
|
||||||
const bulkCheckboxesContainer = document.getElementById('bulk-checkboxes');
|
|
||||||
bulkCheckboxesContainer.innerHTML = '';
|
|
||||||
checkboxes.forEach(checkbox => {
|
|
||||||
const input = document.createElement('input');
|
|
||||||
input.type = 'hidden';
|
|
||||||
input.name = 'selected_theses[]';
|
|
||||||
input.value = checkbox.value;
|
|
||||||
bulkCheckboxesContainer.appendChild(input);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Submit the form
|
|
||||||
document.getElementById('bulk-form').submit();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
// Add change listeners to all checkboxes
|
|
||||||
document.querySelectorAll('input[name="selected_theses[]"]').forEach(checkbox => {
|
|
||||||
checkbox.addEventListener('change', updateBulkActionsVisibility);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<h1>Liste des TFE</h1>
|
|
||||||
<nav>
|
|
||||||
<a href="index.php">← Nouveau TFE</a> |
|
|
||||||
<a href="import.php">📥 Importer CSV</a>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<?php if (isset($_SESSION['error'])): ?>
|
|
||||||
<div style="background: #fee; border: 2px solid #c00; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; color: #c00;">
|
|
||||||
<strong>⚠️ Erreur:</strong> <?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php if (isset($_SESSION['success'])): ?>
|
|
||||||
<div style="background: #efe; border: 2px solid #0a0; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; color: #0a0;">
|
|
||||||
<strong>✓ <?php echo htmlspecialchars($_SESSION['success']); unset($_SESSION['success']); ?></strong>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<div id="bulk-actions" class="bulk-actions" style="display: none;">
|
|
||||||
<strong><span id="selected-count">0</span> TFE(s) sélectionné(s)</strong>
|
|
||||||
<div class="bulk-actions-buttons">
|
|
||||||
<button type="button" class="btn-bulk-publish" onclick="bulkAction('publish')">Publier la sélection</button>
|
|
||||||
<button type="button" class="btn-bulk-unpublish" onclick="bulkAction('unpublish')">Dépublier la sélection</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form id="bulk-form" method="post" action="publish.php" style="display: none;">
|
|
||||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
|
||||||
<input type="hidden" id="bulk-action-input" name="action" value="">
|
|
||||||
<input type="hidden" name="bulk" value="1">
|
|
||||||
<div id="bulk-checkboxes"></div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="stats">
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-number"><?php echo count($theses); ?></div>
|
|
||||||
<div class="stat-label">TFE total</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-number"><?php echo count(array_filter($theses, fn($t) => $t['is_published'])); ?></div>
|
|
||||||
<div class="stat-label">Publiés</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-number"><?php echo count(array_filter($theses, fn($t) => !$t['is_published'])); ?></div>
|
|
||||||
<div class="stat-label">En attente</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="filters">
|
|
||||||
<form method="get" action="list.php">
|
|
||||||
<fieldset>
|
|
||||||
<label for="search">Rechercher</label>
|
|
||||||
<input type="text" id="search" name="search" placeholder="Titre, auteur..." value="<?php echo htmlspecialchars($searchQuery); ?>">
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<label for="year">Année</label>
|
|
||||||
<select id="year" name="year">
|
|
||||||
<option value="">Toutes</option>
|
|
||||||
<?php foreach ($years as $year): ?>
|
|
||||||
<option value="<?php echo $year; ?>" <?php echo $yearFilter == $year ? 'selected' : ''; ?>>
|
|
||||||
<?php echo $year; ?>
|
|
||||||
</option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<label for="orientation">Orientation</label>
|
|
||||||
<select id="orientation" name="orientation">
|
|
||||||
<option value="">Toutes</option>
|
|
||||||
<?php foreach ($orientations as $orientation): ?>
|
|
||||||
<option value="<?php echo $orientation['id']; ?>" <?php echo $orientationFilter == $orientation['id'] ? 'selected' : ''; ?>>
|
|
||||||
<?php echo htmlspecialchars($orientation['name']); ?>
|
|
||||||
</option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<button type="submit">Filtrer</button>
|
|
||||||
<?php if ($searchQuery || $yearFilter || $orientationFilter): ?>
|
|
||||||
<a href="list.php">Réinitialiser</a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if (empty($theses)): ?>
|
|
||||||
<p>Aucun TFE trouvé.</p>
|
|
||||||
<?php else: ?>
|
|
||||||
<table class="thesis-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th><input type="checkbox" class="select-all-checkbox" onchange="toggleAll(this)" title="Tout sélectionner"></th>
|
|
||||||
<th>ID</th>
|
|
||||||
<th>Titre</th>
|
|
||||||
<th>Auteur(s)</th>
|
|
||||||
<th>Année</th>
|
|
||||||
<th>Orientation</th>
|
|
||||||
<th>AP</th>
|
|
||||||
<th>Statut</th>
|
|
||||||
<th>Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach ($theses as $thesis): ?>
|
|
||||||
<tr>
|
|
||||||
<td><input type="checkbox" class="select-checkbox" name="selected_theses[]" value="<?php echo $thesis['id']; ?>"></td>
|
|
||||||
<td><?php echo htmlspecialchars($thesis['identifier'] ?? $thesis['id']); ?></td>
|
|
||||||
<td>
|
|
||||||
<div class="thesis-title"><?php echo htmlspecialchars($thesis['title']); ?></div>
|
|
||||||
<?php if ($thesis['subtitle']): ?>
|
|
||||||
<div class="thesis-subtitle"><?php echo htmlspecialchars($thesis['subtitle']); ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
<td><?php echo htmlspecialchars($thesis['authors'] ?? 'N/A'); ?></td>
|
|
||||||
<td><?php echo $thesis['year']; ?></td>
|
|
||||||
<td><?php echo htmlspecialchars($thesis['orientation'] ?? 'N/A'); ?></td>
|
|
||||||
<td><?php echo htmlspecialchars($thesis['ap_program'] ?? 'N/A'); ?></td>
|
|
||||||
<td>
|
|
||||||
<?php if ($thesis['is_published']): ?>
|
|
||||||
<span class="status-badge status-published">Publié</span>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="status-badge status-pending">En attente</span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="actions">
|
|
||||||
<a href="thanks.php?id=<?php echo $thesis['id']; ?>" class="btn btn-view">Voir</a>
|
|
||||||
<a href="edit.php?id=<?php echo $thesis['id']; ?>" class="btn btn-edit">Éditer</a>
|
|
||||||
<form method="post" action="publish.php" class="publish-form">
|
|
||||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
|
||||||
<input type="hidden" name="thesis_id" value="<?php echo $thesis['id']; ?>">
|
|
||||||
<?php if ($thesis['is_published']): ?>
|
|
||||||
<input type="hidden" name="action" value="unpublish">
|
|
||||||
<button type="submit" class="btn btn-unpublish" onclick="return confirm('Retirer ce TFE de la publication ?');">Dépublier</button>
|
|
||||||
<?php else: ?>
|
|
||||||
<input type="hidden" name="action" value="publish">
|
|
||||||
<button type="submit" class="btn btn-publish">Publier</button>
|
|
||||||
<?php endif; ?>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<?php endif; ?>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<p>Post-ERG - <?php echo count($theses); ?> TFE dans la base de données</p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
180
assets/admin.css
180
assets/admin.css
@@ -0,0 +1,180 @@
|
|||||||
|
main {
|
||||||
|
margin: 1.2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters form {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters fieldset {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thesis-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thesis-table th,
|
||||||
|
.thesis-table td {
|
||||||
|
padding: 0.75rem;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thesis-table th {
|
||||||
|
background: #f0f0f0;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thesis-table tr:hover {
|
||||||
|
background: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thesis-title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thesis-subtitle {
|
||||||
|
font-style: italic;
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-pending {
|
||||||
|
background: #ffd700;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-published {
|
||||||
|
background: #90ee90;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 0.35rem 0.75rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.9em;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-view {
|
||||||
|
background: #4a90e2;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit {
|
||||||
|
background: #f39c12;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-publish {
|
||||||
|
background: #27ae60;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-unpublish {
|
||||||
|
background: #95a5a6;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.publish-form {
|
||||||
|
display: inline;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-number {
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #4a90e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-actions {
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-actions-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-bulk-publish {
|
||||||
|
background: #27ae60;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-bulk-unpublish {
|
||||||
|
background: #95a5a6;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-checkbox {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-all-checkbox {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,68 +1,53 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: police1;
|
font-family: police1;
|
||||||
src: url("./Combinedd.otf");
|
src: url("./fonts/Combinedd.otf");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark theme */
|
/* Dark theme */
|
||||||
/* UTILE POUR FORCER UN MODE LIGHT */
|
/* UTILE POUR FORCER UN MODE LIGHT */
|
||||||
@media (prefers-color-scheme: dark) {
|
/* @media (prefers-color-scheme: dark) { */
|
||||||
:root,
|
/* :root, */
|
||||||
::backdrop {
|
/* ::backdrop { */
|
||||||
--bg: #fff;
|
/* --bg: #fff; */
|
||||||
--accent-bg: #f5f7ff;
|
/* --accent-bg: #f5f7ff; */
|
||||||
--text: #212121;
|
/* --text: #212121; */
|
||||||
--text-light: #585858;
|
/* --text-light: #585858; */
|
||||||
--border: #898EA4;
|
/* --border: #898ea4; */
|
||||||
--accent: #0d47a1;
|
/* --accent: #0d47a1; */
|
||||||
--code: #d81b60;
|
/* --code: #d81b60; */
|
||||||
--preformatted: #444;
|
/* --preformatted: #444; */
|
||||||
--marked: #ffdd33;
|
/* --marked: #ffdd33; */
|
||||||
--disabled: #efefef;
|
/* --disabled: #efefef; */
|
||||||
}
|
/* } */
|
||||||
|
/* } */
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: white;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body{
|
/* FORMULAIRE */
|
||||||
background-color: white;
|
form label {
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ENTÊTE */
|
|
||||||
header {
|
|
||||||
font-family: 'police1';
|
|
||||||
background: linear-gradient(280deg, rgba(77, 168, 112, 1) 0%, rgba(193, 4, 252, 1) 85%);
|
|
||||||
text-decoration: none;
|
|
||||||
outline: none;
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
body > header h1 {
|
|
||||||
color: white;
|
|
||||||
margin: 3rem auto auto auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FORMULAIRE */
|
|
||||||
|
|
||||||
form label {
|
|
||||||
font-family: police1;
|
font-family: police1;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
form input,
|
form input,
|
||||||
select, textarea {
|
select,
|
||||||
|
textarea {
|
||||||
border-color: #c104fc;
|
border-color: #c104fc;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
outline: none;
|
outline: none;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
form input:focus,
|
form input:focus,
|
||||||
select:focus {
|
select:focus {
|
||||||
border: 3px solid rgba(77, 168, 112, 1);
|
border: 3px solid rgba(77, 168, 112, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
label{
|
label {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
/* font-family: police1; */
|
/* font-family: police1; */
|
||||||
@@ -70,34 +55,44 @@ input {
|
|||||||
background-color: none;
|
background-color: none;
|
||||||
color: rgb(193, 4, 252);
|
color: rgb(193, 4, 252);
|
||||||
border: 1px solid rgb(193, 4, 252);
|
border: 1px solid rgb(193, 4, 252);
|
||||||
}
|
}
|
||||||
|
|
||||||
a{
|
a {
|
||||||
color: rgb(193, 4, 252);
|
color: rgb(193, 4, 252);
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a, a:visited {
|
||||||
|
color: rgb(193, 4, 252);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:active {
|
||||||
|
border-color: rgba(77, 168, 112, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
[role="button"],
|
||||||
|
input[type="submit"],
|
||||||
|
input[type="reset"],
|
||||||
|
input[type="button"],
|
||||||
|
label[type="button"] {
|
||||||
|
background-color: white;
|
||||||
|
margin-top: 2rem;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 2ch;
|
||||||
|
margin: 1rem;
|
||||||
|
a {
|
||||||
|
color: black;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
a, a:visited {
|
}
|
||||||
color: rgb(193, 4, 252);
|
|
||||||
}
|
|
||||||
|
|
||||||
input:active {
|
button {}
|
||||||
border-color: rgba(77, 168, 112, 1);
|
|
||||||
|
|
||||||
}
|
/* For Google Chrome, Safari, and newer versions of Opera */
|
||||||
|
|
||||||
button, [role="button"], input[type="submit"], input[type="reset"], input[type="button"], label[type="button"] {
|
|
||||||
background-color: rgb(193, 4, 252);
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* For Google Chrome, Safari, and newer versions of Opera */
|
|
||||||
::placeholder {
|
::placeholder {
|
||||||
/* color: rgb(213, 73, 255); */
|
/* color: rgb(213, 73, 255); */
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
@@ -106,4 +101,5 @@ input {
|
|||||||
/* For Mozilla Firefox */
|
/* For Mozilla Firefox */
|
||||||
::-moz-placeholder {
|
::-moz-placeholder {
|
||||||
/* color: rgb(213, 73, 255); */
|
/* color: rgb(213, 73, 255); */
|
||||||
font-size: 0.8rem;}
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|||||||
101
assets/main.css
101
assets/main.css
@@ -0,0 +1,101 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
height: 20vh;
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
height: 60vh;
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
height: 20vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
header, main, footer {
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
font-family: "police1";
|
||||||
|
background: #9557b5ff;
|
||||||
|
color: white;
|
||||||
|
font-size: 2rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 6%;
|
||||||
|
padding: 1rem 4rem;
|
||||||
|
.title {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header section p:not(:first-child) {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .title, header section, header nav {
|
||||||
|
text-decoration: none;
|
||||||
|
outline: none;
|
||||||
|
font-size: 18px;
|
||||||
|
text-decoration: none;
|
||||||
|
line-height: 2.5rem;
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
height: 60vh;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: repeat(2, minmax(0, 1fr));
|
||||||
|
grid-auto-flow: column;
|
||||||
|
|
||||||
|
/* critical: force column width so new columns form */
|
||||||
|
grid-auto-columns: 260px;
|
||||||
|
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
background: #eee;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
background: #3c856bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
background: #222222ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .card { */
|
||||||
|
/* width: 20%; */
|
||||||
|
/* border: 1px solid white; */
|
||||||
|
/* color: white; */
|
||||||
|
/* margin: 1ch; */
|
||||||
|
/* padding: 2ch; */
|
||||||
|
/* } */
|
||||||
|
main {
|
||||||
|
scroll-snap-type: x mandatory;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
scroll-snap-align: start;
|
||||||
|
}
|
||||||
|
.item {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
<!-- footer.php -->
|
<!-- footer.php -->
|
||||||
<footer class="footer">
|
<footer>
|
||||||
<div class="content has-text-centered">
|
<div>
|
||||||
<p>Site fait avec ❤ en PHP, HTML, CSS fait mains.</p>
|
<button>Année</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button>Cursus</button>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -8,8 +8,9 @@
|
|||||||
<meta name="description" content="">
|
<meta name="description" content="">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Posterg</title>
|
<title>Posterg</title>
|
||||||
<link rel="stylesheet" href="assets/normalize.css">
|
<link rel="stylesheet" href="assets/modern-normalize.css">
|
||||||
<link rel="stylesheet" href="assets/posterg.css?v=2">
|
<link rel="stylesheet" href="assets/common.css">
|
||||||
|
<link rel="stylesheet" href="assets/main.css">
|
||||||
<?php if (getenv('PHP_ENV') === 'development' || php_sapi_name() === 'cli-server'): ?>
|
<?php if (getenv('PHP_ENV') === 'development' || php_sapi_name() === 'cli-server'): ?>
|
||||||
<!-- Live reload for development -->
|
<!-- Live reload for development -->
|
||||||
<script src="/vendor/php-live-reload/php-live-reload/live-reload.js"></script>
|
<script src="/vendor/php-live-reload/php-live-reload/live-reload.js"></script>
|
||||||
|
|||||||
19
index.php
19
index.php
@@ -3,6 +3,8 @@ ini_set("display_errors", 0);
|
|||||||
ini_set("log_errors", 1);
|
ini_set("log_errors", 1);
|
||||||
ini_set("error_log", "error.log");
|
ini_set("error_log", "error.log");
|
||||||
|
|
||||||
|
$pageTitle = "Liste des TFE";
|
||||||
|
|
||||||
require_once __DIR__ . "/lib/Database.php";
|
require_once __DIR__ . "/lib/Database.php";
|
||||||
|
|
||||||
$page = isset($_GET["page"]) ? intval($_GET["page"]) : 1;
|
$page = isset($_GET["page"]) ? intval($_GET["page"]) : 1;
|
||||||
@@ -20,15 +22,10 @@ try {
|
|||||||
$totalPages = 0;
|
$totalPages = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
include "inc/header.php";
|
include "inc/header.php"; ?>
|
||||||
?>
|
|
||||||
<main>
|
|
||||||
<section class="section">
|
|
||||||
<div class="container">
|
|
||||||
<div class="columns is-multiline">
|
|
||||||
|
|
||||||
|
<main>
|
||||||
<?php foreach ($itemsToLoad as $item): ?>
|
<?php foreach ($itemsToLoad as $item): ?>
|
||||||
<div class="column is-one-fifth">
|
|
||||||
<a href="memoire.php?id=<?= $item["id"] ?>" class="card-link">
|
<a href="memoire.php?id=<?= $item["id"] ?>" class="card-link">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<?php
|
<?php
|
||||||
@@ -84,12 +81,6 @@ include "inc/header.php";
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</main>
|
</main>
|
||||||
<?php include "inc/footer.php";
|
<?php include "inc/footer.php"; ?>
|
||||||
?>
|
|
||||||
23
justfile
23
justfile
@@ -65,6 +65,7 @@ deploy:
|
|||||||
@echo ""
|
@echo ""
|
||||||
@echo "Deploying public site..."
|
@echo "Deploying public site..."
|
||||||
rsync -vur --progress \
|
rsync -vur --progress \
|
||||||
|
--chown="root:posterg" \
|
||||||
--exclude 'vendor' \
|
--exclude 'vendor' \
|
||||||
--exclude 'tests' \
|
--exclude 'tests' \
|
||||||
--exclude 'test.db' \
|
--exclude 'test.db' \
|
||||||
@@ -72,8 +73,8 @@ deploy:
|
|||||||
--exclude 'cache' \
|
--exclude 'cache' \
|
||||||
--exclude '*.md' \
|
--exclude '*.md' \
|
||||||
--exclude '.git*' \
|
--exclude '.git*' \
|
||||||
|
--exclude '.jj' \
|
||||||
--exclude '.DS_Store' \
|
--exclude '.DS_Store' \
|
||||||
--exclude 'admin' \
|
|
||||||
--exclude 'database' \
|
--exclude 'database' \
|
||||||
--exclude 'nginx' \
|
--exclude 'nginx' \
|
||||||
--exclude 'docs' \
|
--exclude 'docs' \
|
||||||
@@ -82,17 +83,6 @@ deploy:
|
|||||||
--exclude 'setup-dev.sh' \
|
--exclude 'setup-dev.sh' \
|
||||||
./ posterg:/var/www/html/
|
./ posterg:/var/www/html/
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Deploying admin panel..."
|
|
||||||
rsync -vur --progress \
|
|
||||||
--exclude 'test.db' \
|
|
||||||
--exclude '*.db' \
|
|
||||||
--exclude 'cache' \
|
|
||||||
--exclude '*.md' \
|
|
||||||
./admin/ posterg:/var/www/html/admin/
|
|
||||||
@echo ""
|
|
||||||
@echo "Deploying shared libraries..."
|
|
||||||
rsync -vur --progress --exclude 'test.db' ./lib/ posterg:/var/www/html/lib/
|
|
||||||
@echo ""
|
|
||||||
@echo "Fixing permissions..."
|
@echo "Fixing permissions..."
|
||||||
ssh posterg "chgrp -R posterg /var/www/html/inc /var/www/html/lib && \
|
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/* && \
|
chmod 755 /var/www/html/inc && chmod 644 /var/www/html/inc/* && \
|
||||||
@@ -105,6 +95,15 @@ deploy:
|
|||||||
@echo " • Public: https://posterg.erg.be/"
|
@echo " • Public: https://posterg.erg.be/"
|
||||||
@echo " • Admin: https://posterg.erg.be/admin/"
|
@echo " • Admin: https://posterg.erg.be/admin/"
|
||||||
|
|
||||||
|
[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"
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Testing
|
# Testing
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
[1770317579]
|
[1770319036]
|
||||||
@@ -1 +1 @@
|
|||||||
[1770318922,1770318923,1770318924,1770318926,1770318930]
|
[1770322829]
|
||||||
15
memoire.php
15
memoire.php
@@ -32,13 +32,8 @@ if (isset($_GET['id'])) {
|
|||||||
|
|
||||||
// Include the header template
|
// Include the header template
|
||||||
include 'inc/header.php'; ?>
|
include 'inc/header.php'; ?>
|
||||||
|
<main>
|
||||||
<section class="section">
|
<div class="item">
|
||||||
<div class="container">
|
|
||||||
<div class="columns is-variable is-1-mobile is-0-tablet is-3-desktop is-8-widescreen is-2-fullhd">
|
|
||||||
<!-- INFO CARD -->
|
|
||||||
<div class="column is-one-third">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<!-- Display the title and author from the database -->
|
<!-- Display the title and author from the database -->
|
||||||
<h1 class="title">
|
<h1 class="title">
|
||||||
@@ -155,11 +150,7 @@ include 'inc/header.php'; ?>
|
|||||||
<p class="notification is-warning">Aucun fichier disponible pour ce mémoire.</p>
|
<p class="notification is-warning">Aucun fichier disponible pour ce mémoire.</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
</main>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Include the footer template -->
|
<!-- Include the footer template -->
|
||||||
<?php include 'inc/footer.php'; ?>
|
<?php include 'inc/footer.php'; ?>
|
||||||
Reference in New Issue
Block a user