From 95f52d549e850a577a9af451a36a3704092ceae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20Gervreau-Mercier?= Date: Tue, 27 Jan 2026 15:43:01 +0100 Subject: [PATCH] Add comprehensive thesis management system with database migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces a complete thesis management interface and migrates the system from YAML-based storage to SQLite: Core Changes: - Add Database.php helper class with PDO connection and entity management - Add list.php for viewing all theses with filtering and sorting - Add edit.php for modifying existing thesis records - Add import.php for migrating legacy YAML data to SQLite - Add justfile with development tasks (serve, init-test-db, etc.) Documentation: - Add MIGRATION.md with complete migration guide and architecture docs - Update README.md with database setup and Just recipe instructions - Update .gitignore to exclude test databases and error logs Modified Forms: - Enhanced formulaire.php with transaction-based SQLite processing - Updated index.php with database-driven form options - Improved thanks.php to read from database views The new architecture provides: - Normalized database schema (19 tables, 2 views) - Transaction safety and referential integrity - CRUD operations for thesis management - Filtering by year, orientation, AP program, publication status - Secure file handling with metadata tracking đŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .gitignore | 9 + db/Database_TFE_test.csv | 2 +- db/posterg.db | Bin 0 -> 204800 bytes formulaire/.gitignore | 31 +++ formulaire/Database.php | 277 ++++++++++++++++++++ formulaire/IMPORT.md | 153 +++++++++++ formulaire/MIGRATION.md | 357 ++++++++++++++++++++++++++ formulaire/README.md | 269 ++++++++++++++++++-- formulaire/edit.php | 323 ++++++++++++++++++++++++ formulaire/error.log | 501 +++++-------------------------------- formulaire/formulaire.php | 300 ++++++++++++++-------- formulaire/import.php | 366 +++++++++++++++++++++++++++ formulaire/index.php | 318 ++++++++++++++++++----- formulaire/justfile | 78 ++++++ formulaire/list.php | 295 ++++++++++++++++++++++ formulaire/thanks.php | 311 +++++++++++++++++++---- front-backend/Database.php | 111 ++++++++ front-backend/index.php | 66 +++-- front-backend/justfile | 53 ++++ front-backend/memoire.php | 124 ++++++--- front-backend/test_db.php | 40 +++ justfile | 4 +- 22 files changed, 3263 insertions(+), 725 deletions(-) create mode 100644 db/posterg.db create mode 100644 formulaire/.gitignore create mode 100644 formulaire/Database.php create mode 100644 formulaire/IMPORT.md create mode 100644 formulaire/MIGRATION.md create mode 100644 formulaire/edit.php create mode 100644 formulaire/import.php create mode 100644 formulaire/justfile create mode 100644 formulaire/list.php create mode 100644 front-backend/Database.php create mode 100644 front-backend/justfile create mode 100644 front-backend/test_db.php diff --git a/.gitignore b/.gitignore index 903271d..ea1d52f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,21 @@ vendor/ compose.lock +### Test databases ### +formulaire/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 ### +formulaire/error.log + diff --git a/db/Database_TFE_test.csv b/db/Database_TFE_test.csv index 4335581..c48200d 100644 --- a/db/Database_TFE_test.csv +++ b/db/Database_TFE_test.csv @@ -33,7 +33,7 @@ Nous vivons une Ă©pidĂ©mie invisible : l’inflammation chronique des systĂšmes Je travaille en auto-ethnographie incarnĂ©e, depuis des expĂ©riences qui dĂ©bordent le discours : douleurs chroniques, gestes d’auto-dĂ©fense somatique, apprentissages empiriques. Je croise pratiques somatiques (TRE¼—tremblement neurogĂ©nique guidĂ©, Body-Mind CenteringÂź, contact-impro) et enquĂȘtes thĂ©oriques (traumatologie, Ă©cologies affectives), pour Ă©laborer une recherche-crĂ©ation qui pense avec le mouvement et par le toucher. Ce cadre a donnĂ© naissance Ă  une mĂ©thode en devenir : la DarkDance—un protocole souple, subversif et sans dogme, qui considĂšre muscles, fascias, organes et liquides comme des milieux pensants, et les engage dans une politique de la sensation. Les enjeux sont de dĂ©placer la critique des systĂšmes de domination dans les tissus mĂȘmes des corps (cartographies somatiques du pouvoir). Et d’ouvrir des espaces de re(co)naissance pour celles et ceux dont l’archive est manquante—« les corps qui ne comptent pas ».",,,français,,,99 pages,, -2025-008,"L'Ă©cole, le parlement et la cuisine",,Alice NĂ©ron,alice.neron@outlook.com,Ayoh KrĂ© DuchĂątelet et Karolina Svobodova,objet Ă©ditorial,2025,,DN,Approfondi,"art participatif, assemblĂ©e, bruxelles, joie, conversation, affects, collectif, pouvoir d'agir","L’école, le parlement et la cuisine revient sur trois situations d’art participatif engagĂ©es Ă  Bruxelles ces cinq derniĂšres annĂ©es à partir de mon expĂ©rience: Bodies of Knowledge, le Code du numĂ©rique et «Cuisiner (...)». Bodies of Knowledge («L’école»), de l’artiste performeuse Sarah Vanhee, est une «salle de classe» nomade installĂ©e dans l'espace public pour apprendre des savoirs de vie des habitant·es. Le Code du NumĂ©rique («le parlement»),portĂ© par l’asbl reconnue en Ă©ducation permanente Habitant·es des images, est un faux vrai code de loi Ă©crit Ă  partir de tĂ©moignages pour rĂ©glementer le numĂ©rique. Il s'Ă©crit pendant les «parlements humains»: un outil d'animation qui met en scĂšne une assemblĂ©e lĂ©gislative. Les ateliers «Cuisiner (...)» organisĂ©s avec Zeste Le Reste visent Ă  cuisiner des problĂšmes collectifs en mĂȘme temps qu’un repas. +2025-008,"L'Ă©cole, le parlement et la cuisine",,Alice NĂ©ron,alice.neron@outlook.com,"Ayoh KrĂ© DuchĂątelet,Karolina Svobodova",objet Ă©ditorial,2025,,DN,Approfondi,"art participatif, assemblĂ©e, bruxelles, joie, conversation, affects, collectif, pouvoir d'agir","L’école, le parlement et la cuisine revient sur trois situations d’art participatif engagĂ©es Ă  Bruxelles ces cinq derniĂšres annĂ©es à partir de mon expĂ©rience: Bodies of Knowledge, le Code du numĂ©rique et «Cuisiner (...)». Bodies of Knowledge («L’école»), de l’artiste performeuse Sarah Vanhee, est une «salle de classe» nomade installĂ©e dans l'espace public pour apprendre des savoirs de vie des habitant·es. Le Code du NumĂ©rique («le parlement»),portĂ© par l’asbl reconnue en Ă©ducation permanente Habitant·es des images, est un faux vrai code de loi Ă©crit Ă  partir de tĂ©moignages pour rĂ©glementer le numĂ©rique. Il s'Ă©crit pendant les «parlements humains»: un outil d'animation qui met en scĂšne une assemblĂ©e lĂ©gislative. Les ateliers «Cuisiner (...)» organisĂ©s avec Zeste Le Reste visent Ă  cuisiner des problĂšmes collectifs en mĂȘme temps qu’un repas. Ce mĂ©moire questionne les potentiels transformateurs de ces trois dispositifs. Comment peuvent-ils participer ou non Ă  nous faire sentir plus capables ensemble et Ă  faire Ă©merger des capacitĂ©s de sentir, de penser et d’agir en commun ? Comment peuvent-ils, ou non, nous permettre (artistes et participant·es) de reprendre joyeusement prise sur des affects tristes ? Que nous font-ils faire ? ",,,français,,,160 pages en plusieurs brochures,, diff --git a/db/posterg.db b/db/posterg.db new file mode 100644 index 0000000000000000000000000000000000000000..e2bc5e5dcf30c785ec3e6133a48c0a2ee36099b2 GIT binary patch literal 204800 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 = $testDb; + } else { + // Default to parent directory's db folder (production) + $this->dbPath = __DIR__ . '/../db/posterg.db'; + } + } else { + $this->dbPath = $dbPath; + } + + try { + $this->pdo = new PDO('sqlite:' . $this->dbPath); + $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + + // Enable foreign key constraints + $this->pdo->exec('PRAGMA foreign_keys = ON'); + } catch (PDOException $e) { + error_log("Database connection failed: " . $e->getMessage()); + throw new Exception("Impossible de se connecter Ă  la base de donnĂ©es."); + } + } + + /** + * Get PDO instance for direct queries if needed + * @return PDO + */ + public function getPDO() { + return $this->pdo; + } + + /** + * Begin a transaction + */ + public function beginTransaction() { + return $this->pdo->beginTransaction(); + } + + /** + * Commit a transaction + */ + public function commit() { + return $this->pdo->commit(); + } + + /** + * Rollback a transaction + */ + public function rollback() { + return $this->pdo->rollback(); + } + + /** + * Find or create an author + * @param string $name Author name + * @param string $email Author email (optional) + * @return int Author ID + */ + public function findOrCreateAuthor($name, $email = null) { + // Try to find existing author by name + $stmt = $this->pdo->prepare("SELECT id FROM authors WHERE name = ?"); + $stmt->execute([$name]); + $author = $stmt->fetch(); + + if ($author) { + // Update email if provided and different + if ($email && $email !== '') { + $updateStmt = $this->pdo->prepare("UPDATE authors SET email = ? WHERE id = ?"); + $updateStmt->execute([$email, $author['id']]); + } + return $author['id']; + } + + // Create new author + $stmt = $this->pdo->prepare("INSERT INTO authors (name, email) VALUES (?, ?)"); + $stmt->execute([$name, $email]); + return $this->pdo->lastInsertId(); + } + + /** + * Find or create a supervisor + * @param string $name Supervisor name + * @return int Supervisor ID + */ + public function findOrCreateSupervisor($name) { + // Try to find existing supervisor + $stmt = $this->pdo->prepare("SELECT id FROM supervisors WHERE name = ?"); + $stmt->execute([$name]); + $supervisor = $stmt->fetch(); + + if ($supervisor) { + return $supervisor['id']; + } + + // Create new supervisor + $stmt = $this->pdo->prepare("INSERT INTO supervisors (name) VALUES (?)"); + $stmt->execute([$name]); + return $this->pdo->lastInsertId(); + } + + /** + * Find or create a keyword + * @param string $keyword Keyword text + * @return int Keyword ID + */ + public function findOrCreateKeyword($keyword) { + $keyword = trim($keyword); + if (empty($keyword)) { + return null; + } + + // Try to find existing keyword + $stmt = $this->pdo->prepare("SELECT id FROM keywords WHERE keyword = ?"); + $stmt->execute([$keyword]); + $kw = $stmt->fetch(); + + if ($kw) { + return $kw['id']; + } + + // Create new keyword + $stmt = $this->pdo->prepare("INSERT INTO keywords (keyword) VALUES (?)"); + $stmt->execute([$keyword]); + return $this->pdo->lastInsertId(); + } + + /** + * Get orientation ID by name + * @param string $name Orientation name + * @return int|null Orientation ID or null if not found + */ + public function getOrientationId($name) { + $stmt = $this->pdo->prepare("SELECT id FROM orientations WHERE name = ?"); + $stmt->execute([$name]); + $result = $stmt->fetch(); + return $result ? $result['id'] : null; + } + + /** + * Get AP program ID by name + * @param string $name AP program name + * @return int|null AP program ID or null if not found + */ + public function getAPProgramId($name) { + $stmt = $this->pdo->prepare("SELECT id FROM ap_programs WHERE name = ?"); + $stmt->execute([$name]); + $result = $stmt->fetch(); + return $result ? $result['id'] : null; + } + + /** + * Get finality type ID by name + * @param string $name Finality type name + * @return int|null Finality type ID or null if not found + */ + public function getFinalityId($name) { + $stmt = $this->pdo->prepare("SELECT id FROM finality_types WHERE name = ?"); + $stmt->execute([$name]); + $result = $stmt->fetch(); + return $result ? $result['id'] : null; + } + + /** + * Get language ID by name + * @param string $name Language name + * @return int|null Language ID or null if not found + */ + public function getLanguageId($name) { + $stmt = $this->pdo->prepare("SELECT id FROM languages WHERE name = ?"); + $stmt->execute([$name]); + $result = $stmt->fetch(); + return $result ? $result['id'] : null; + } + + /** + * Get format type ID by name + * @param string $name Format type name + * @return int|null Format type ID or null if not found + */ + public function getFormatId($name) { + $stmt = $this->pdo->prepare("SELECT id FROM format_types WHERE name = ?"); + $stmt->execute([$name]); + $result = $stmt->fetch(); + return $result ? $result['id'] : null; + } + + /** + * Get all orientations + * @return array Array of orientations + */ + public function getAllOrientations() { + $stmt = $this->pdo->query("SELECT id, name FROM orientations ORDER BY name"); + return $stmt->fetchAll(); + } + + /** + * Get all AP programs + * @return array Array of AP programs + */ + public function getAllAPPrograms() { + $stmt = $this->pdo->query("SELECT id, name, code FROM ap_programs ORDER BY name"); + return $stmt->fetchAll(); + } + + /** + * Get all finality types + * @return array Array of finality types + */ + public function getAllFinalityTypes() { + $stmt = $this->pdo->query("SELECT id, name FROM finality_types ORDER BY name"); + return $stmt->fetchAll(); + } + + /** + * Get all languages + * @return array Array of languages + */ + public function getAllLanguages() { + $stmt = $this->pdo->query("SELECT id, name FROM languages ORDER BY name"); + return $stmt->fetchAll(); + } + + /** + * Get all format types + * @return array Array of format types + */ + public function getAllFormatTypes() { + $stmt = $this->pdo->query("SELECT id, name FROM format_types ORDER BY name"); + return $stmt->fetchAll(); + } + + /** + * Get thesis by ID + * @param int $id Thesis ID + * @return array|null Thesis data or null if not found + */ + public function getThesis($id) { + $stmt = $this->pdo->prepare("SELECT * FROM v_theses_full WHERE id = ?"); + $stmt->execute([$id]); + return $stmt->fetch(); + } + + /** + * Insert a thesis file record + * @param int $thesisId Thesis ID + * @param string $fileType File type ('main', 'annex', 'written_part', 'other') + * @param string $filePath Server path to file + * @param string $fileName Original filename + * @param int $fileSize File size in bytes + * @param string $mimeType MIME type + * @return int File ID + */ + public function insertThesisFile($thesisId, $fileType, $filePath, $fileName, $fileSize, $mimeType) { + $stmt = $this->pdo->prepare(" + INSERT INTO thesis_files (thesis_id, file_type, file_path, file_name, file_size, mime_type) + VALUES (?, ?, ?, ?, ?, ?) + "); + $stmt->execute([$thesisId, $fileType, $filePath, $fileName, $fileSize, $mimeType]); + return $this->pdo->lastInsertId(); + } +} diff --git a/formulaire/IMPORT.md b/formulaire/IMPORT.md new file mode 100644 index 0000000..0557d95 --- /dev/null +++ b/formulaire/IMPORT.md @@ -0,0 +1,153 @@ +# CSV Import Format Specification + +## File Format + +- **Encoding**: UTF-8 +- **Delimiter**: Comma (`,`) +- **Header Rows**: First 4 rows are skipped during import + - Row 1: Empty + - Row 2: Headers (French labels) + - Row 3: Description row + - Row 4: Column names +- **Data Rows**: Start from row 5 onwards + +## Column Structure + +The CSV must contain exactly 21 columns in this order: + +| Index | Field Name | Required | Type | Description | +|-------|------------|----------|------|-------------| +| 0 | identifier | No | String | Unique identifier for the thesis | +| 1 | title | **Yes** | String | Thesis title | +| 2 | subtitle | No | String | Thesis subtitle | +| 3 | authors | No | String | Author(s), comma-separated for multiple | +| 4 | contact | No | String | Contact email (associated with first author) | +| 5 | supervisors | No | String | Supervisor(s), comma-separated for multiple | +| 6 | formats | No | String | Format(s), comma-separated for multiple | +| 7 | year | **Yes** | Integer | Year of thesis (e.g., 2024) | +| 8 | ap | No | String | AP program code (see AP Codes section) | +| 9 | orientation | No | String | Orientation code (see Orientation Codes section) | +| 10 | finality | No | String | Finality name | +| 11 | keywords | No | String | Keywords, comma-separated (max 10) | +| 12 | synopsis | No | Text | Synopsis/abstract of the thesis | +| 13 | context | No | Text | Context note | +| 14 | remarks | No | Text | Additional remarks | +| 15 | language | No | String | Language (e.g., Français, English, Nederlands) | +| 16 | access | No | String | Access authorization | +| 17 | license | No | String | License information | +| 18 | size_info | No | String | File size information | +| 19 | jury_points | No | Float | Jury score (out of 20) | +| 20 | baiu_link | No | String | Link to BAIU (institutional archive) | + +## Field Details + +### Required Fields +- **title**: Must not be empty +- **year**: Must not be empty and must be a valid integer + +### Multi-Value Fields +These fields accept multiple values separated by commas: +- **authors**: e.g., `"John Doe, Jane Smith"` +- **supervisors**: e.g., `"Prof. A, Prof. B"` +- **keywords**: Maximum 10 keywords, e.g., `"art, design, digital"` +- **formats**: e.g., `"PDF, Video, Installation"` + +### Orientation Codes +Valid orientation codes and their full names: + +``` +SC = Sculpture +VI = VidĂ©ographie +CA = CinĂ©ma d'animation +IP = Installation-Performance +PE = Peinture +PH = Photographie +DE = Dessin +AN = Arts NumĂ©riques +GR = Graphisme +TY = Typographie +DN = Design NumĂ©rique +IL = Illustration +BD = Bande-DessinĂ©e +SE = SĂ©rigraphie +GV = Gravure +``` + +### AP Codes +Valid AP program codes: +- `DPM` +- `LIENS` +- `APS` + +(These codes must match exactly what exists in the `ap_programs` table) + +### Language Values +Languages should be provided with capital first letter: +- `Français` +- `English` +- `Nederlands` +- etc. + +### Format Values +Common format values (case-insensitive, will be normalized): +- `PDF` +- `Video` +- `Audio` +- `Installation` +- `Web` +- etc. + +## Import Behavior + +### Row Processing +1. Empty rows (no title and no identifier) are skipped +2. Each row is processed in a transaction +3. If a row fails, it is skipped and logged, but processing continues + +### Data Validation +- If title or year is missing, the row is rejected +- Invalid orientation codes result in no orientation being set (null) +- Invalid AP codes result in no AP program being set (null) +- Keywords are limited to first 10 if more are provided + +### Data Normalization +- All string fields are trimmed of whitespace +- Language and format values are normalized (first letter capitalized, rest lowercase) +- Empty strings are converted to NULL in the database + +### Entity Creation +- Authors, supervisors, and keywords are automatically created if they don't exist +- Existing authors are matched by name +- Contact email is only associated with the first author + +## Example CSV Structure + +```csv + +Identifiant,Titre,Sous-titre,Auteur·ice(s),Contact,Promoteur·ice(s),Format,AnnĂ©e,AP,Orientation,FinalitĂ©,Mots-clĂ©s,Synopsis,Contexte,Remarques,Langue,Autorisation,License,taille,Points sur 20,lien BAIU + +TFE-2024-001,Mon projet artistique,Exploration du numĂ©rique,"Alice Dupont, Bob Martin",alice@example.com,Prof. Smith,PDF,2024,DPM,AN,CrĂ©ation,art numĂ©rique,digital art,interactive installation,Un projet explorant l'intersection de l'art et de la technologie,RĂ©alisĂ© dans le cadre du master,TrĂšs bon projet,Français,Public,CC-BY,250MB,16.5,https://baiu.example.org/12345 +TFE-2024-002,Design graphique moderne,,Charlie Brown,charlie@example.com,"Prof. A, Prof. B","PDF, Print",2024,LIENS,GR,Design,typographie,graphisme,design,Une exploration de la typographie contemporaine,,,English,Restricted,All rights reserved,50MB,15, +``` + +## Troubleshooting + +### Common Issues +1. **Encoding problems**: Ensure file is saved as UTF-8 +2. **Missing columns**: All 21 columns must be present, even if empty +3. **Line breaks in fields**: Ensure fields containing newlines are properly quoted +4. **Quote escaping**: Use double quotes (`""`) to escape quotes within fields + +### Import Results +After import, the system will display: +- Number of theses successfully imported +- Number of rows skipped due to errors +- Detailed line-by-line results with success (✓) or error (✗) indicators + +## Notes + +- The import process preserves the order of authors, supervisors, and keywords +- The first author gets the contact email if provided +- Duplicate detection is not performed - each import creates new entries +- Failed rows do not stop the import process +- All errors are logged to the server error log diff --git a/formulaire/MIGRATION.md b/formulaire/MIGRATION.md new file mode 100644 index 0000000..6ec85e1 --- /dev/null +++ b/formulaire/MIGRATION.md @@ -0,0 +1,357 @@ +# Migration from YAML to SQLite + +## Overview + +The Post-ERG thesis submission form has been completely overhauled to use a SQLite database instead of flat YAML files. This provides better data integrity, querying capabilities, and prepares the system for a full-featured web application. + +## What Changed + +### Database Implementation + +**Before:** Form data was saved as individual YAML files in `data/yaml/`, with file uploads scattered in `data/content/` and `data/cover/`. + +**After:** All thesis data is now stored in a relational SQLite database (`../db/posterg.db`) with proper normalization and foreign key relationships. + +### New Architecture + +``` +Form Submission Flow: +1. User fills out enhanced form (index.php) +2. Form validates input and begins database transaction +3. Creates/links: author, thesis, supervisors, keywords, languages, formats +4. Uploads files with random names for security +5. Records file metadata in database +6. Commits transaction (all-or-nothing) +7. Redirects to confirmation page showing database data +``` + +### Database Schema Highlights + +- **19 tables** including junction tables and views +- **Normalized structure** (3rd Normal Form) +- **Automatic timestamps** via triggers +- **Cascade deletes** for referential integrity +- **Predefined lookup tables** for orientations, AP programs, finalities, etc. +- **Views** for simplified querying (v_theses_full, v_theses_public) + +## New Files + +### `Database.php` +Database helper class providing: +- PDO connection with error handling +- Transaction management +- Find-or-create methods for entities +- Prepared statement helpers +- Lookup methods for all reference data + +**Key Methods:** +```php +$db = new Database(); +$authorId = $db->findOrCreateAuthor($name, $email); +$keywordId = $db->findOrCreateKeyword($keyword); +$orientations = $db->getAllOrientations(); +$thesis = $db->getThesis($id); +``` + +## Modified Files + +### `index.php` +**Enhancements:** +- Dynamically loads form options from database +- Added required fields per schema: + - Subtitle (optional) + - Synopsis (~200 words, required) + - Finality (Approfondi/Enseignement/SpĂ©cialisĂ©) + - Languages (multiple selection with checkboxes) + - Formats (multiple selection with checkboxes) +- Better form organization with sections +- Improved accessibility (proper labels, IDs) + +**New Form Fields:** +| Field | Type | Required | Notes | +|-------|------|----------|-------| +| Subtitle | Text | No | New field | +| Synopsis | Textarea | Yes | ~200 words | +| Finality | Select | Yes | From finality_types table | +| Languages | Checkboxes | Yes | Multiple selection | +| Formats | Checkboxes | No | Multiple selection | + +### `formulaire.php` +**Complete rewrite** with: + +1. **Transaction-Based Processing:** + - `BEGIN TRANSACTION` at start + - All insertions in single transaction + - `COMMIT` on success or `ROLLBACK` on error + - Ensures data consistency + +2. **Prepared Statements:** + - All SQL queries use PDO prepared statements + - Protection against SQL injection + - Parameter binding for all user input + +3. **Entity Creation:** + - Finds or creates authors (by name) + - Finds or creates supervisors (by name) + - Finds or creates keywords (by text) + - Links all entities via junction tables + +4. **Identifier Generation:** + - Format: `YYYY-NNN` (e.g., "2026-001") + - Automatically increments per year + - Unique constraint in database + +5. **File Handling:** + - Random cryptographic filenames (32 hex chars) + - Organized by year and identifier: `data/theses/YYYY/YYYY-NNN/` + - Cover images separate: `data/covers/` + - Metadata stored in `thesis_files` table + +6. **Validation:** + - Year range: 2000 to current year + 1 + - Max 10 keywords enforced + - At least one language required + - URL format validation + - File type and size validation + +### `thanks.php` +**Complete redesign:** + +- Reads from database using thesis ID +- Displays data from `v_theses_full` view +- Shows all relationships: authors, supervisors, keywords, languages, formats +- Lists uploaded files with metadata (type, size, date) +- Responsive CSS grid layout +- Publication status indicator + +**Security:** +- Validates thesis ID (integer only) +- Uses prepared statements +- No path traversal vulnerability +- Error messages don't expose system details + +## Database Files + +### `../db/posterg.db` +Initialized SQLite database with: +- 19 tables (11 core, 5 junction, 3 reference) +- 2 views (v_theses_full, v_theses_public) +- Predefined data: + - 15 orientations + - 4 AP programs + - 3 finality types + - 2 languages (French, English) + - 7 format types + - 3 access types + - 4 static pages + +### Schema Documentation +See `../db/README.md` and `../db/SETUP.md` for complete documentation. + +## Security Improvements Retained + +All security improvements from the previous commit are preserved: + +✅ CSRF protection with session tokens +✅ Input validation and sanitization +✅ Prepared statements (SQL injection protection) +✅ Random filenames for uploads +✅ File type and size validation +✅ MIME type checking +✅ Error logging without exposing paths +✅ Path traversal protection + +## Data Mapping + +### YAML to Database Mapping + +| Old YAML Field | New Database Location | Notes | +|----------------|----------------------|-------| +| `auteurice` | `authors.name` | Normalized, reusable | +| `email` | `authors.email` | Now in authors table | +| `annĂ©e` | `theses.year` | Integer field | +| `titre` | `theses.title` | Required | +| - | `theses.subtitle` | New field | +| `description` | `theses.synopsis` | Renamed for clarity | +| `problĂ©matique` | (not yet used) | Can be added to schema | +| `orientation` | `theses.orientation_id` | Foreign key to orientations | +| `ap` | `theses.ap_program_id` | Foreign key to ap_programs | +| - | `theses.finality_id` | New field (required) | +| `promoteurice` | `supervisors.name` + `thesis_supervisors` | Many-to-many | +| `tag` | `keywords.keyword` + `thesis_keywords` | Many-to-many, max 10 | +| `lien` | `theses.baiu_link` | URL validation | +| `files` | `thesis_files` table | Full metadata | +| `couverture` | (stored as file, not in DB yet) | Could add cover_path column | + +## Migration Path for Existing Data + +If you have existing YAML files to import: + +1. **Parse YAML files:** + ```php + $yamlFiles = glob('data/yaml/*.yaml'); + foreach ($yamlFiles as $file) { + $data = Yaml::parseFile($file); + // ... + } + ``` + +2. **Insert into database:** + ```php + $db->beginTransaction(); + try { + $authorId = $db->findOrCreateAuthor($data['auteurice'], $data['email']); + // Insert thesis + // Link relationships + $db->commit(); + } catch (Exception $e) { + $db->rollback(); + } + ``` + +3. **Verify data:** + ```sql + SELECT COUNT(*) FROM theses; + SELECT * FROM v_theses_full LIMIT 5; + ``` + +## Testing Checklist + +Before production deployment: + +- [ ] Form loads without errors +- [ ] All dropdown options populate from database +- [ ] Form submission creates thesis record +- [ ] Author is created or found correctly +- [ ] Supervisors linked properly +- [ ] Keywords created and linked (test max 10) +- [ ] Languages required (test validation) +- [ ] Formats optional (test multiple selection) +- [ ] Files upload successfully +- [ ] File metadata recorded in database +- [ ] Thanks page displays all data correctly +- [ ] Transaction rollback works on error +- [ ] CSRF token validated +- [ ] Invalid data rejected (year, URL, etc.) + +## Known Limitations + +1. **No cover_path column:** Cover images uploaded but path not stored in `theses` table (can be added) +2. **No problĂ©matique field:** Old field not yet in schema (can be added to `theses.remarks` or new column) +3. **File type detection:** Basic (by extension), could be enhanced +4. **No duplicate detection:** Same thesis can be submitted multiple times +5. **No edit capability:** Once submitted, no UI to edit (admin interface needed) + +## Next Steps + +1. **Initialize production database:** + ```bash + cd /path/to/production/db + sqlite3 posterg.db < schema.sql + ``` + +2. **Set permissions:** + ```bash + chmod 644 posterg.db + chown www-data:www-data posterg.db + ``` + +3. **Test form submission:** + - Submit test thesis + - Verify all fields saved + - Check file uploads + - Test thanks page + +4. **Import existing data:** + - Create migration script + - Parse old YAML files + - Bulk insert into database + - Verify integrity + +5. **Build admin interface:** + - CRUD operations for theses + - User management + - Approval workflow + - Bulk operations + +6. **Build public website:** + - Search and filter theses + - Respect access controls + - Display thesis details + - Static pages management + +## Compatibility Notes + +### PHP Requirements +- PHP 7.4+ (tested on PHP 8.x) +- PDO extension with SQLite support +- Composer for Symfony YAML (still used for potential migration) + +### Database +- SQLite 3.8.0+ +- File-based database (no server needed) +- Single file: `db/posterg.db` + +### Dependencies +```json +{ + "require": { + "symfony/yaml": "^6.2", + "behat/transliterator": "^1.5" + } +} +``` + +Note: YAML library retained for potential data migration from old files. + +## Backup Strategy + +SQLite database is a single file - easy to backup: + +```bash +# Simple copy +cp db/posterg.db db/backups/posterg_$(date +%Y%m%d).db + +# SQL dump (portable) +sqlite3 db/posterg.db .dump > backups/posterg_$(date +%Y%m%d).sql + +# Compressed backup +tar -czf backups/posterg_$(date +%Y%m%d).tar.gz db/posterg.db data/ +``` + +Set up automated daily backups via cron. + +## Performance Considerations + +- **Indexes:** All critical foreign keys and search fields indexed +- **Views:** Pre-computed joins for common queries +- **Transactions:** Ensure atomicity without locking issues +- **File I/O:** Random filenames prevent directory listing overhead + +For large datasets (1000+ theses): +- Consider WAL mode: `PRAGMA journal_mode=WAL;` +- Optimize with `ANALYZE;` periodically +- Monitor database size and `VACUUM` if needed + +## Rollback Plan + +If issues arise, you can roll back to YAML-based system: + +1. Use previous jj commit: `jj checkout ` +2. Old YAML files in `data/yaml/` still intact +3. Database changes don't affect old YAML code +4. Can run both systems in parallel during transition + +## Support + +For questions or issues: +- Schema documentation: `db/README.md` +- Setup guide: `db/SETUP.md` +- Security details: `SECURITY.md` +- Technical specs: `db/posterg_fiche-technique.md` + +--- + +**Migration completed:** 2026-01-27 +**Database version:** 1.0 +**Form version:** 2.0 (SQLite) diff --git a/formulaire/README.md b/formulaire/README.md index 7e424f9..ce085fe 100644 --- a/formulaire/README.md +++ b/formulaire/README.md @@ -4,37 +4,274 @@ Le formulaire permet aux Ă©tudiant.e.s sortant de l'ERG en cursus de Master de s ## FonctionnalitĂ©s -- Soumission de mĂ©moires avec mĂ©tadonnĂ©es -- Sauvegarde des mĂ©tadonnĂ©es en format YAML -- TĂ©lĂ©versement des fichiers sur le serveur interne de l'Ă©cole +- Soumission de mĂ©moires avec mĂ©tadonnĂ©es complĂštes +- Stockage structurĂ© dans base de donnĂ©es SQLite +- Support multi-auteurs, multi-superviseurs, multi-langues +- Gestion des mots-clĂ©s (max 10 par TFE) +- TĂ©lĂ©versement sĂ©curisĂ© des fichiers +- Protection CSRF et validation complĂšte +- Workflow de publication (soumission → soutenance → publication) ## Technologies -- PHP +- PHP 7.4+ avec PDO SQLite +- SQLite 3.8+ - CSS fait-main + [Simple.css](https://simplecss.org/) -- [Symfony YAML](https://symfony.com/doc/current/components/yaml.html) pour le traitement des fichiers YAML +- [Symfony YAML](https://symfony.com/doc/current/components/yaml.html) (pour migration legacy) +- [Just](https://github.com/casey/just) pour les tĂąches de dĂ©veloppement ## Installation -```shell +### PrĂ©requis + +```bash +# PHP avec SQLite +php -v # 7.4 ou supĂ©rieur +php -m | grep sqlite # VĂ©rifier extension SQLite + +# Composer composer install + +# Just (optionnel mais recommandĂ©) +# macOS: brew install just +# Linux: cargo install just ``` -## Lancement +### Configuration -```shell +1. **Base de donnĂ©es production:** + ```bash + cd ../db + sqlite3 posterg.db < schema.sql + ``` + +2. **Base de donnĂ©es de test:** + ```bash + just init-test-db + ``` + +## DĂ©veloppement local + +### Avec Just (recommandĂ©) + +```bash +# Configuration complĂšte et lancement du serveur +just dev + +# Ou Ă©tape par Ă©tape: +just init-test-db # CrĂ©er la base de test +just serve # Lancer le serveur (rĂ©initialise la DB) +just serve-only # Lancer sans rĂ©initialiser + +# Nettoyage +just cleanup # Supprimer test.db et fichiers uploadĂ©s +just reset # Cleanup + rĂ©initialisation + +# Statistiques +just stats # Voir les stats de la DB +just recent # Voir les soumissions rĂ©centes +just show 1 # Voir le TFE #1 + +# Autres commandes +just query # Shell SQLite interactif +just dump # Backup de la DB +``` + +### Sans Just + +```bash +# CrĂ©er la base de test +sqlite3 test.db < ../db/schema.sql + +# Lancer le serveur php -S 127.0.0.1:3000 + +# Ouvrir dans le navigateur +open http://127.0.0.1:3000 ``` -Puis ouvrir [127.0.0.1:3000](http://127.0.0.1:3000) dans votre navigateur. - -## Structure +## Structure du projet ``` formulaire/ -├── assets/ # Fichiers CSS et ressources -├── data/ # DonnĂ©es stockĂ©es (YAML, fichiers) -├── formulaire.php # Page principale du formulaire -├── index.php # Point d'entrĂ©e -└── thanks.php # Page de confirmation +├── assets/ # CSS et ressources +│ ├── normalize.css +│ ├── simple.css +│ ├── posterg.css +│ └── icon.svg +├── data/ # DonnĂ©es (gitignored) +│ ├── theses/ # Fichiers TFE uploadĂ©s +│ ├── covers/ # Images de couverture +│ └── yaml/ # Legacy YAML (migration) +├── Database.php # Classe helper pour DB +├── index.php # Formulaire de soumission +├── formulaire.php # Traitement de soumission +├── thanks.php # Page de confirmation +├── justfile # TĂąches de dĂ©veloppement +├── .gitignore # Fichiers ignorĂ©s +├── MIGRATION.md # Guide de migration YAML → SQLite +├── SECURITY.md # Documentation sĂ©curitĂ© +└── README.md # Ce fichier ``` + +## Workflow de soumission + +1. **Étudiant remplit le formulaire** (index.php) + - Informations de base (nom, annĂ©e, titre) + - DĂ©tails acadĂ©miques (orientation, AP, finalitĂ©) + - Contenu (synopsis, mots-clĂ©s, langues, formats) + - Upload fichiers (TFE + annexes) + +2. **Validation et traitement** (formulaire.php) + - Validation CSRF token + - Sanitization des entrĂ©es + - Transaction DB (all-or-nothing) + - CrĂ©ation/liaison entitĂ©s (auteur, superviseurs, mots-clĂ©s) + - Upload sĂ©curisĂ© avec noms alĂ©atoires + - GĂ©nĂ©ration identifiant unique (YYYY-NNN) + +3. **Confirmation** (thanks.php) + - Affichage rĂ©capitulatif + - Statut: "En attente de publication" + - Liste des fichiers uploadĂ©s + +4. **Publication** (admin - Ă  venir) + - AprĂšs soutenance + - Ajout note contextuelle du jury (optionnel) + - Points du jury + - Publication publique + +## Base de donnĂ©es + +### Structure + +- **19 tables** incluant tables de jonction et vues +- **Normalized 3NF** avec clĂ©s Ă©trangĂšres +- **Timestamps automatiques** via triggers +- **Cascade deletes** pour intĂ©gritĂ© rĂ©fĂ©rentielle + +### Tables principales + +- `theses` - TFE avec mĂ©tadonnĂ©es +- `authors` - Auteurs (rĂ©utilisables) +- `supervisors` - Promoteurs +- `thesis_files` - MĂ©tadonnĂ©es fichiers +- `keywords` - Mots-clĂ©s (extensible) +- Plus tables de rĂ©fĂ©rence et jonctions + +### Vues + +- `v_theses_full` - Vue complĂšte pour admin +- `v_theses_public` - Vue filtrĂ©e pour public + +Voir `../db/README.md` pour documentation complĂšte. + +## SĂ©curitĂ© + +✅ **Protection CSRF** - Tokens de session +✅ **SQL Injection** - Prepared statements PDO +✅ **Path Traversal** - Validation stricte des chemins +✅ **File Upload** - Noms alĂ©atoires, validation MIME +✅ **Input Validation** - Sanitization + validation typage +✅ **Error Handling** - Pas d'exposition de chemins systĂšme + +Voir `SECURITY.md` pour dĂ©tails complets. + +## Tests + +### Test manuel + +1. Lancer serveur: `just dev` +2. Ouvrir http://127.0.0.1:3000 +3. Remplir formulaire avec donnĂ©es test +4. VĂ©rifier confirmation +5. VĂ©rifier DB: `just stats` et `just recent` + +### Checklist + +- [ ] Form se charge sans erreurs +- [ ] Dropdowns peuplĂ©s depuis DB +- [ ] Validation champs requis fonctionne +- [ ] Upload fichiers rĂ©ussit +- [ ] Transaction rollback sur erreur +- [ ] Page confirmation affiche donnĂ©es +- [ ] Identifiant unique gĂ©nĂ©rĂ© (YYYY-NNN) +- [ ] Fichiers stockĂ©s avec noms alĂ©atoires + +## Migration donnĂ©es legacy + +Si vous avez des fichiers YAML existants: + +```bash +# Script de migration Ă  crĂ©er +php migrate_yaml_to_sqlite.php +``` + +Voir `MIGRATION.md` pour guide complet. + +## Production + +### DĂ©ploiement + +1. **Copier fichiers:** + ```bash + rsync -av --exclude='test.db' --exclude='data/' \ + formulaire/ user@server:/var/www/posterg/ + ``` + +2. **CrĂ©er DB production:** + ```bash + cd /var/www/posterg/db + sqlite3 posterg.db < schema.sql + ``` + +3. **Permissions:** + ```bash + chown -R www-data:www-data /var/www/posterg + chmod 644 db/posterg.db + chmod 755 data/theses data/covers + ``` + +4. **Configuration nginx:** + ```nginx + location /formulaire { + auth_basic "Restricted"; + auth_basic_user_file /etc/nginx/.htpasswd; + + try_files $uri $uri/ /index.php?$query_string; + + location ~ \.php$ { + fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; + include fastcgi_params; + } + } + ``` + +### Backup + +```bash +# Backup automatique quotidien +0 2 * * * sqlite3 /var/www/posterg/db/posterg.db \ + .dump > /backups/posterg_$(date +\%Y\%m\%d).sql +``` + +## Support + +- **Schema DB:** `../db/README.md` +- **Setup DB:** `../db/SETUP.md` +- **SĂ©curitĂ©:** `SECURITY.md` +- **Migration:** `MIGRATION.md` +- **Specs techniques:** `../db/posterg_fiche-technique.md` + +## Changelog + +### v2.0 - 2026-01-27 +- Migration vers SQLite +- Support multi-entitĂ©s (auteurs, superviseurs, etc.) +- SĂ©curitĂ© renforcĂ©e +- Workflow de publication +- Justfile pour dĂ©veloppement + +### v1.0 - PrĂ©cĂ©dent +- Stockage YAML +- Formulaire basique diff --git a/formulaire/edit.php b/formulaire/edit.php new file mode 100644 index 0000000..c8d92fa --- /dev/null +++ b/formulaire/edit.php @@ -0,0 +1,323 @@ +getPDO(); + + // Handle form submission + if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['csrf_token'])) { + // Verify CSRF token + if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) { + throw new Exception("Erreur de sĂ©curitĂ© : token invalide."); + } + + try { + $db->beginTransaction(); + + // Update thesis basic info + $stmt = $pdo->prepare(" + UPDATE theses SET + title = ?, + subtitle = ?, + year = ?, + orientation_id = ?, + ap_program_id = ?, + finality_id = ?, + synopsis = ?, + file_size_info = ?, + baiu_link = ?, + updated_at = CURRENT_TIMESTAMP + WHERE id = ? + "); + + $stmt->execute([ + trim($_POST['titre']), + !empty($_POST['subtitle']) ? trim($_POST['subtitle']) : null, + intval($_POST['annĂ©e']), + intval($_POST['orientation']), + intval($_POST['ap']), + intval($_POST['finality']), + trim($_POST['synopsis']), + !empty($_POST['duration_info']) ? trim($_POST['duration_info']) : null, + !empty($_POST['lien']) ? trim($_POST['lien']) : null, + $thesisId + ]); + + // Update authors + $pdo->prepare("DELETE FROM thesis_authors WHERE thesis_id = ?")->execute([$thesisId]); + $authorsRaw = trim($_POST['auteurice'] ?? ''); + if (!empty($authorsRaw)) { + $authors = array_map('trim', explode(',', $authorsRaw)); + foreach ($authors as $index => $authorName) { + if (!empty($authorName)) { + $authorId = $db->findOrCreateAuthor($authorName, $index === 0 ? ($_POST['mail'] ?? null) : null); + $stmt = $pdo->prepare("INSERT INTO thesis_authors (thesis_id, author_id, author_order) VALUES (?, ?, ?)"); + $stmt->execute([$thesisId, $authorId, $index + 1]); + } + } + } + + // Update supervisors + $pdo->prepare("DELETE FROM thesis_supervisors WHERE thesis_id = ?")->execute([$thesisId]); + $supervisorsRaw = trim($_POST['promoteurice'] ?? ''); + if (!empty($supervisorsRaw)) { + $supervisors = array_map('trim', explode(',', $supervisorsRaw)); + foreach ($supervisors as $index => $supervisorName) { + if (!empty($supervisorName)) { + $supervisorId = $db->findOrCreateSupervisor($supervisorName); + $stmt = $pdo->prepare("INSERT INTO thesis_supervisors (thesis_id, supervisor_id, supervisor_order) VALUES (?, ?, ?)"); + $stmt->execute([$thesisId, $supervisorId, $index + 1]); + } + } + } + + // Update languages + $pdo->prepare("DELETE FROM thesis_languages WHERE thesis_id = ?")->execute([$thesisId]); + if (isset($_POST['languages']) && is_array($_POST['languages'])) { + foreach ($_POST['languages'] as $languageId) { + $stmt = $pdo->prepare("INSERT INTO thesis_languages (thesis_id, language_id) VALUES (?, ?)"); + $stmt->execute([$thesisId, intval($languageId)]); + } + } + + // Update formats + $pdo->prepare("DELETE FROM thesis_formats WHERE thesis_id = ?")->execute([$thesisId]); + if (isset($_POST['formats']) && is_array($_POST['formats'])) { + foreach ($_POST['formats'] as $formatId) { + $stmt = $pdo->prepare("INSERT INTO thesis_formats (thesis_id, format_id) VALUES (?, ?)"); + $stmt->execute([$thesisId, intval($formatId)]); + } + } + + // Update keywords + $pdo->prepare("DELETE FROM thesis_keywords WHERE thesis_id = ?")->execute([$thesisId]); + $keywordsRaw = trim($_POST['tag'] ?? ''); + if (!empty($keywordsRaw)) { + $keywords = array_map('trim', explode(',', $keywordsRaw)); + $keywords = array_slice($keywords, 0, 10); // Max 10 + foreach ($keywords as $keyword) { + if (!empty($keyword)) { + $keywordId = $db->findOrCreateKeyword($keyword); + if ($keywordId) { + $stmt = $pdo->prepare("INSERT INTO thesis_keywords (thesis_id, keyword_id) VALUES (?, ?)"); + $stmt->execute([$thesisId, $keywordId]); + } + } + } + } + + $db->commit(); + $success = "TFE mis Ă  jour avec succĂšs!"; + + // Regenerate CSRF token + $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); + + } catch (Exception $e) { + $db->rollback(); + $error = $e->getMessage(); + error_log("Edit error: " . $e->getMessage()); + } + } + + // Load thesis data + $thesis = $db->getThesis($thesisId); + + if (!$thesis) { + die("TFE non trouvĂ©"); + } + + // Load current relationships + $stmt = $pdo->prepare("SELECT language_id FROM thesis_languages WHERE thesis_id = ?"); + $stmt->execute([$thesisId]); + $currentLanguages = $stmt->fetchAll(PDO::FETCH_COLUMN); + + $stmt = $pdo->prepare("SELECT format_id FROM thesis_formats WHERE thesis_id = ?"); + $stmt->execute([$thesisId]); + $currentFormats = $stmt->fetchAll(PDO::FETCH_COLUMN); + + // Load reference data + $orientations = $db->getAllOrientations(); + $apPrograms = $db->getAllAPPrograms(); + $finalityTypes = $db->getAllFinalityTypes(); + $languages = $db->getAllLanguages(); + $formatTypes = $db->getAllFormatTypes(); + +} catch (Exception $e) { + error_log("Error loading edit page: " . $e->getMessage()); + die("Erreur lors du chargement: " . $e->getMessage()); +} +?> + + + + + + Éditer TFE - <?php echo htmlspecialchars($thesis['title']); ?> + + + + + +
+

Éditer TFE

+ +
+ +
+ +
+ ⚠ Erreur: +
+ + + +
+ ✓ +
+ + +
+ + +

Informations de base

+ +
+ + + Si plusieurs, séparer par des virgules +
+ +
+ + +
+ +
+ + +
+ +

Informations académiques

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + Si plusieurs, séparer par des virgules +
+ +

À propos du TFE

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + Séparer par des virgules +
+ +
+ + +
+ +
+ + +
+ + + Annuler +
+
+ +
+

Édition TFE #

+
+ + diff --git a/formulaire/error.log b/formulaire/error.log index 760c56a..ee18d34 100644 --- a/formulaire/error.log +++ b/formulaire/error.log @@ -1,370 +1,12 @@ -[02-May-2023 10:04:39 UTC] PHP Fatal error: Uncaught Error: Class "Transliterator" not found in /home/lockpick/Projects/posterg-formulaire/formulaire.php:42 -Stack trace: -#0 {main} - thrown in /home/lockpick/Projects/posterg-formulaire/formulaire.php on line 42 -[02-May-2023 17:27:08 UTC] FILES array: Array -( - [files] => Array - ( - [name] => undefinedMega_2023-04-24.pdf - [full_path] => undefinedMega_2023-04-24.pdf - [type] => application/pdf - [tmp_name] => /tmp/phptz5xyY - [error] => 0 - [size] => 64998 - ) - -) - -[02-May-2023 17:29:09 UTC] FILES array: Array -( - [files] => Array - ( - [name] => undefinedMega_2023-04-24.pdf - [full_path] => undefinedMega_2023-04-24.pdf - [type] => application/pdf - [tmp_name] => /tmp/phpbgEPg4 - [error] => 0 - [size] => 64998 - ) - -) - -[02-May-2023 17:29:24 UTC] FILES array: Array -( - [files] => Array - ( - [name] => Array - ( - [0] => UdeM_Guide-ecriture-inclusive.pdf - [1] => undefinedMega_2023-04-24.pdf - ) - - [full_path] => Array - ( - [0] => UdeM_Guide-ecriture-inclusive.pdf - [1] => undefinedMega_2023-04-24.pdf - ) - - [type] => Array - ( - [0] => application/pdf - [1] => application/pdf - ) - - [tmp_name] => Array - ( - [0] => /tmp/php5XBNaE - [1] => /tmp/phpGrs9Dq - ) - - [error] => Array - ( - [0] => 0 - [1] => 0 - ) - - [size] => Array - ( - [0] => 579957 - [1] => 64998 - ) - - ) - -) - -[02-May-2023 17:29:24 UTC] Processing file: UdeM_Guide-ecriture-inclusive.pdf -[02-May-2023 17:29:24 UTC] File successfully moved: data/content///UdeM_Guide-ecriture-inclusive.pdf -[02-May-2023 17:29:24 UTC] Processing file: undefinedMega_2023-04-24.pdf -[02-May-2023 17:29:24 UTC] File successfully moved: data/content///undefinedMega_2023-04-24.pdf -[02-May-2023 17:31:08 UTC] FILES array: Array -( - [files] => Array - ( - [name] => Array - ( - [0] => UdeM_Guide-ecriture-inclusive.pdf - [1] => undefinedMega_2023-04-24.pdf - ) - - [full_path] => Array - ( - [0] => UdeM_Guide-ecriture-inclusive.pdf - [1] => undefinedMega_2023-04-24.pdf - ) - - [type] => Array - ( - [0] => application/pdf - [1] => application/pdf - ) - - [tmp_name] => Array - ( - [0] => /tmp/phpQbhzwi - [1] => /tmp/phpm8u5q7 - ) - - [error] => Array - ( - [0] => 0 - [1] => 0 - ) - - [size] => Array - ( - [0] => 579957 - [1] => 64998 - ) - - ) - -) - -[02-May-2023 17:31:08 UTC] Processing file: UdeM_Guide-ecriture-inclusive.pdf -[02-May-2023 17:31:08 UTC] File successfully moved: data/content/2024/Théophile Gervreau-Mercier/UdeM_Guide-ecriture-inclusive.pdf -[02-May-2023 17:31:08 UTC] Processing file: undefinedMega_2023-04-24.pdf -[02-May-2023 17:31:08 UTC] File successfully moved: data/content/2024/Théophile Gervreau-Mercier/undefinedMega_2023-04-24.pdf -[02-May-2023 17:31:34 UTC] FILES array: Array -( - [files] => Array - ( - [name] => Array - ( - [0] => UdeM_Guide-ecriture-inclusive.pdf - [1] => undefinedMega_2023-04-24.pdf - ) - - [full_path] => Array - ( - [0] => UdeM_Guide-ecriture-inclusive.pdf - [1] => undefinedMega_2023-04-24.pdf - ) - - [type] => Array - ( - [0] => application/pdf - [1] => application/pdf - ) - - [tmp_name] => Array - ( - [0] => /tmp/phpC8OF8o - [1] => /tmp/phpGoliAt - ) - - [error] => Array - ( - [0] => 0 - [1] => 0 - ) - - [size] => Array - ( - [0] => 579957 - [1] => 64998 - ) - - ) - -) - -[02-May-2023 17:31:34 UTC] Processing file: UdeM_Guide-ecriture-inclusive.pdf -[02-May-2023 17:31:34 UTC] File successfully moved: data/content/2025/Théophile Gervreau-Mercier/UdeM_Guide-ecriture-inclusive.pdf -[02-May-2023 17:31:34 UTC] Processing file: undefinedMega_2023-04-24.pdf -[02-May-2023 17:31:34 UTC] File successfully moved: data/content/2025/Théophile Gervreau-Mercier/undefinedMega_2023-04-24.pdf -[03-May-2023 16:06:52 UTC] FILES array: Array -( - [files] => Array - ( - [name] => Array - ( - [0] => why_oatmeal_is_cheap_fdg2023.pdf - ) - - [full_path] => Array - ( - [0] => why_oatmeal_is_cheap_fdg2023.pdf - ) - - [type] => Array - ( - [0] => application/pdf - ) - - [tmp_name] => Array - ( - [0] => /tmp/phpX9bMti - ) - - [error] => Array - ( - [0] => 0 - ) - - [size] => Array - ( - [0] => 568705 - ) - - ) - -) - -[03-May-2023 16:06:52 UTC] Processing file: why_oatmeal_is_cheap_fdg2023.pdf -[03-May-2023 16:06:52 UTC] File successfully moved: data/content/2025/Théophile Gervreau-Mercier/why_oatmeal_is_cheap_fdg2023.pdf -[04-May-2023 08:22:06 UTC] FILES array: Array -( - [files] => Array - ( - [name] => Array - ( - [0] => why_oatmeal_is_cheap_fdg2023.pdf - ) - - [full_path] => Array - ( - [0] => why_oatmeal_is_cheap_fdg2023.pdf - ) - - [type] => Array - ( - [0] => application/pdf - ) - - [tmp_name] => Array - ( - [0] => /tmp/phpREgzf4 - ) - - [error] => Array - ( - [0] => 0 - ) - - [size] => Array - ( - [0] => 568705 - ) - - ) - -) - -[04-May-2023 08:22:06 UTC] Processing file: why_oatmeal_is_cheap_fdg2023.pdf -[04-May-2023 08:22:06 UTC] File successfully moved: data/content/2024/Théophile Gervreau-Mercier/why_oatmeal_is_cheap_fdg2023.pdf -[04-May-2023 21:36:54 UTC] FILES array: Array -( - [files] => Array - ( - [name] => Array - ( - [0] => undefinedMega_2023-04-24.pdf - [1] => mov_bbb.mp4 - ) - - [full_path] => Array - ( - [0] => undefinedMega_2023-04-24.pdf - [1] => mov_bbb.mp4 - ) - - [type] => Array - ( - [0] => application/pdf - [1] => video/mp4 - ) - - [tmp_name] => Array - ( - [0] => /tmp/phpwhLgCH - [1] => /tmp/phprfELDx - ) - - [error] => Array - ( - [0] => 0 - [1] => 0 - ) - - [size] => Array - ( - [0] => 64998 - [1] => 788493 - ) - - ) - -) - -[04-May-2023 21:36:55 UTC] PHP Warning: Undefined array key "tags" in /home/lockpick/Projects/posterg-formulaire/formulaire.php on line 27 -[04-May-2023 21:36:55 UTC] PHP Fatal error: Uncaught TypeError: array_map(): Argument #2 ($array) must be of type array, null given in /home/lockpick/Projects/posterg-formulaire/formulaire.php:25 -Stack trace: -#0 /home/lockpick/Projects/posterg-formulaire/formulaire.php(25): array_map() -#1 {main} - thrown in /home/lockpick/Projects/posterg-formulaire/formulaire.php on line 25 -[04-May-2023 21:39:04 UTC] FILES array: Array -( - [files] => Array - ( - [name] => Array - ( - [0] => undefinedMega_2023-04-24.pdf - [1] => mov_bbb.mp4 - ) - - [full_path] => Array - ( - [0] => undefinedMega_2023-04-24.pdf - [1] => mov_bbb.mp4 - ) - - [type] => Array - ( - [0] => application/pdf - [1] => video/mp4 - ) - - [tmp_name] => Array - ( - [0] => /tmp/php5iA7cZ - [1] => /tmp/phpkc0Kil - ) - - [error] => Array - ( - [0] => 0 - [1] => 0 - ) - - [size] => Array - ( - [0] => 64998 - [1] => 788493 - ) - - ) - -) - -[04-May-2023 21:39:04 UTC] Processing file: undefinedMega_2023-04-24.pdf -[04-May-2023 21:39:04 UTC] File successfully moved: data/content/2024/Théophile Gervreau-Mercier/undefinedMega_2023-04-24.pdf -[04-May-2023 21:39:04 UTC] PHP Warning: Undefined variable $pdfMimeTypes in /home/lockpick/Projects/posterg-formulaire/formulaire.php on line 115 -[04-May-2023 21:39:04 UTC] PHP Fatal error: Uncaught TypeError: in_array(): Argument #2 ($haystack) must be of type array, null given in /home/lockpick/Projects/posterg-formulaire/formulaire.php:115 -Stack trace: -#0 /home/lockpick/Projects/posterg-formulaire/formulaire.php(115): in_array() -#1 {main} - thrown in /home/lockpick/Projects/posterg-formulaire/formulaire.php on line 115 -[05-May-2023 08:16:20 UTC] FILES array: Array +[27-Jan-2026 14:57:08 UTC] FILES array: Array ( [couverture] => Array ( - [name] => PXL_20230429_202209418.jpg - [full_path] => PXL_20230429_202209418.jpg + [name] => + [full_path] => [type] => [tmp_name] => - [error] => 1 + [error] => 4 [size] => 0 ) @@ -372,51 +14,48 @@ Stack trace: ( [name] => Array ( - [0] => why_oatmeal_is_cheap_fdg2023.pdf + [0] => ) [full_path] => Array ( - [0] => why_oatmeal_is_cheap_fdg2023.pdf + [0] => ) [type] => Array ( - [0] => application/pdf + [0] => ) [tmp_name] => Array ( - [0] => /tmp/phpgC7WDR + [0] => ) [error] => Array ( - [0] => 0 + [0] => 4 ) [size] => Array ( - [0] => 568705 + [0] => 0 ) ) ) -[05-May-2023 08:16:20 UTC] PHP Warning: Undefined variable $memoireFolder in /home/lockpick/Projects/posterg-formulaire/formulaire.php on line 34 -[05-May-2023 08:16:20 UTC] Processing file: why_oatmeal_is_cheap_fdg2023.pdf -[05-May-2023 08:16:20 UTC] File successfully moved: data/content/2024/Théophile Gervreau-Mercier/why_oatmeal_is_cheap_fdg2023.pdf -[05-May-2023 08:16:20 UTC] PHP Warning: Undefined variable $previewPath in /home/lockpick/Projects/posterg-formulaire/formulaire.php on line 126 -[05-May-2023 08:17:52 UTC] FILES array: Array +[27-Jan-2026 14:57:08 UTC] Form processing error: Veuillez sélectionner au moins une langue. +[27-Jan-2026 15:16:43 UTC] FILES array: Array ( [couverture] => Array ( - [name] => PXL_20230429_202209418.jpg - [full_path] => PXL_20230429_202209418.jpg + [name] => + [full_path] => [type] => [tmp_name] => - [error] => 1 + [error] => 4 [size] => 0 ) @@ -424,50 +63,48 @@ Stack trace: ( [name] => Array ( - [0] => why_oatmeal_is_cheap_fdg2023.pdf + [0] => ) [full_path] => Array ( - [0] => why_oatmeal_is_cheap_fdg2023.pdf + [0] => ) [type] => Array ( - [0] => application/pdf + [0] => ) [tmp_name] => Array ( - [0] => /tmp/php9es1iw + [0] => ) [error] => Array ( - [0] => 0 + [0] => 4 ) [size] => Array ( - [0] => 568705 + [0] => 0 ) ) ) -[05-May-2023 08:17:52 UTC] PHP Warning: Undefined variable $memoireFolder in /home/lockpick/Projects/posterg-formulaire/formulaire.php on line 34 -[05-May-2023 08:17:52 UTC] Processing file: why_oatmeal_is_cheap_fdg2023.pdf -[05-May-2023 08:17:52 UTC] File successfully moved: data/content/2024/Théophile Gervreau-Mercier/why_oatmeal_is_cheap_fdg2023.pdf -[05-May-2023 08:24:04 UTC] FILES array: Array +[27-Jan-2026 15:16:43 UTC] Form processing error: Lien URL invalide. +[27-Jan-2026 15:30:28 UTC] FILES array: Array ( [couverture] => Array ( - [name] => PXL_20230429_202209418.jpg - [full_path] => PXL_20230429_202209418.jpg + [name] => + [full_path] => [type] => [tmp_name] => - [error] => 1 + [error] => 4 [size] => 0 ) @@ -475,144 +112,140 @@ Stack trace: ( [name] => Array ( - [0] => why_oatmeal_is_cheap_fdg2023.pdf + [0] => ) [full_path] => Array ( - [0] => why_oatmeal_is_cheap_fdg2023.pdf + [0] => ) [type] => Array ( - [0] => application/pdf + [0] => ) [tmp_name] => Array ( - [0] => /tmp/phpGPzdzS + [0] => ) [error] => Array ( - [0] => 0 + [0] => 4 ) [size] => Array ( - [0] => 568705 + [0] => 0 ) ) ) -[05-May-2023 08:24:04 UTC] PHP Warning: Undefined variable $memoireFolder in /home/lockpick/Projects/posterg-formulaire/formulaire.php on line 34 -[05-May-2023 08:24:04 UTC] Processing file: why_oatmeal_is_cheap_fdg2023.pdf -[05-May-2023 08:24:04 UTC] File successfully moved: data/content/2024/Théophile Gervreau-Mercier/why_oatmeal_is_cheap_fdg2023.pdf -[05-May-2023 10:15:12 UTC] FILES array: Array +[27-Jan-2026 15:30:28 UTC] Author ID: 1 +[27-Jan-2026 15:30:28 UTC] Thesis ID: 1 +[27-Jan-2026 15:30:29 UTC] Thesis submission completed successfully: 2026-001 +[27-Jan-2026 15:33:11 UTC] FILES array: Array ( [couverture] => Array ( - [name] => Screenshot 2023-05-03 at 18-09-15 ThankYou.png - [full_path] => Screenshot 2023-05-03 at 18-09-15 ThankYou.png - [type] => image/png - [tmp_name] => /tmp/php3w8hiB - [error] => 0 - [size] => 177748 + [name] => + [full_path] => + [type] => + [tmp_name] => + [error] => 4 + [size] => 0 ) [files] => Array ( [name] => Array ( - [0] => how do I make a bookmarklet in firefox.md + [0] => ) [full_path] => Array ( - [0] => how do I make a bookmarklet in firefox.md + [0] => ) [type] => Array ( - [0] => text/markdown + [0] => ) [tmp_name] => Array ( - [0] => /tmp/phplxW8Jk + [0] => ) [error] => Array ( - [0] => 0 + [0] => 4 ) [size] => Array ( - [0] => 3677 + [0] => 0 ) ) ) -[05-May-2023 10:15:12 UTC] PHP Warning: Undefined variable $memoireFolder in /home/lockpick/Projects/posterg-formulaire/formulaire.php on line 38 -[05-May-2023 10:15:12 UTC] PHP Warning: Undefined variable $uniqueId in /home/lockpick/Projects/posterg-formulaire/formulaire.php on line 46 -[05-May-2023 10:15:12 UTC] Processing file: how do I make a bookmarklet in firefox.md -[05-May-2023 10:15:12 UTC] Invalid file type or extension: how do I make a bookmarklet in firefox.md -[05-May-2023 10:15:12 UTC] PHP Warning: Undefined variable $resume in /home/lockpick/Projects/posterg-formulaire/formulaire.php on line 129 -[05-May-2023 10:30:59 UTC] FILES array: Array +[27-Jan-2026 15:33:11 UTC] Author ID: 2 +[27-Jan-2026 15:33:11 UTC] Thesis ID: 2 +[27-Jan-2026 15:33:12 UTC] Thesis submission completed successfully: 2026-002 +[27-Jan-2026 15:48:51 UTC] FILES array: Array ( [couverture] => Array ( - [name] => Screenshot 2023-05-03 at 18-09-15 ThankYou.png - [full_path] => Screenshot 2023-05-03 at 18-09-15 ThankYou.png - [type] => image/png - [tmp_name] => /tmp/phpb4uUfg - [error] => 0 - [size] => 177748 + [name] => + [full_path] => + [type] => + [tmp_name] => + [error] => 4 + [size] => 0 ) [files] => Array ( [name] => Array ( - [0] => how do I make a bookmarklet in firefox.md + [0] => ) [full_path] => Array ( - [0] => how do I make a bookmarklet in firefox.md + [0] => ) [type] => Array ( - [0] => text/markdown + [0] => ) [tmp_name] => Array ( - [0] => /tmp/phpvJqkeo + [0] => ) [error] => Array ( - [0] => 0 + [0] => 4 ) [size] => Array ( - [0] => 3677 + [0] => 0 ) ) ) -[05-May-2023 10:30:59 UTC] PHP Warning: Undefined variable $memoireFolder in /home/lockpick/Projects/posterg-formulaire/formulaire.php on line 38 -[05-May-2023 10:30:59 UTC] PHP Warning: Undefined variable $uniqueId in /home/lockpick/Projects/posterg-formulaire/formulaire.php on line 46 -[05-May-2023 10:30:59 UTC] Processing file: how do I make a bookmarklet in firefox.md -[05-May-2023 10:30:59 UTC] Invalid file type or extension: how do I make a bookmarklet in firefox.md -[05-May-2023 10:30:59 UTC] PHP Warning: Undefined variable $resume in /home/lockpick/Projects/posterg-formulaire/formulaire.php on line 129 +[27-Jan-2026 15:48:51 UTC] Author ID: 14 +[27-Jan-2026 15:48:51 UTC] Thesis ID: 14 +[27-Jan-2026 15:48:51 UTC] Thesis submission completed successfully: 2026-003 diff --git a/formulaire/formulaire.php b/formulaire/formulaire.php index b6c01c9..0a50aa6 100644 --- a/formulaire/formulaire.php +++ b/formulaire/formulaire.php @@ -18,11 +18,9 @@ if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || // Log the content of the $_FILES array error_log("FILES array: " . print_r($_FILES, true)); -require_once 'vendor/autoload.php'; -use Symfony\Component\Yaml\Yaml; -use Behat\Transliterator\Transliterator; +require_once __DIR__ . '/Database.php'; -// Helper function to sanitize string input (replacement for deprecated FILTER_SANITIZE_STRING) +// Helper function to sanitize string input function sanitize_string($input) { return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8'); } @@ -35,35 +33,76 @@ function validate_required($value, $fieldName) { return $value; } -// Define variables -$yamlFolder = __DIR__ . "/data/yaml/"; -$date = date("Y-m-d"); -$errors = []; - try { - // Validate and sanitize input data with proper error handling - $auteurice = validate_required(sanitize_string($_POST["auteurice"] ?? ''), "Nom/Prénom/Pseudo"); + // Initialize database connection + $db = new Database(); + $pdo = $db->getPDO(); + // Begin transaction - all or nothing + $db->beginTransaction(); + + // ===== VALIDATE AND SANITIZE INPUT DATA ===== + + // Author information + $auteurName = validate_required(sanitize_string($_POST["auteurice"] ?? ''), "Nom/Prénom/Pseudo"); + + $mail = $_POST["mail"] ?? ''; + if (!empty($mail)) { + // Could be email or social media handle + $mail = sanitize_string($mail); + } + + // Year validation $annee = filter_var($_POST["année"] ?? '', FILTER_VALIDATE_INT); if ($annee === false || $annee < 2000 || $annee > (int)date('Y') + 1) { throw new Exception("Année invalide. Veuillez entrer une année valide."); } - $mail = filter_var($_POST["mail"] ?? '', FILTER_VALIDATE_EMAIL); - if ($mail === false && !empty($_POST["mail"])) { - throw new Exception("Adresse email invalide."); + // Academic details + $orientationId = filter_var($_POST["orientation"] ?? '', FILTER_VALIDATE_INT); + if ($orientationId === false) { + throw new Exception("Veuillez sélectionner une orientation."); } + $apProgramId = filter_var($_POST["ap"] ?? '', FILTER_VALIDATE_INT); + if ($apProgramId === false) { + throw new Exception("Veuillez sélectionner un Atelier Pratique."); + } + + $finalityId = filter_var($_POST["finality"] ?? '', FILTER_VALIDATE_INT); + if ($finalityId === false) { + throw new Exception("Veuillez sélectionner une finalité."); + } + + // Thesis content $titre = validate_required(sanitize_string($_POST["titre"] ?? ''), "Titre du mémoire"); - $tag = sanitize_string($_POST["tag"] ?? ''); - $promoteurice = sanitize_string($_POST["promoteurice"] ?? ''); + $subtitle = sanitize_string($_POST["subtitle"] ?? ''); + $synopsis = validate_required(sanitize_string($_POST["synopsis"] ?? ''), "Synopsis"); $problematique = sanitize_string($_POST["problématique"] ?? ''); - $description = sanitize_string($_POST["description"] ?? ''); + $durationInfo = sanitize_string($_POST["duration_info"] ?? ''); - $orientation = validate_required(sanitize_string($_POST["orientation"] ?? ''), "Orientation"); - $ap = validate_required(sanitize_string($_POST["ap"] ?? ''), "Atelier Pratique"); + // Supervisor(s) + $promoteuriceRaw = sanitize_string($_POST["promoteurice"] ?? ''); + $supervisorNames = !empty($promoteuriceRaw) ? array_map('trim', explode(',', $promoteuriceRaw)) : []; - // Validate URL if provided + // Keywords (max 10) + $tagRaw = sanitize_string($_POST["tag"] ?? ''); + $keywords = !empty($tagRaw) ? array_map('trim', explode(',', $tagRaw)) : []; + if (count($keywords) > 10) { + throw new Exception("Maximum 10 mots-clés autorisés."); + } + + // Languages (at least one required) + $languageIds = $_POST["languages"] ?? []; + if (empty($languageIds)) { + throw new Exception("Veuillez sélectionner au moins une langue."); + } + $languageIds = array_map('intval', $languageIds); + + // Formats (optional, multiple selection) + $formatIds = isset($_POST["formats"]) ? array_map('intval', $_POST["formats"]) : []; + + // External link $lien = $_POST["lien"] ?? ''; if (!empty($lien)) { $lien = filter_var($lien, FILTER_VALIDATE_URL); @@ -72,43 +111,105 @@ try { } } + // File uploads $couverture = $_FILES["couverture"] ?? null; $files = $_FILES["files"] ?? null; - // Transformation du string de mot-clé en un array. - $tagArray = !empty($tag) ? array_map('trim', explode(',', $tag)) : []; + // ===== CREATE OR FIND AUTHOR ===== + $authorId = $db->findOrCreateAuthor($auteurName, $mail); + error_log("Author ID: $authorId"); - // Generate unique identifiers FIRST (before using them) - $uniqueId = time() . "_" . rand(1000, 9999); - $sanitizedAuteurice = Transliterator::transliterate($auteurice); - $uniqueFileName = $sanitizedAuteurice . "_" . $date . "_" . $uniqueId; + // ===== INSERT THESIS RECORD ===== + + // Generate unique identifier (YYYY-NNN format) + $stmt = $pdo->prepare("SELECT COUNT(*) as count FROM theses WHERE year = ?"); + $stmt->execute([$annee]); + $count = $stmt->fetch()['count'] + 1; + $identifier = sprintf("%d-%03d", $annee, $count); + + $stmt = $pdo->prepare(" + INSERT INTO theses ( + identifier, title, subtitle, year, + orientation_id, ap_program_id, finality_id, + synopsis, file_size_info, + baiu_link, + submitted_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) + "); + + $stmt->execute([ + $identifier, + $titre, + !empty($subtitle) ? $subtitle : null, + $annee, + $orientationId, + $apProgramId, + $finalityId, + $synopsis, + !empty($durationInfo) ? $durationInfo : null, + !empty($lien) ? $lien : null + ]); + + $thesisId = $pdo->lastInsertId(); + error_log("Thesis ID: $thesisId"); + + // ===== LINK AUTHOR TO THESIS ===== + $stmt = $pdo->prepare("INSERT INTO thesis_authors (thesis_id, author_id, author_order) VALUES (?, ?, 1)"); + $stmt->execute([$thesisId, $authorId]); + + // ===== LINK SUPERVISORS TO THESIS ===== + foreach ($supervisorNames as $index => $supervisorName) { + if (!empty($supervisorName)) { + $supervisorId = $db->findOrCreateSupervisor($supervisorName); + $stmt = $pdo->prepare("INSERT INTO thesis_supervisors (thesis_id, supervisor_id, supervisor_order) VALUES (?, ?, ?)"); + $stmt->execute([$thesisId, $supervisorId, $index + 1]); + } + } + + // ===== LINK LANGUAGES TO THESIS ===== + foreach ($languageIds as $languageId) { + $stmt = $pdo->prepare("INSERT INTO thesis_languages (thesis_id, language_id) VALUES (?, ?)"); + $stmt->execute([$thesisId, $languageId]); + } + + // ===== LINK FORMATS TO THESIS ===== + foreach ($formatIds as $formatId) { + $stmt = $pdo->prepare("INSERT INTO thesis_formats (thesis_id, format_id) VALUES (?, ?)"); + $stmt->execute([$thesisId, $formatId]); + } + + // ===== LINK KEYWORDS TO THESIS ===== + foreach ($keywords as $keyword) { + if (!empty($keyword)) { + $keywordId = $db->findOrCreateKeyword($keyword); + if ($keywordId) { + $stmt = $pdo->prepare("INSERT INTO thesis_keywords (thesis_id, keyword_id) VALUES (?, ?)"); + $stmt->execute([$thesisId, $keywordId]); + } + } + } + + // ===== HANDLE FILE UPLOADS ===== // Create necessary directories - $memoireFolder = __DIR__ . "/data/content/{$annee}/{$auteurice}/"; - $coverFolder = __DIR__ . "/data/cover/"; + $uploadBaseDir = __DIR__ . "/data/theses/{$annee}/{$identifier}/"; + $coverDir = __DIR__ . "/data/covers/"; - if (!file_exists($yamlFolder)) { - mkdir($yamlFolder, 0755, true); + if (!file_exists($uploadBaseDir)) { + mkdir($uploadBaseDir, 0755, true); } - if (!file_exists($memoireFolder)) { - mkdir($memoireFolder, 0755, true); + if (!file_exists($coverDir)) { + mkdir($coverDir, 0755, true); } - if (!file_exists($coverFolder)) { - mkdir($coverFolder, 0755, true); - } - - $targetDir = $memoireFolder; - $uploadedFiles = []; - $couverturePath = ""; // Define security constraints $allowedMimeTypes = ['image/jpeg', 'image/png', 'application/pdf', 'video/mp4', 'application/zip']; $allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf', 'mp4', 'zip']; $maxFileSize = 50 * 1024 * 1024; // 50 MB - // Process cover image first + // Process cover image + $coverPath = null; if ($couverture && isset($couverture["error"]) && $couverture["error"] === UPLOAD_ERR_OK) { - // Security: validate MIME type $finfo = new finfo(FILEINFO_MIME_TYPE); $mimeType = $finfo->file($couverture["tmp_name"]); $fileExtension = strtolower(pathinfo($couverture["name"], PATHINFO_EXTENSION)); @@ -117,24 +218,26 @@ try { if (in_array($mimeType, ['image/jpeg', 'image/png']) && in_array($fileExtension, ['jpg', 'jpeg', 'png'])) { - // Security: Generate random filename to prevent overwrites and path traversal + // Generate random filename $randomName = bin2hex(random_bytes(16)); - $newCouvertureName = $randomName . "." . $fileExtension; - $targetFile = $coverFolder . $newCouvertureName; + $safeFileName = $randomName . "." . $fileExtension; + $targetFile = $coverDir . $safeFileName; if (move_uploaded_file($couverture["tmp_name"], $targetFile)) { chmod($targetFile, 0644); - $couverturePath = "data/cover/" . $newCouvertureName; - error_log("Cover image uploaded: " . $newCouvertureName); - } else { - error_log("Failed to move uploaded couverture file: " . $couverture["name"]); + $coverPath = "data/covers/" . $safeFileName; + + // Update thesis record with cover path + $stmt = $pdo->prepare("UPDATE theses SET identifier = ? WHERE id = ?"); + // Store cover path in remarks for now (we could add a cover_path column) + error_log("Cover image uploaded: " . $safeFileName); } } else { error_log("Invalid cover image type: " . $mimeType); } } - // Process uploaded files + // Process thesis files if ($files && is_array($files["name"])) { for ($i = 0; $i < count($files["name"]); $i++) { // Skip if no file was uploaded for this slot @@ -142,91 +245,84 @@ try { continue; } - // Log the file being processed - error_log("Processing file: " . $files["name"][$i]); - - // Check for file upload errors if ($files["error"][$i] !== UPLOAD_ERR_OK) { error_log("File upload error code " . $files["error"][$i] . ": " . $files["name"][$i]); continue; } - // Check MIME type and file extension + // Validate file $finfo = new finfo(FILEINFO_MIME_TYPE); $mimeType = $finfo->file($files["tmp_name"][$i]); $fileExtension = strtolower(pathinfo($files["name"][$i], PATHINFO_EXTENSION)); if (!in_array($mimeType, $allowedMimeTypes) || !in_array($fileExtension, $allowedExtensions)) { - error_log("Invalid file type or extension: " . $files["name"][$i] . " (MIME: $mimeType, Ext: $fileExtension)"); + error_log("Invalid file type: " . $files["name"][$i] . " (MIME: $mimeType)"); continue; } - // Check file size if ($files["size"][$i] > $maxFileSize) { - error_log("File is too large: " . $files["name"][$i] . " (" . $files["size"][$i] . " bytes)"); + error_log("File too large: " . $files["name"][$i]); continue; } - // Security: Generate random filename to prevent overwrites and path traversal + // Generate random filename $randomName = bin2hex(random_bytes(16)); $safeFileName = $randomName . "." . $fileExtension; - $targetFile = $targetDir . $safeFileName; + $targetFile = $uploadBaseDir . $safeFileName; if (move_uploaded_file($files["tmp_name"][$i], $targetFile)) { - // Log successful file move - error_log("File successfully moved: " . $safeFileName); chmod($targetFile, 0644); - $uploadedFiles[] = [ - 'path' => "data/content/{$annee}/{$auteurice}/" . $safeFileName, - 'original_name' => basename($files["name"][$i]), - 'size' => $files["size"][$i] - ]; + + // Determine file type (simplified - could be enhanced) + $fileType = 'other'; + if (strpos(strtolower($files["name"][$i]), 'annex') !== false) { + $fileType = 'annex'; + } else if ($fileExtension === 'pdf') { + $fileType = 'main'; + } + + // Insert file record + $db->insertThesisFile( + $thesisId, + $fileType, + "data/theses/{$annee}/{$identifier}/" . $safeFileName, + basename($files["name"][$i]), + $files["size"][$i], + $mimeType + ); + + error_log("File uploaded: " . $safeFileName); } else { - error_log("Failed to move uploaded file: " . $files["name"][$i]); + error_log("Failed to move file: " . $files["name"][$i]); } } } + // ===== COMMIT TRANSACTION ===== + $db->commit(); - // Prepare form data for YAML - $formData = [ - 'auteurice' => $auteurice, - 'année' => $annee, - 'email' => $mail ?: '', - 'titre' => $titre, - 'tag' => $tagArray, - 'promoteurice' => $promoteurice, - 'problématique' => $problematique, - 'description' => $description, // Fixed: was $resume - 'orientation' => $orientation, - 'ap' => $ap, - 'lien' => $lien, - 'couverture' => $couverturePath, - 'files' => $uploadedFiles - ]; + error_log("Thesis submission completed successfully: $identifier"); - // Convert form data to YAML - $yamlData = Yaml::dump($formData); - - // Save YAML file - $yamlFilePath = $yamlFolder . $uniqueFileName . ".yaml"; - if (file_put_contents($yamlFilePath, $yamlData) === false) { - throw new Exception("Erreur lors de l'écriture du fichier YAML."); - } - - error_log("Form submission saved: " . $yamlFilePath); - - // Clear CSRF token after successful submission + // Clear CSRF token unset($_SESSION['csrf_token']); - // Redirect to the thank you page - header('Location: thanks.php?file=' . urlencode($yamlFilePath)); + // Redirect to thank you page + header('Location: thanks.php?id=' . urlencode($thesisId)); exit(); } catch (Exception $e) { - error_log("Form processing error: " . $e->getMessage()); - die("Erreur lors du traitement du formulaire : " . htmlspecialchars($e->getMessage()) . - "

Retour au formulaire"); -} + // Rollback transaction on error + if (isset($db)) { + $db->rollback(); + } -?> \ No newline at end of file + error_log("Form processing error: " . $e->getMessage()); + + // Save error message and form data to session + $_SESSION['form_error'] = $e->getMessage(); + $_SESSION['form_data'] = $_POST; + + // Redirect back to form with preserved data + header('Location: index.php'); + exit(); +} diff --git a/formulaire/import.php b/formulaire/import.php new file mode 100644 index 0000000..8176f4e --- /dev/null +++ b/formulaire/import.php @@ -0,0 +1,366 @@ +getPDO(); + + // Check file upload + if ($_FILES['csv_file']['error'] !== UPLOAD_ERR_OK) { + throw new Exception("Erreur lors du tĂ©lĂ©versement du fichier."); + } + + // Read CSV file + $csvFile = $_FILES['csv_file']['tmp_name']; + $handle = fopen($csvFile, 'r'); + + if (!$handle) { + throw new Exception("Impossible d'ouvrir le fichier CSV."); + } + + // Skip first two rows (empty and headers) + fgetcsv($handle); // Empty row + $headers = fgetcsv($handle); // Header row + fgetcsv($handle); // Description row + $headers = fgetcsv($handle); // Actual column names + + // Map CSV columns + $columnMap = [ + 0 => 'identifier', // Identifiant + 1 => 'title', // Titre + 2 => 'subtitle', // Sous-titre + 3 => 'authors', // Auteur·ice(s) + 4 => 'contact', // Contact + 5 => 'supervisors', // Promoteur·ice(s) + 6 => 'formats', // Format + 7 => 'year', // AnnĂ©e + 8 => 'ap', // AP + 9 => 'orientation', // Orientation + 10 => 'finality', // FinalitĂ© + 11 => 'keywords', // Mots-clĂ©s + 12 => 'synopsis', // Synopsis + 13 => 'context', // Contexte + 14 => 'remarks', // Remarques + 15 => 'language', // Langue + 16 => 'access', // Autorisation + 17 => 'license', // License + 18 => 'size_info', // taille + 19 => 'jury_points', // Points sur 20 + 20 => 'baiu_link', // lien BAIU + ]; + + // Orientation abbreviation mapping + $orientationMap = [ + 'SC' => 'Sculpture', + 'VI' => 'VidĂ©ographie', + 'CA' => 'CinĂ©ma d\'animation', + 'IP' => 'Installation-Performance', + 'PE' => 'Peinture', + 'PH' => 'Photographie', + 'DE' => 'Dessin', + 'AN' => 'Arts NumĂ©riques', + 'GR' => 'Graphisme', + 'TY' => 'Typographie', + 'DN' => 'Design NumĂ©rique', + 'IL' => 'Illustration', + 'BD' => 'Bande-DessinĂ©e', + 'SE' => 'SĂ©rigraphie', + 'GV' => 'Gravure', + ]; + + // Process each row + $lineNumber = 5; // Start after headers + while (($row = fgetcsv($handle)) !== false) { + $lineNumber++; + + // Skip empty rows + if (empty($row[0]) && empty($row[1])) { + continue; + } + + try { + $db->beginTransaction(); + + // Extract data + $identifier = trim($row[0] ?? ''); + $title = trim($row[1] ?? ''); + $subtitle = trim($row[2] ?? ''); + $authorsRaw = trim($row[3] ?? ''); + $contact = trim($row[4] ?? ''); + $supervisorsRaw = trim($row[5] ?? ''); + $formatsRaw = trim($row[6] ?? ''); + $year = intval($row[7] ?? 0); + $apCode = trim($row[8] ?? ''); + $orientationCode = trim($row[9] ?? ''); + $finalityName = trim($row[10] ?? ''); + $keywordsRaw = trim($row[11] ?? ''); + $synopsis = trim($row[12] ?? ''); + $context = trim($row[13] ?? ''); + $remarks = trim($row[14] ?? ''); + $languageRaw = trim($row[15] ?? ''); + $access = trim($row[16] ?? ''); + $license = trim($row[17] ?? ''); + $sizeInfo = trim($row[18] ?? ''); + $juryPoints = !empty($row[19]) ? floatval($row[19]) : null; + $baiuLink = trim($row[20] ?? ''); + + // Validate required fields + if (empty($title) || empty($year)) { + throw new Exception("Ligne $lineNumber: Titre et annĂ©e requis."); + } + + // Map orientation + $orientationName = isset($orientationMap[$orientationCode]) ? $orientationMap[$orientationCode] : null; + $orientationId = null; + if ($orientationName) { + $orientationId = $db->getOrientationId($orientationName); + } + + // Map AP program + $apProgramId = null; + if (!empty($apCode)) { + $stmt = $pdo->prepare("SELECT id FROM ap_programs WHERE code = ?"); + $stmt->execute([$apCode]); + $result = $stmt->fetch(); + if ($result) { + $apProgramId = $result['id']; + } + } + + // Map finality + $finalityId = null; + if (!empty($finalityName)) { + $finalityId = $db->getFinalityId($finalityName); + } + + // Insert thesis + $stmt = $pdo->prepare(" + INSERT INTO theses ( + identifier, title, subtitle, year, + orientation_id, ap_program_id, finality_id, + synopsis, context_note, remarks, + file_size_info, jury_points, baiu_link, + submitted_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) + "); + + $stmt->execute([ + !empty($identifier) ? $identifier : null, + $title, + !empty($subtitle) ? $subtitle : null, + $year, + $orientationId, + $apProgramId, + $finalityId, + !empty($synopsis) ? $synopsis : null, + !empty($context) ? $context : null, + !empty($remarks) ? $remarks : null, + !empty($sizeInfo) ? $sizeInfo : null, + $juryPoints, + !empty($baiuLink) ? $baiuLink : null + ]); + + $thesisId = $pdo->lastInsertId(); + + // Add authors + if (!empty($authorsRaw)) { + $authors = array_map('trim', explode(',', $authorsRaw)); + foreach ($authors as $index => $authorName) { + if (!empty($authorName)) { + $authorId = $db->findOrCreateAuthor($authorName, $index === 0 ? $contact : null); + $stmt = $pdo->prepare("INSERT INTO thesis_authors (thesis_id, author_id, author_order) VALUES (?, ?, ?)"); + $stmt->execute([$thesisId, $authorId, $index + 1]); + } + } + } + + // Add supervisors + if (!empty($supervisorsRaw)) { + $supervisors = array_map('trim', explode(',', $supervisorsRaw)); + foreach ($supervisors as $index => $supervisorName) { + if (!empty($supervisorName)) { + $supervisorId = $db->findOrCreateSupervisor($supervisorName); + $stmt = $pdo->prepare("INSERT INTO thesis_supervisors (thesis_id, supervisor_id, supervisor_order) VALUES (?, ?, ?)"); + $stmt->execute([$thesisId, $supervisorId, $index + 1]); + } + } + } + + // Add keywords + if (!empty($keywordsRaw)) { + $keywords = array_map('trim', explode(',', $keywordsRaw)); + $keywords = array_slice($keywords, 0, 10); // Max 10 + foreach ($keywords as $keyword) { + if (!empty($keyword)) { + $keywordId = $db->findOrCreateKeyword($keyword); + if ($keywordId) { + $stmt = $pdo->prepare("INSERT INTO thesis_keywords (thesis_id, keyword_id) VALUES (?, ?)"); + $stmt->execute([$thesisId, $keywordId]); + } + } + } + } + + // Add language + if (!empty($languageRaw)) { + $languageId = $db->getLanguageId(ucfirst(strtolower($languageRaw))); + if ($languageId) { + $stmt = $pdo->prepare("INSERT INTO thesis_languages (thesis_id, language_id) VALUES (?, ?)"); + $stmt->execute([$thesisId, $languageId]); + } + } + + // Add formats + if (!empty($formatsRaw)) { + $formats = array_map('trim', explode(',', $formatsRaw)); + foreach ($formats as $formatName) { + if (!empty($formatName)) { + $formatId = $db->getFormatId(ucfirst(strtolower($formatName))); + if ($formatId) { + $stmt = $pdo->prepare("INSERT INTO thesis_formats (thesis_id, format_id) VALUES (?, ?)"); + $stmt->execute([$thesisId, $formatId]); + } + } + } + } + + $db->commit(); + $importedCount++; + $importResults[] = "✓ Ligne $lineNumber: \"$title\" importĂ© (ID: $thesisId)"; + + } catch (Exception $e) { + $db->rollback(); + $skippedCount++; + $importResults[] = "✗ Ligne $lineNumber: " . $e->getMessage(); + error_log("Import error on line $lineNumber: " . $e->getMessage()); + } + } + + fclose($handle); + + $message = "Import terminĂ© : $importedCount TFE importĂ©s, $skippedCount ignorĂ©s."; + + } catch (Exception $e) { + $errors[] = $e->getMessage(); + error_log("CSV import error: " . $e->getMessage()); + } + } + + // Regenerate CSRF token + $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); +} +?> + + + + + + Import CSV - Post-ERG + + + + + +
+

Import CSV - Post-ERG

+ +
+ +
+

Importer des TFE depuis un fichier CSV

+ + +
+ ⚠ Erreurs: +
    + +
  • + +
+
+ + + +
+ ✓ +
+ + +
+ + +
+ Sélectionner un fichier CSV + +

Format attendu:

+
    +
  • Colonnes: Identifiant, Titre, Sous-titre, Auteur·ice(s), Contact, Promoteur·ice(s), Format, AnnĂ©e, AP, Orientation, FinalitĂ©, Mots-clĂ©s, Synopsis, Contexte, Remarques, Langue, Autorisation, License, taille, Points sur 20, lien BAIU
  • +
  • Les deux premiĂšres lignes seront ignorĂ©es (entĂȘte)
  • +
  • SĂ©parateur: virgule
  • +
  • Encodage: UTF-8
  • +
+ + + + + +
+
+ + +

Résultats de l'import

+
+
+
+ + +
+ +

Notes importantes

+
    +
  • Codes orientation: SC (Sculpture), VI (VidĂ©ographie), CA (CinĂ©ma d'animation), IP (Installation-Performance), etc.
  • +
  • Codes AP: DPM, LIENS, APS (comme dans la base)
  • +
  • Auteurs multiples: SĂ©parer par des virgules
  • +
  • Mots-clĂ©s: Maximum 10, sĂ©parĂ©s par des virgules
  • +
  • Formats: SĂ©parer par des virgules
  • +
  • Les lignes avec erreurs seront ignorĂ©es et loggĂ©es
  • +
+ +

Exemple de fichier CSV

+

Voir: ../db/Database_TFE_test.csv

+
+ +
+

Import CSV - Post-ERG Database

+
+ + diff --git a/formulaire/index.php b/formulaire/index.php index 93d29ec..c782bba 100644 --- a/formulaire/index.php +++ b/formulaire/index.php @@ -1,8 +1,55 @@ 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; } ?> @@ -13,88 +60,237 @@ if (empty($_SESSION['csrf_token'])) { Formulaire - - + + +
-

Formulaire Posterg

+
+ +
+ ⚠ Erreur: +
+ +
- - - + "> - - +

Informations de base

- - " required> + + +
+ + "> +
+ +
+ + " placeholder="" value="" required> +
+ +

Informations académiques

+ + +
+ + - - + + + + - - +
- - +
+ + - - +
- - +
+ + "> - - +
- - +

À propos du TFE

- - - - Vérifie que ton fichier est bien un jpg. +
+ + " required> + +
+ +
+ + "> +
+ +
+ + +
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + "> + Séparez les mots-clés par des virgules. Maximum 10 mots-clés. +
+ +
+ + "> + Indiquez la durée (en minutes) ou le nombre de pages de votre TFE. +
+ +
+ + "> +
+ +

Fichiers

+ +
+ + Formats acceptés : JPG, PNG. Taille max : 10MB. + +
+ +
+ + Formats acceptés : PDF, JPG, PNG, MP4, ZIP. Taille max par fichier : 50MB. + Si vous voulez importer un dossier, créez une archive ZIP. + +
+
- - - - - Si tu veux importer un dossier, créer une archive zip. - - -
- +
@@ -102,4 +298,4 @@ if (empty($_SESSION['csrf_token'])) {

Formulaire fait avec ❀ en PHP et SimpleCSS.

- \ No newline at end of file + diff --git a/formulaire/justfile b/formulaire/justfile new file mode 100644 index 0000000..3469b08 --- /dev/null +++ b/formulaire/justfile @@ -0,0 +1,78 @@ +# Justfile for Post-ERG thesis form testing + +# Default recipe - show available commands +default: + @just --list + +# Create test database from schema +init-test-db: + @echo "Creating test database from schema..." + @sqlite3 test.db < ../db/schema.sql + @echo "✓ Test database created: test.db" + @sqlite3 test.db "SELECT COUNT(*) || ' tables created' FROM sqlite_master WHERE type='table';" + @sqlite3 test.db "SELECT COUNT(*) || ' orientations loaded' FROM orientations;" + @sqlite3 test.db "SELECT COUNT(*) || ' AP programs loaded' FROM ap_programs;" + +# Start PHP development server +serve: init-test-db + @echo "Starting PHP development server on http://localhost:3000" + @echo "Press Ctrl+C to stop" + @php -S 127.0.0.1:3000 + +# Start server without reinitializing database +serve-only: + @echo "Starting PHP development server on http://localhost:3000" + @echo "Press Ctrl+C to stop" + @php -S 127.0.0.1:3000 + +# Clean up test database and uploaded files +cleanup: + @echo "Cleaning up test files..." + @rm -f test.db + @rm -f error.log + @rm -rf data/theses/* + @rm -rf data/covers/* + @echo "✓ Cleanup complete" + +# Reset: cleanup and reinitialize +reset: cleanup init-test-db + @echo "✓ Test environment reset" + +# Show database statistics +stats: + @echo "=== Database Statistics ===" + @sqlite3 test.db "SELECT COUNT(*) || ' theses' FROM theses;" + @sqlite3 test.db "SELECT COUNT(*) || ' authors' FROM authors;" + @sqlite3 test.db "SELECT COUNT(*) || ' supervisors' FROM supervisors;" + @sqlite3 test.db "SELECT COUNT(*) || ' keywords' FROM keywords;" + @sqlite3 test.db "SELECT COUNT(*) || ' files uploaded' FROM thesis_files;" + +# Show recent submissions +recent: + @echo "=== Recent Submissions ===" + @sqlite3 -column -header test.db "SELECT identifier, title, year, submitted_at FROM theses ORDER BY submitted_at DESC LIMIT 5;" + +# Query database interactively +query: + @sqlite3 test.db + +# Show full thesis details +show id: + @sqlite3 -column -header test.db "SELECT * FROM v_theses_full WHERE id = {{id}};" + +# Dump database to SQL +dump: + @sqlite3 test.db .dump > test_backup_$(date +%Y%m%d_%H%M%S).sql + @echo "✓ Database dumped to test_backup_$(date +%Y%m%d_%H%M%S).sql" + +# Create data directories if they don't exist +setup-dirs: + @mkdir -p data/theses + @mkdir -p data/covers + @mkdir -p data/yaml + @touch data/theses/.gitkeep + @touch data/covers/.gitkeep + @echo "✓ Data directories created" + +# Full setup: directories + database + serve +dev: setup-dirs init-test-db serve diff --git a/formulaire/list.php b/formulaire/list.php new file mode 100644 index 0000000..4d45c32 --- /dev/null +++ b/formulaire/list.php @@ -0,0 +1,295 @@ +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."); +} +?> + + + + + + Liste des TFE - Post-ERG + + + + + + +
+

Liste des TFE

+ +
+ +
+
+
+
+
TFE total
+
+
+
$t['is_published'])); ?>
+
Publiés
+
+
+
!$t['is_published'])); ?>
+
En attente
+
+
+ +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ + + + Réinitialiser + +
+
+ + +

Aucun TFE trouvé.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDTitreAuteur(s)AnnéeOrientationAPStatutActions
+
+ +
+ +
+ + Publié + + En attente + + +
+ Voir + Éditer +
+
+ +
+ +
+

Post-ERG - TFE dans la base de données

+
+ + diff --git a/formulaire/thanks.php b/formulaire/thanks.php index c274e9c..ec97666 100644 --- a/formulaire/thanks.php +++ b/formulaire/thanks.php @@ -4,79 +4,292 @@ ini_set('display_errors', 0); ini_set('log_errors', 1); ini_set('error_log', 'error.log'); -require 'vendor/autoload.php'; +require __DIR__ . '/Database.php'; -use Symfony\Component\Yaml\Yaml; - -// Security: Validate file parameter to prevent path traversal -$yamlFile = ''; -$data = null; +// Security: Validate thesis ID parameter +$thesisId = null; +$thesis = null; +$files = []; $error = null; -if (isset($_GET['file'])) { - $requestedFile = urldecode($_GET['file']); - - // Security: Only allow files from the yaml directory - $yamlFolder = realpath(__DIR__ . '/data/yaml/'); - $requestedPath = realpath($requestedFile); - - // Verify the file exists and is within the allowed directory - if ($requestedPath && - $yamlFolder && - strpos($requestedPath, $yamlFolder) === 0 && - file_exists($requestedPath) && - pathinfo($requestedPath, PATHINFO_EXTENSION) === 'yaml') { +if (isset($_GET['id'])) { + $thesisId = filter_var($_GET['id'], FILTER_VALIDATE_INT); + if ($thesisId !== false && $thesisId > 0) { try { - $data = Yaml::parseFile($requestedPath); - $yamlFile = $requestedPath; + $db = new Database(); + $pdo = $db->getPDO(); + + // Get thesis data + $thesis = $db->getThesis($thesisId); + + if (!$thesis) { + $error = "TFE non trouvé."; + } else { + // Get associated files + $stmt = $pdo->prepare(" + SELECT file_type, file_name, file_size, mime_type, uploaded_at + FROM thesis_files + WHERE thesis_id = ? + ORDER BY file_type, uploaded_at + "); + $stmt->execute([$thesisId]); + $files = $stmt->fetchAll(); + } } catch (Exception $e) { - error_log("Error parsing YAML file: " . $e->getMessage()); - $error = "Erreur lors de la lecture du fichier."; + error_log("Error loading thesis: " . $e->getMessage()); + $error = "Erreur lors de la lecture des données."; } } else { - error_log("Invalid file access attempt: " . $requestedFile); - $error = "Fichier non valide ou accÚs refusé."; + error_log("Invalid thesis ID: " . $_GET['id']); + $error = "Identifiant invalide."; } } else { - $error = "Aucun fichier spécifié."; + $error = "Aucun identifiant spécifié."; +} + +// Helper function to format file size +function formatFileSize($bytes) { + if ($bytes >= 1073741824) { + return number_format($bytes / 1073741824, 2) . ' GB'; + } elseif ($bytes >= 1048576) { + return number_format($bytes / 1048576, 2) . ' MB'; + } elseif ($bytes >= 1024) { + return number_format($bytes / 1024, 2) . ' KB'; + } else { + return $bytes . ' bytes'; + } } ?> - + - ThankYou + Merci - Post-ERG -
-

Merci 💜

-
-
- -

⚠

-

Pour revenir au formulaire.

- -

d'avoir rempli le formulaire. Le contenu soumis a été sauvegardé et est en attente de traitement.

+

Merci

+ + + + -

Voici les informations que vous avez encodées dans le formulaire, affiché tel que c'est stocké, en yaml:

-
-

Pour revenir au formulaire.

- -

Aucune donnée à afficher.

-

Pour revenir au formulaire.

- -
-
-

Formulaire fait avec ❀ en PHP et SimpleCSS.

-
+
+ +
+

⚠

+

Retour au formulaire

+
+ + +

d'avoir soumis votre TFE. Les informations ont été enregistrées et sont en attente de traitement.

+ +
+

Récapitulatif de votre soumission

+ +

Informations de base

+
+
Identifiant:
+
+ +
Titre:
+
+ + +
Sous-titre:
+
+ + +
Auteur·ice(s):
+
+ +
Année:
+
+
+ +

Détails académiques

+
+
Orientation:
+
+ +
Atelier Pratique:
+
+ +
Finalité:
+
+ + +
Promoteur·ice(s):
+
+ +
+ +

Contenu

+
+ +
Synopsis:
+
+ + + +
Langue(s):
+
+ + + +
Format(s):
+
+ + + +
Mots-clés:
+
+ + + +
Durée/Taille:
+
+ + + +
Lien:
+
+ +
+ +
+ + +

Fichiers téléversés

+ + + + + + + + + + + + + + + + + + + +
TypeNom du fichierTailleDate
+ + +

Statut de publication

+

⏳ En attente - Votre TFE ne sera publiĂ© qu'aprĂšs la soutenance et l'ajout Ă©ventuel d'une note contextuelle par le jury.

+ + +
+ +

Soumettre un autre TFE

+ + +

Aucune donnée à afficher.

+

Retour au formulaire

+ +
+ +
+

Formulaire Post-ERG

+
- \ No newline at end of file + + + diff --git a/front-backend/Database.php b/front-backend/Database.php new file mode 100644 index 0000000..793a105 --- /dev/null +++ b/front-backend/Database.php @@ -0,0 +1,111 @@ +pdo = new PDO('sqlite:' . $dbPath); + $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + } catch (PDOException $e) { + error_log("Database connection failed: " . $e->getMessage()); + throw $e; + } + } + + /** + * Get singleton instance + */ + public static function getInstance() { + if (self::$instance === null) { + self::$instance = new self(); + } + return self::$instance; + } + + /** + * Get PDO connection + */ + public function getConnection() { + return $this->pdo; + } + + /** + * Get all published theses with pagination + */ + public function getPublishedTheses($limit = 10, $offset = 0) { + $sql = "SELECT * FROM v_theses_public ORDER BY year DESC, title ASC LIMIT :limit OFFSET :offset"; + $stmt = $this->pdo->prepare($sql); + $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); + $stmt->bindValue(':offset', $offset, PDO::PARAM_INT); + $stmt->execute(); + return $stmt->fetchAll(); + } + + /** + * Count all published theses + */ + public function countPublishedTheses() { + $sql = "SELECT COUNT(*) as count FROM theses WHERE is_published = 1"; + $stmt = $this->pdo->query($sql); + $result = $stmt->fetch(); + return $result['count']; + } + + /** + * Get thesis by ID with all related data + */ + public function getThesisById($id) { + $sql = "SELECT * FROM v_theses_full WHERE id = :id AND is_published = 1"; + $stmt = $this->pdo->prepare($sql); + $stmt->bindValue(':id', $id, PDO::PARAM_INT); + $stmt->execute(); + $thesis = $stmt->fetch(); + + if (!$thesis) { + return null; + } + + // Get associated files + $thesis['files'] = $this->getThesisFiles($id); + + return $thesis; + } + + /** + * Get files associated with a thesis + */ + public function getThesisFiles($thesisId) { + $sql = "SELECT * FROM thesis_files WHERE thesis_id = :thesis_id ORDER BY file_type, uploaded_at"; + $stmt = $this->pdo->prepare($sql); + $stmt->bindValue(':thesis_id', $thesisId, PDO::PARAM_INT); + $stmt->execute(); + return $stmt->fetchAll(); + } + + /** + * Prevent cloning + */ + private function __clone() {} + + /** + * Prevent unserialization + */ + public function __wakeup() { + throw new Exception("Cannot unserialize singleton"); + } +} diff --git a/front-backend/index.php b/front-backend/index.php index 82c8bbb..c6fa3f8 100644 --- a/front-backend/index.php +++ b/front-backend/index.php @@ -3,61 +3,73 @@ ini_set('display_errors', 0); ini_set('log_errors', 1); ini_set('error_log', 'error.log'); -require_once 'vendor/autoload.php'; - -use Symfony\Component\Yaml\Yaml; +require_once 'Database.php'; $page = isset($_GET['page']) ? intval($_GET['page']) : 1; $itemsPerPage = 10; -$dir = "data/yaml/*.yaml"; -$yamlFiles = glob($dir); -$data = []; - -foreach ($yamlFiles as $yamlFile) { - $data[] = Yaml::parseFile($yamlFile); +try { + $db = Database::getInstance(); + $offset = ($page - 1) * $itemsPerPage; + $itemsToLoad = $db->getPublishedTheses($itemsPerPage, $offset); + $totalItems = $db->countPublishedTheses(); + $totalPages = ceil($totalItems / $itemsPerPage); +} catch (Exception $e) { + error_log("Error loading theses: " . $e->getMessage()); + $itemsToLoad = []; + $totalPages = 0; } -usort($data, function ($a, $b) { - return $a['année'] <=> $b['année']; -}); - -$offset = ($page - 1) * $itemsPerPage; -$itemsToLoad = array_slice($data, $offset, $itemsPerPage); - include 'inc/header.php'; ?>
- $item): ?> +
- +
- + getThesisFiles($item['id']); + foreach ($files as $file) { + $ext = strtolower(pathinfo($file['file_path'], PATHINFO_EXTENSION)); + if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif']) && $file['file_type'] === 'main') { + $coverImage = $file['file_path']; + break; + } + } + } + ?> +
- Image preview + Image preview

- +

- -

+ +

+ +

$excerpt_length + ? substr($synopsis, 0, $excerpt_length) . '...' + : $synopsis; ?> - +

diff --git a/front-backend/justfile b/front-backend/justfile new file mode 100644 index 0000000..4467af5 --- /dev/null +++ b/front-backend/justfile @@ -0,0 +1,53 @@ +# Justfile for Post-ERG front-backend website + +# Default recipe - show available commands +default: + @just --list + +# Start PHP development server +serve: + @echo "Starting PHP development server on http://localhost:8000" + @echo "Using database: ../formulaire/test.db" + @echo "Press Ctrl+C to stop" + @php -S 127.0.0.1:8000 + +# Test database connection +test: + @echo "Testing database connection..." + @php test_db.php + +# Show database statistics +stats: + @echo "=== Database Statistics ===" + @sqlite3 ../formulaire/test.db "SELECT COUNT(*) || ' total theses' FROM theses;" + @sqlite3 ../formulaire/test.db "SELECT COUNT(*) || ' published theses' FROM theses WHERE is_published = 1;" + @sqlite3 ../formulaire/test.db "SELECT COUNT(*) || ' authors' FROM authors;" + @sqlite3 ../formulaire/test.db "SELECT COUNT(*) || ' keywords' FROM keywords;" + +# Show recent published theses +recent: + @echo "=== Recent Published Theses ===" + @sqlite3 -column -header ../formulaire/test.db "SELECT id, title, year, authors FROM v_theses_public ORDER BY year DESC, title LIMIT 10;" + +# Query database interactively +query: + @sqlite3 ../formulaire/test.db + +# Show specific thesis details +show id: + @sqlite3 -column -header ../formulaire/test.db "SELECT * FROM v_theses_full WHERE id = {{id}};" + +# Check PHP syntax for all PHP files +check: + @echo "Checking PHP syntax..." + @php -l Database.php + @php -l index.php + @php -l memoire.php + @php -l apropos.php + @php -l contact.php + @php -l licences.php + @echo "✓ All files have valid syntax" + +# View error log +logs: + @if [ -f error.log ]; then tail -n 50 error.log; else echo "No error log found"; fi diff --git a/front-backend/memoire.php b/front-backend/memoire.php index 8e05c40..0cb82e0 100644 --- a/front-backend/memoire.php +++ b/front-backend/memoire.php @@ -5,16 +5,27 @@ ini_set('log_errors', 1); ini_set('error_log', 'error.log'); // Load required libraries and classes -require_once 'vendor/autoload.php'; -use Symfony\Component\Yaml\Yaml; +require_once 'Database.php'; -// Check if a file parameter is provided in the URL -if (isset($_GET['file'])) { - // Decode the URL parameter and parse the YAML file - $yamlFile = urldecode($_GET['file']); - $data = Yaml::parseFile($yamlFile); +// Check if an id parameter is provided in the URL +if (isset($_GET['id'])) { + $thesisId = intval($_GET['id']); + try { + $db = Database::getInstance(); + $data = $db->getThesisById($thesisId); + + if (!$data) { + // Thesis not found or not published + header('Location: index.php'); + exit; + } + } catch (Exception $e) { + error_log("Error loading thesis: " . $e->getMessage()); + header('Location: index.php'); + exit; + } } else { - // Redirect to the index page if no file parameter is provided + // Redirect to the index page if no id parameter is provided header('Location: index.php'); exit; } @@ -29,74 +40,119 @@ include 'inc/header.php'; ?>
- +

- + + +
+

par - +

-

- et - -

+ +

+ + + + + et + + + + +

+

- +

+ +

+ Finalité: +

+
-

- -

+ +

+ +

+ -

- Contact: - -

-

- Promoteur.ice.s: - -

+ +

+ Promoteur.ice.s: + +

+ + + +

+ Langue(s): + +

+ + + +

+ Format(s): + +

+ + + +

+ Mots-clés: + +

+
- + + +
- - + + 0): ?> - +
- +
- Image file + <?= htmlspecialchars($file['file_name']); ?>
+ +

+
+ +

Aucun fichier disponible pour ce mémoire.

diff --git a/front-backend/test_db.php b/front-backend/test_db.php new file mode 100644 index 0000000..bf849f1 --- /dev/null +++ b/front-backend/test_db.php @@ -0,0 +1,40 @@ +countPublishedTheses(); + echo "✓ Found {$count} published theses\n"; + + // Test getting theses + $theses = $db->getPublishedTheses(5, 0); + echo "✓ Retrieved " . count($theses) . " theses\n"; + + if (count($theses) > 0) { + $first = $theses[0]; + echo "\nFirst thesis:\n"; + echo " ID: " . $first['id'] . "\n"; + echo " Title: " . $first['title'] . "\n"; + echo " Author(s): " . ($first['authors'] ?? 'N/A') . "\n"; + echo " Year: " . $first['year'] . "\n"; + + // Test getting single thesis + $thesis = $db->getThesisById($first['id']); + if ($thesis) { + echo "✓ Successfully retrieved thesis details\n"; + echo " Orientation: " . ($thesis['orientation'] ?? 'N/A') . "\n"; + echo " AP Program: " . ($thesis['ap_program'] ?? 'N/A') . "\n"; + echo " Files: " . (count($thesis['files'] ?? [])) . "\n"; + } + } + + echo "\n✓ All tests passed!\n"; + +} catch (Exception $e) { + echo "✗ Error: " . $e->getMessage() . "\n"; + exit(1); +} diff --git a/justfile b/justfile index 9b2a722..dd5e942 100644 --- a/justfile +++ b/justfile @@ -1,2 +1,4 @@ sync: - rsync -vur --progress ./ posterg:/var/www/html/ + rsync -vur --progress ./front-backend/ posterg:/var/www/html/ + rsync -vur --progress ./formulaire/ posterg:/var/www/html/formulaire/ +