mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 11:09:18 +02:00
Major refactor
- update the structure to have monolithic setup - updated deployments - added live-reloading for devops
This commit is contained in:
11
.gitignore
vendored
11
.gitignore
vendored
@@ -19,3 +19,14 @@ front-backend/data/cover/*
|
||||
### Logs ###
|
||||
formulaire/error.log
|
||||
|
||||
|
||||
# Vendor directory (third-party code)
|
||||
vendor/
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
|
Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 174 KiB |
@@ -7,7 +7,7 @@ if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../../shared/Database.php';
|
||||
require_once __DIR__ . '/../lib/Database.php';
|
||||
|
||||
$thesisId = isset($_GET['id']) ? intval($_GET['id']) : 0;
|
||||
$error = null;
|
||||
@@ -18,7 +18,7 @@ if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) ||
|
||||
// Log the content of the $_FILES array
|
||||
error_log("FILES array: " . print_r($_FILES, true));
|
||||
|
||||
require_once __DIR__ . '/../../shared/Database.php';
|
||||
require_once __DIR__ . '/../lib/Database.php';
|
||||
|
||||
// Helper function to sanitize string input
|
||||
function sanitize_string($input) {
|
||||
@@ -9,7 +9,7 @@ if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../../shared/Database.php';
|
||||
require_once __DIR__ . '/../lib/Database.php';
|
||||
|
||||
$message = '';
|
||||
$errors = [];
|
||||
300
admin/index.php
Normal file
300
admin/index.php
Normal file
@@ -0,0 +1,300 @@
|
||||
<?php
|
||||
// Start session and generate CSRF token
|
||||
session_start();
|
||||
if (empty($_SESSION["csrf_token"])) {
|
||||
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
// Load database helper
|
||||
require_once __DIR__ . '/../lib/Database.php';
|
||||
|
||||
try {
|
||||
$db = new Database();
|
||||
$orientations = $db->getAllOrientations();
|
||||
$apPrograms = $db->getAllAPPrograms();
|
||||
$finalityTypes = $db->getAllFinalityTypes();
|
||||
$languages = $db->getAllLanguages();
|
||||
$formatTypes = $db->getAllFormatTypes();
|
||||
} catch (Exception $e) {
|
||||
error_log("Failed to load form data: " . $e->getMessage());
|
||||
die("Erreur lors du chargement du formulaire. Veuillez réessayer plus tard.");
|
||||
}
|
||||
|
||||
// Get error message and preserved form data from session (if redirected back from error)
|
||||
$error = isset($_SESSION["form_error"]) ? $_SESSION["form_error"] : null;
|
||||
$formData = isset($_SESSION["form_data"]) ? $_SESSION["form_data"] : [];
|
||||
|
||||
// Clear session data after retrieving
|
||||
unset($_SESSION["form_error"]);
|
||||
unset($_SESSION["form_data"]);
|
||||
|
||||
// Helper function to get old form value
|
||||
function old($key, $default = "")
|
||||
{
|
||||
global $formData;
|
||||
return isset($formData[$key])
|
||||
? htmlspecialchars($formData[$key])
|
||||
: $default;
|
||||
}
|
||||
|
||||
// Helper function to check if value was previously selected
|
||||
function wasSelected($key, $value)
|
||||
{
|
||||
global $formData;
|
||||
if (!isset($formData[$key])) {
|
||||
return false;
|
||||
}
|
||||
if (is_array($formData[$key])) {
|
||||
return in_array($value, $formData[$key]);
|
||||
}
|
||||
return $formData[$key] == $value;
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Formulaire</title>
|
||||
<link rel="stylesheet" href="/assets/modern-normalize.css">
|
||||
<link rel="stylesheet" href="https://raw.githack.com/waldyrious/downstyler/master/downstyler.css" />
|
||||
<link rel="shortcut icon" href="assets/icon.svg" type="image/svg">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<h1>Formulaire Posterg</h1>
|
||||
<nav style="margin-top: 1rem;">
|
||||
<a href="list.php" style="font-size: 0.9em;">📋 Liste des TFE</a> |
|
||||
<a href="import.php" style="font-size: 0.9em;">📥 Importer CSV</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<?php if ($error): ?>
|
||||
<div class="error-message" style="background: #fee; border: 2px solid #c00; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; color: #c00;">
|
||||
<strong>⚠️ Erreur:</strong> <?php echo htmlspecialchars($error); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="formulaire.php" method="post" enctype="multipart/form-data">
|
||||
<!-- CSRF Protection -->
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars(
|
||||
$_SESSION["csrf_token"],
|
||||
); ?>">
|
||||
|
||||
<h2>Informations de base</h2>
|
||||
|
||||
<fieldset>
|
||||
<label for="auteurice">Nom/Prénom/Pseudo *</label>
|
||||
<input type="text" id="auteurice" name="auteurice" placeholder="Nom de l'auteur·ice" value="<?php echo old(
|
||||
"auteurice",
|
||||
); ?>" required>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="mail">Contact (email, site web, insta, ...)</label>
|
||||
<input type="text" id="mail" name="mail" placeholder="votre.email@example.com ou @instagram" value="<?php echo old(
|
||||
"mail",
|
||||
); ?>">
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="année">Année diplômante *</label>
|
||||
<input type="number" id="année" name="année" min="2000" max="<?php echo date(
|
||||
"Y",
|
||||
) + 1; ?>" placeholder="<?php echo date(
|
||||
"Y",
|
||||
); ?>" value="<?php echo old("année"); ?>" required>
|
||||
</fieldset>
|
||||
|
||||
<h2>Informations académiques</h2>
|
||||
|
||||
|
||||
<fieldset>
|
||||
<label for="orientation">Orientation principale *</label>
|
||||
<select id="orientation" name="orientation" required>
|
||||
<option value="">-- Sélectionner une orientation --</option>
|
||||
<?php foreach ($orientations as $orientation): ?>
|
||||
<option value="<?php echo htmlspecialchars(
|
||||
$orientation["id"],
|
||||
); ?>" <?php echo wasSelected(
|
||||
"orientation",
|
||||
$orientation["id"],
|
||||
)
|
||||
? "selected"
|
||||
: ""; ?>>
|
||||
<?php echo htmlspecialchars(
|
||||
$orientation["name"],
|
||||
); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<fieldset>
|
||||
<label for="ap">Atelier Pratique (AP) *</label>
|
||||
<select id="ap" name="ap" required>
|
||||
<option value="">-- Sélectionner un AP --</option>
|
||||
<?php foreach ($apPrograms as $ap): ?>
|
||||
<option value="<?php echo htmlspecialchars(
|
||||
$ap["id"],
|
||||
); ?>" <?php echo wasSelected("ap", $ap["id"])
|
||||
? "selected"
|
||||
: ""; ?>>
|
||||
<?php echo htmlspecialchars($ap["name"]); ?>
|
||||
<?php if (
|
||||
$ap["code"]
|
||||
): ?> (<?php echo htmlspecialchars(
|
||||
$ap["code"],
|
||||
); ?>)<?php endif; ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="finality">Finalité du master *</label>
|
||||
<select id="finality" name="finality" required>
|
||||
<option value="">-- Sélectionner une finalité --</option>
|
||||
<?php foreach ($finalityTypes as $finality): ?>
|
||||
<option value="<?php echo htmlspecialchars(
|
||||
$finality["id"],
|
||||
); ?>" <?php echo wasSelected(
|
||||
"finality",
|
||||
$finality["id"],
|
||||
)
|
||||
? "selected"
|
||||
: ""; ?>>
|
||||
<?php echo htmlspecialchars($finality["name"]); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="promoteurice">Promoteur·ice(s)</label>
|
||||
<input type="text" id="promoteurice" name="promoteurice" placeholder="Nom du/de la promoteur·ice (si plusieurs, séparer par des virgules)" value="<?php echo old(
|
||||
"promoteurice",
|
||||
); ?>">
|
||||
|
||||
</fieldset>
|
||||
|
||||
<h2>À propos du TFE</h2>
|
||||
|
||||
|
||||
<fieldset>
|
||||
<label for="titre">Titre du mémoire *</label>
|
||||
<input type="text" id="titre" name="titre" placeholder="Titre de votre TFE" value="<?php echo old(
|
||||
"titre",
|
||||
); ?>" required>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="subtitle">Sous-titre (si applicable)</label>
|
||||
<input type="text" id="subtitle" name="subtitle" placeholder="Sous-titre de votre TFE" value="<?php echo old(
|
||||
"subtitle",
|
||||
); ?>">
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="synopsis">Synopsis (environ 200 mots) *</label>
|
||||
<textarea id="synopsis" name="synopsis" rows="8" placeholder="Décrivez votre TFE en quelques paragraphes..." required><?php echo old(
|
||||
"synopsis",
|
||||
); ?></textarea>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="problématique">Problématique</label>
|
||||
<textarea id="problématique" name="problématique" rows="4" placeholder="La problématique principale de votre mémoire..."><?php echo old(
|
||||
"problématique",
|
||||
); ?></textarea>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label>Langue(s) du TFE * (sélection multiple possible)</label>
|
||||
<?php foreach ($languages as $language): ?>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" name="languages[]" value="<?php echo htmlspecialchars(
|
||||
$language["id"],
|
||||
); ?>" <?php echo wasSelected(
|
||||
"languages",
|
||||
$language["id"],
|
||||
)
|
||||
? "checked"
|
||||
: ""; ?>>
|
||||
<?php echo htmlspecialchars($language["name"]); ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label>Format(s) (sélection multiple possible)</label>
|
||||
<?php foreach ($formatTypes as $format): ?>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" name="formats[]" value="<?php echo htmlspecialchars(
|
||||
$format["id"],
|
||||
); ?>" <?php echo wasSelected(
|
||||
"formats",
|
||||
$format["id"],
|
||||
)
|
||||
? "checked"
|
||||
: ""; ?>>
|
||||
<?php echo htmlspecialchars($format["name"]); ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="tag">Mots-clés (max 10, séparés par des virgules)</label>
|
||||
<input type="text" id="tag" name="tag" placeholder="typographie, photographie, outils libre, post-colonial..." value="<?php echo old(
|
||||
"tag",
|
||||
); ?>">
|
||||
<small>Séparez les mots-clés par des virgules. Maximum 10 mots-clés.</small>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="duration_info">Durée/Taille (si applicable)</label>
|
||||
<input type="text" id="duration_info" name="duration_info" placeholder="Ex: 68 minutes, 128 pages, 78 pages + 15 minutes" value="<?php echo old(
|
||||
"duration_info",
|
||||
); ?>">
|
||||
<small>Indiquez la durée (en minutes) ou le nombre de pages de votre TFE.</small>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="lien">Lien vers un site web ou ressource en ligne</label>
|
||||
<input type="url" id="lien" name="lien" placeholder="https://monmemoire.erg.be/..." value="<?php echo old(
|
||||
"lien",
|
||||
); ?>">
|
||||
</fieldset>
|
||||
|
||||
<h2>Fichiers</h2>
|
||||
|
||||
<fieldset>
|
||||
<label for="couverture">Importer une image de couverture</label>
|
||||
<small>Formats acceptés : JPG, PNG. Taille max : 10MB.</small>
|
||||
<input type="file" id="couverture" name="couverture" accept="image/jpeg,image/png">
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="files">Importer le TFE et les fichiers annexes</label>
|
||||
<small>Formats acceptés : PDF, JPG, PNG, MP4, ZIP. Taille max par fichier : 50MB.</small>
|
||||
<small>Si vous voulez importer un dossier, créez une archive ZIP.</small>
|
||||
<input type="file" id="files" name="files[]" multiple accept=".pdf,.jpg,.jpeg,.png,.mp4,.zip">
|
||||
</fieldset>
|
||||
|
||||
<br>
|
||||
<input type="submit" name="go" value="Soumettre mon TFE">
|
||||
</form>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>Formulaire fait avec ❤ en PHP et <a href="https://github.com/kevquirk/simple.css">SimpleCSS</a>.</p>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -7,7 +7,7 @@ if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../../shared/Database.php';
|
||||
require_once __DIR__ . '/../lib/Database.php';
|
||||
|
||||
try {
|
||||
$db = new Database();
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
session_start();
|
||||
|
||||
require_once __DIR__ . '/../../shared/Database.php';
|
||||
require_once __DIR__ . '/../lib/Database.php';
|
||||
|
||||
// Verify CSRF token
|
||||
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
||||
@@ -4,7 +4,7 @@ ini_set('display_errors', 0);
|
||||
ini_set('log_errors', 1);
|
||||
ini_set('error_log', 'error.log');
|
||||
|
||||
require __DIR__ . '/../../shared/Database.php';
|
||||
require __DIR__ . '/../lib/Database.php';
|
||||
|
||||
// Security: Validate thesis ID parameter
|
||||
$thesisId = null;
|
||||
349
apps/admin/assets/normalize.css
vendored
349
apps/admin/assets/normalize.css
vendored
@@ -1,349 +0,0 @@
|
||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
/* Document
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Correct the line height in all browsers.
|
||||
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/* Sections
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the `main` element consistently in IE.
|
||||
*/
|
||||
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background on active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Chrome 57-
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Change the font styles in all browsers.
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
|
||||
button,
|
||||
input { /* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select { /* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the padding in Firefox.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE 10+.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10.
|
||||
* 2. Remove the padding in IE 10.
|
||||
*/
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Add the correct display in Edge, IE 10+, and Firefox.
|
||||
*/
|
||||
|
||||
details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Misc
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10+.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
@@ -1,673 +0,0 @@
|
||||
/* Global variables. */
|
||||
:root,
|
||||
::backdrop {
|
||||
/* Set sans-serif & mono fonts */
|
||||
--sans-font: -apple-system, BlinkMacSystemFont, "Avenir Next", Avenir,
|
||||
"Nimbus Sans L", Roboto, "Noto Sans", "Segoe UI", Arial, Helvetica,
|
||||
"Helvetica Neue", sans-serif;
|
||||
--mono-font: Consolas, Menlo, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
|
||||
--standard-border-radius: 5px;
|
||||
|
||||
/* Default (light) theme */
|
||||
--bg: #fff;
|
||||
--accent-bg: #f5f7ff;
|
||||
--text: #212121;
|
||||
--text-light: #585858;
|
||||
--border: #898EA4;
|
||||
--accent: #0d47a1;
|
||||
--code: #d81b60;
|
||||
--preformatted: #444;
|
||||
--marked: #ffdd33;
|
||||
--disabled: #efefef;
|
||||
}
|
||||
|
||||
/* Dark theme */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root,
|
||||
::backdrop {
|
||||
color-scheme: dark;
|
||||
--bg: #212121;
|
||||
--accent-bg: #2b2b2b;
|
||||
--text: #dcdcdc;
|
||||
--text-light: #ababab;
|
||||
--accent: #ffb300;
|
||||
--code: #f06292;
|
||||
--preformatted: #ccc;
|
||||
--disabled: #111;
|
||||
}
|
||||
/* Add a bit of transparency so light media isn't so glaring in dark mode */
|
||||
img,
|
||||
video {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reset box-sizing */
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Reset default appearance */
|
||||
textarea,
|
||||
select,
|
||||
input,
|
||||
progress {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
}
|
||||
|
||||
html {
|
||||
/* Set the font globally */
|
||||
font-family: var(--sans-font);
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Make the body a nice central block */
|
||||
body {
|
||||
color: var(--text);
|
||||
background-color: var(--bg);
|
||||
font-size: 1.15rem;
|
||||
line-height: 1.5;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr min(45rem, 90%) 1fr;
|
||||
margin: 0;
|
||||
}
|
||||
body > * {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
/* Make the header bg full width, but the content inline with body */
|
||||
body > header {
|
||||
background-color: var(--accent-bg);
|
||||
border-bottom: 1px solid var(--border);
|
||||
text-align: center;
|
||||
padding: 0 0.5rem 2rem 0.5rem;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
body > header h1 {
|
||||
max-width: 1200px;
|
||||
margin: 1rem auto;
|
||||
}
|
||||
|
||||
body > header p {
|
||||
max-width: 40rem;
|
||||
margin: 1rem auto;
|
||||
}
|
||||
|
||||
/* Add a little padding to ensure spacing is correct between content and header > nav */
|
||||
main {
|
||||
padding-top: 1.5rem;
|
||||
}
|
||||
|
||||
body > footer {
|
||||
margin-top: 4rem;
|
||||
padding: 2rem 1rem 1.5rem 1rem;
|
||||
color: var(--text-light);
|
||||
font-size: 0.9rem;
|
||||
text-align: center;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
/* Format headers */
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2.6rem;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 2rem;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.44rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 0.96rem;
|
||||
}
|
||||
|
||||
/* Prevent long strings from overflowing container */
|
||||
p, h1, h2, h3, h4, h5, h6 {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Fix line height when title wraps */
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
/* Reduce header size on mobile */
|
||||
@media only screen and (max-width: 720px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2.1rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Format links & buttons */
|
||||
a,
|
||||
a:visited {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button,
|
||||
[role="button"],
|
||||
input[type="submit"],
|
||||
input[type="reset"],
|
||||
input[type="button"],
|
||||
label[type="button"] {
|
||||
border: none;
|
||||
border-radius: var(--standard-border-radius);
|
||||
background-color: var(--accent);
|
||||
font-size: 1rem;
|
||||
color: var(--bg);
|
||||
padding: 0.7rem 0.9rem;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
button[disabled],
|
||||
[role="button"][aria-disabled="true"],
|
||||
input[type="submit"][disabled],
|
||||
input[type="reset"][disabled],
|
||||
input[type="button"][disabled],
|
||||
input[type="checkbox"][disabled],
|
||||
input[type="radio"][disabled],
|
||||
select[disabled] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
input:disabled,
|
||||
textarea:disabled,
|
||||
select:disabled,
|
||||
button[disabled] {
|
||||
cursor: not-allowed;
|
||||
background-color: var(--disabled);
|
||||
color: var(--text-light)
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Set the cursor to '?' on an abbreviation and style the abbreviation to show that there is more information underneath */
|
||||
abbr[title] {
|
||||
cursor: help;
|
||||
text-decoration-line: underline;
|
||||
text-decoration-style: dotted;
|
||||
}
|
||||
|
||||
button:enabled:hover,
|
||||
[role="button"]:not([aria-disabled="true"]):hover,
|
||||
input[type="submit"]:enabled:hover,
|
||||
input[type="reset"]:enabled:hover,
|
||||
input[type="button"]:enabled:hover,
|
||||
label[type="button"]:hover {
|
||||
filter: brightness(1.4);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:focus-visible:where(:enabled, [role="button"]:not([aria-disabled="true"])),
|
||||
input:enabled:focus-visible:where(
|
||||
[type="submit"],
|
||||
[type="reset"],
|
||||
[type="button"]
|
||||
) {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
/* Format navigation */
|
||||
header > nav {
|
||||
font-size: 1rem;
|
||||
line-height: 2;
|
||||
padding: 1rem 0 0 0;
|
||||
}
|
||||
|
||||
/* Use flexbox to allow items to wrap, as needed */
|
||||
header > nav ul,
|
||||
header > nav ol {
|
||||
align-content: space-around;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* List items are inline elements, make them behave more like blocks */
|
||||
header > nav ul li,
|
||||
header > nav ol li {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
header > nav a,
|
||||
header > nav a:visited {
|
||||
margin: 0 0.5rem 1rem 0.5rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--standard-border-radius);
|
||||
color: var(--text);
|
||||
display: inline-block;
|
||||
padding: 0.1rem 1rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
header > nav a:hover {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Reduce nav side on mobile */
|
||||
@media only screen and (max-width: 720px) {
|
||||
header > nav a {
|
||||
border: none;
|
||||
padding: 0;
|
||||
text-decoration: underline;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Consolidate box styling */
|
||||
aside, details, pre, progress {
|
||||
background-color: var(--accent-bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--standard-border-radius);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
aside {
|
||||
font-size: 1rem;
|
||||
width: 30%;
|
||||
padding: 0 15px;
|
||||
margin-left: 15px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* Make aside full-width on mobile */
|
||||
@media only screen and (max-width: 720px) {
|
||||
aside {
|
||||
width: 100%;
|
||||
float: none;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
article, fieldset, dialog {
|
||||
border: 1px solid var(--border);
|
||||
padding: 1rem;
|
||||
border-radius: var(--standard-border-radius);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
article h2:first-child,
|
||||
section h2:first-child {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
section {
|
||||
border-top: 1px solid var(--border);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 2rem 1rem;
|
||||
margin: 3rem 0;
|
||||
}
|
||||
|
||||
/* Don't double separators when chaining sections */
|
||||
section + section,
|
||||
section:first-child {
|
||||
border-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
section:last-child {
|
||||
border-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
details {
|
||||
padding: 0.7rem 1rem;
|
||||
}
|
||||
|
||||
summary {
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
padding: 0.7rem 1rem;
|
||||
margin: -0.7rem -1rem;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
details[open] > summary + * {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
details[open] > summary {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
details[open] > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Format tables */
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
border: 1px solid var(--border);
|
||||
text-align: left;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: var(--accent-bg);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
/* Set every other cell slightly darker. Improves readability. */
|
||||
background-color: var(--accent-bg);
|
||||
}
|
||||
|
||||
table caption {
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* Format forms */
|
||||
textarea,
|
||||
select,
|
||||
input {
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
padding: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--text);
|
||||
background-color: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--standard-border-radius);
|
||||
box-shadow: none;
|
||||
max-width: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
textarea:not([cols]) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Add arrow to drop-down */
|
||||
select:not([multiple]) {
|
||||
background-image: linear-gradient(45deg, transparent 49%, var(--text) 51%),
|
||||
linear-gradient(135deg, var(--text) 51%, transparent 49%);
|
||||
background-position: calc(100% - 15px), calc(100% - 10px);
|
||||
background-size: 5px 5px, 5px 5px;
|
||||
background-repeat: no-repeat;
|
||||
padding-right: 25px;
|
||||
}
|
||||
|
||||
/* checkbox and radio button style */
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
input[type="checkbox"] + label,
|
||||
input[type="radio"] + label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked,
|
||||
input[type="radio"]:checked {
|
||||
background-color: var(--accent);
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked::after {
|
||||
/* Creates a rectangle with colored right and bottom borders which is rotated to look like a check mark */
|
||||
content: " ";
|
||||
width: 0.18em;
|
||||
height: 0.32em;
|
||||
border-radius: 0;
|
||||
position: absolute;
|
||||
top: 0.05em;
|
||||
left: 0.17em;
|
||||
background-color: transparent;
|
||||
border-right: solid var(--bg) 0.08em;
|
||||
border-bottom: solid var(--bg) 0.08em;
|
||||
font-size: 1.8em;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
input[type="radio"]:checked::after {
|
||||
/* creates a colored circle for the checked radio button */
|
||||
content: " ";
|
||||
width: 0.25em;
|
||||
height: 0.25em;
|
||||
border-radius: 100%;
|
||||
position: absolute;
|
||||
top: 0.125em;
|
||||
background-color: var(--bg);
|
||||
left: 0.125em;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
/* Makes input fields wider on smaller screens */
|
||||
@media only screen and (max-width: 720px) {
|
||||
textarea,
|
||||
select,
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set a height for color input */
|
||||
input[type="color"] {
|
||||
height: 2.5rem;
|
||||
padding: 0.2rem;
|
||||
}
|
||||
|
||||
/* do not show border around file selector button */
|
||||
input[type="file"] {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Misc body elements */
|
||||
hr {
|
||||
border: none;
|
||||
height: 1px;
|
||||
background: var(--border);
|
||||
margin: 1rem auto;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 2px 5px;
|
||||
border-radius: var(--standard-border-radius);
|
||||
background-color: var(--marked);
|
||||
color: black;
|
||||
}
|
||||
|
||||
img,
|
||||
video {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: var(--standard-border-radius);
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0;
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-light);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 2rem 0 2rem 2rem;
|
||||
padding: 0.4rem 0.8rem;
|
||||
border-left: 0.35rem solid var(--accent);
|
||||
color: var(--text-light);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
cite {
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-light);
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
dt {
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
/* Use mono font for code elements */
|
||||
code,
|
||||
pre,
|
||||
pre span,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--mono-font);
|
||||
color: var(--code);
|
||||
}
|
||||
|
||||
kbd {
|
||||
color: var(--preformatted);
|
||||
border: 1px solid var(--preformatted);
|
||||
border-bottom: 3px solid var(--preformatted);
|
||||
border-radius: var(--standard-border-radius);
|
||||
padding: 0.1rem 0.4rem;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 1rem 1.4rem;
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
color: var(--preformatted);
|
||||
}
|
||||
|
||||
/* Fix embedded code within pre */
|
||||
pre code {
|
||||
color: var(--preformatted);
|
||||
background: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Progress bars */
|
||||
/* Declarations are repeated because you */
|
||||
/* cannot combine vendor-specific selectors */
|
||||
progress {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
progress:indeterminate {
|
||||
background-color: var(--accent-bg);
|
||||
}
|
||||
|
||||
progress::-webkit-progress-bar {
|
||||
border-radius: var(--standard-border-radius);
|
||||
background-color: var(--accent-bg);
|
||||
}
|
||||
|
||||
progress::-webkit-progress-value {
|
||||
border-radius: var(--standard-border-radius);
|
||||
background-color: var(--accent);
|
||||
}
|
||||
|
||||
progress::-moz-progress-bar {
|
||||
border-radius: var(--standard-border-radius);
|
||||
background-color: var(--accent);
|
||||
transition-property: width;
|
||||
transition-duration: 0.3s;
|
||||
}
|
||||
|
||||
progress:indeterminate::-moz-progress-bar {
|
||||
background-color: var(--accent-bg);
|
||||
}
|
||||
|
||||
dialog {
|
||||
max-width: 40rem;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
dialog::backdrop {
|
||||
background-color: var(--bg);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 720px) {
|
||||
dialog {
|
||||
max-width: 100%;
|
||||
margin: auto 1em;
|
||||
}
|
||||
}
|
||||
|
||||
/* Classes for buttons and notices */
|
||||
.button,
|
||||
.button:visited {
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background: var(--accent);
|
||||
font-size: 1rem;
|
||||
color: var(--bg);
|
||||
padding: 0.7rem 0.9rem;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.button:hover,
|
||||
.button:focus {
|
||||
filter: brightness(1.4);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.notice {
|
||||
background: var(--accent-bg);
|
||||
border: 2px solid var(--border);
|
||||
border-radius: 5px;
|
||||
padding: 1.5rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"require": {
|
||||
"symfony/polyfill-iconv": "^1.27",
|
||||
"symfony/yaml": "^6.2",
|
||||
"symfony/intl": "^6.2",
|
||||
"behat/transliterator": "^1.5"
|
||||
}
|
||||
}
|
||||
@@ -1,301 +0,0 @@
|
||||
<?php
|
||||
// Start session and generate CSRF token
|
||||
session_start();
|
||||
if (empty($_SESSION["csrf_token"])) {
|
||||
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
// Load database helper
|
||||
require_once __DIR__ . '/../../shared/Database.php';
|
||||
|
||||
try {
|
||||
$db = new Database();
|
||||
$orientations = $db->getAllOrientations();
|
||||
$apPrograms = $db->getAllAPPrograms();
|
||||
$finalityTypes = $db->getAllFinalityTypes();
|
||||
$languages = $db->getAllLanguages();
|
||||
$formatTypes = $db->getAllFormatTypes();
|
||||
} catch (Exception $e) {
|
||||
error_log("Failed to load form data: " . $e->getMessage());
|
||||
die(
|
||||
"Erreur lors du chargement du formulaire. Veuillez réessayer plus tard."
|
||||
);
|
||||
}
|
||||
|
||||
// Get error message and preserved form data from session (if redirected back from error)
|
||||
$error = isset($_SESSION["form_error"]) ? $_SESSION["form_error"] : null;
|
||||
$formData = isset($_SESSION["form_data"]) ? $_SESSION["form_data"] : [];
|
||||
|
||||
// Clear session data after retrieving
|
||||
unset($_SESSION["form_error"]);
|
||||
unset($_SESSION["form_data"]);
|
||||
|
||||
// Helper function to get old form value
|
||||
function old($key, $default = "")
|
||||
{
|
||||
global $formData;
|
||||
return isset($formData[$key])
|
||||
? htmlspecialchars($formData[$key])
|
||||
: $default;
|
||||
}
|
||||
|
||||
// Helper function to check if value was previously selected
|
||||
function wasSelected($key, $value)
|
||||
{
|
||||
global $formData;
|
||||
if (!isset($formData[$key])) {
|
||||
return false;
|
||||
}
|
||||
if (is_array($formData[$key])) {
|
||||
return in_array($value, $formData[$key]);
|
||||
}
|
||||
return $formData[$key] == $value;
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Formulaire</title>
|
||||
<link rel="stylesheet" href="assets/normalize.css">
|
||||
<link rel="stylesheet" href="https://raw.githack.com/waldyrious/downstyler/master/downstyler.css" />
|
||||
<!-- <link rel="stylesheet" href="assets/simple.css"> -->
|
||||
<!--<link rel="stylesheet" href="assets/posterg.css"> -->
|
||||
<link rel="shortcut icon" href="assets/icon.svg" type="image/svg">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Formulaire Posterg</h1>
|
||||
<nav style="margin-top: 1rem;">
|
||||
<a href="list.php" style="font-size: 0.9em;">📋 Liste des TFE</a> |
|
||||
<a href="import.php" style="font-size: 0.9em;">📥 Importer CSV</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<?php if ($error): ?>
|
||||
<div class="error-message" style="background: #fee; border: 2px solid #c00; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; color: #c00;">
|
||||
<strong>⚠️ Erreur:</strong> <?php echo htmlspecialchars($error); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="formulaire.php" method="post" enctype="multipart/form-data">
|
||||
<!-- CSRF Protection -->
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars(
|
||||
$_SESSION["csrf_token"],
|
||||
); ?>">
|
||||
|
||||
<h2>Informations de base</h2>
|
||||
|
||||
<fieldset>
|
||||
<label for="auteurice">Nom/Prénom/Pseudo *</label>
|
||||
<input type="text" id="auteurice" name="auteurice" placeholder="Nom de l'auteur·ice" value="<?php echo old(
|
||||
"auteurice",
|
||||
); ?>" required>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="mail">Contact (email, site web, insta, ...)</label>
|
||||
<input type="text" id="mail" name="mail" placeholder="votre.email@example.com ou @instagram" value="<?php echo old(
|
||||
"mail",
|
||||
); ?>">
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="année">Année diplômante *</label>
|
||||
<input type="number" id="année" name="année" min="2000" max="<?php echo date(
|
||||
"Y",
|
||||
) + 1; ?>" placeholder="<?php echo date(
|
||||
"Y",
|
||||
); ?>" value="<?php echo old("année"); ?>" required>
|
||||
</fieldset>
|
||||
|
||||
<h2>Informations académiques</h2>
|
||||
|
||||
|
||||
<fieldset>
|
||||
<label for="orientation">Orientation principale *</label>
|
||||
<select id="orientation" name="orientation" required>
|
||||
<option value="">-- Sélectionner une orientation --</option>
|
||||
<?php foreach ($orientations as $orientation): ?>
|
||||
<option value="<?php echo htmlspecialchars(
|
||||
$orientation["id"],
|
||||
); ?>" <?php echo wasSelected(
|
||||
"orientation",
|
||||
$orientation["id"],
|
||||
)
|
||||
? "selected"
|
||||
: ""; ?>>
|
||||
<?php echo htmlspecialchars(
|
||||
$orientation["name"],
|
||||
); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<fieldset>
|
||||
<label for="ap">Atelier Pratique (AP) *</label>
|
||||
<select id="ap" name="ap" required>
|
||||
<option value="">-- Sélectionner un AP --</option>
|
||||
<?php foreach ($apPrograms as $ap): ?>
|
||||
<option value="<?php echo htmlspecialchars(
|
||||
$ap["id"],
|
||||
); ?>" <?php echo wasSelected("ap", $ap["id"])
|
||||
? "selected"
|
||||
: ""; ?>>
|
||||
<?php echo htmlspecialchars($ap["name"]); ?>
|
||||
<?php if (
|
||||
$ap["code"]
|
||||
): ?> (<?php echo htmlspecialchars(
|
||||
$ap["code"],
|
||||
); ?>)<?php endif; ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="finality">Finalité du master *</label>
|
||||
<select id="finality" name="finality" required>
|
||||
<option value="">-- Sélectionner une finalité --</option>
|
||||
<?php foreach ($finalityTypes as $finality): ?>
|
||||
<option value="<?php echo htmlspecialchars(
|
||||
$finality["id"],
|
||||
); ?>" <?php echo wasSelected(
|
||||
"finality",
|
||||
$finality["id"],
|
||||
)
|
||||
? "selected"
|
||||
: ""; ?>>
|
||||
<?php echo htmlspecialchars($finality["name"]); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="promoteurice">Promoteur·ice(s)</label>
|
||||
<input type="text" id="promoteurice" name="promoteurice" placeholder="Nom du/de la promoteur·ice (si plusieurs, séparer par des virgules)" value="<?php echo old(
|
||||
"promoteurice",
|
||||
); ?>">
|
||||
|
||||
</fieldset>
|
||||
|
||||
<h2>À propos du TFE</h2>
|
||||
|
||||
|
||||
<fieldset>
|
||||
<label for="titre">Titre du mémoire *</label>
|
||||
<input type="text" id="titre" name="titre" placeholder="Titre de votre TFE" value="<?php echo old(
|
||||
"titre",
|
||||
); ?>" required>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="subtitle">Sous-titre (si applicable)</label>
|
||||
<input type="text" id="subtitle" name="subtitle" placeholder="Sous-titre de votre TFE" value="<?php echo old(
|
||||
"subtitle",
|
||||
); ?>">
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="synopsis">Synopsis (environ 200 mots) *</label>
|
||||
<textarea id="synopsis" name="synopsis" rows="8" placeholder="Décrivez votre TFE en quelques paragraphes..." required><?php echo old(
|
||||
"synopsis",
|
||||
); ?></textarea>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="problématique">Problématique</label>
|
||||
<textarea id="problématique" name="problématique" rows="4" placeholder="La problématique principale de votre mémoire..."><?php echo old(
|
||||
"problématique",
|
||||
); ?></textarea>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label>Langue(s) du TFE * (sélection multiple possible)</label>
|
||||
<?php foreach ($languages as $language): ?>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" name="languages[]" value="<?php echo htmlspecialchars(
|
||||
$language["id"],
|
||||
); ?>" <?php echo wasSelected(
|
||||
"languages",
|
||||
$language["id"],
|
||||
)
|
||||
? "checked"
|
||||
: ""; ?>>
|
||||
<?php echo htmlspecialchars($language["name"]); ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label>Format(s) (sélection multiple possible)</label>
|
||||
<?php foreach ($formatTypes as $format): ?>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" name="formats[]" value="<?php echo htmlspecialchars(
|
||||
$format["id"],
|
||||
); ?>" <?php echo wasSelected(
|
||||
"formats",
|
||||
$format["id"],
|
||||
)
|
||||
? "checked"
|
||||
: ""; ?>>
|
||||
<?php echo htmlspecialchars($format["name"]); ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="tag">Mots-clés (max 10, séparés par des virgules)</label>
|
||||
<input type="text" id="tag" name="tag" placeholder="typographie, photographie, outils libre, post-colonial..." value="<?php echo old(
|
||||
"tag",
|
||||
); ?>">
|
||||
<small>Séparez les mots-clés par des virgules. Maximum 10 mots-clés.</small>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="duration_info">Durée/Taille (si applicable)</label>
|
||||
<input type="text" id="duration_info" name="duration_info" placeholder="Ex: 68 minutes, 128 pages, 78 pages + 15 minutes" value="<?php echo old(
|
||||
"duration_info",
|
||||
); ?>">
|
||||
<small>Indiquez la durée (en minutes) ou le nombre de pages de votre TFE.</small>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="lien">Lien vers un site web ou ressource en ligne</label>
|
||||
<input type="url" id="lien" name="lien" placeholder="https://monmemoire.erg.be/..." value="<?php echo old(
|
||||
"lien",
|
||||
); ?>">
|
||||
</fieldset>
|
||||
|
||||
<h2>Fichiers</h2>
|
||||
|
||||
<fieldset>
|
||||
<label for="couverture">Importer une image de couverture</label>
|
||||
<small>Formats acceptés : JPG, PNG. Taille max : 10MB.</small>
|
||||
<input type="file" id="couverture" name="couverture" accept="image/jpeg,image/png">
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="files">Importer le TFE et les fichiers annexes</label>
|
||||
<small>Formats acceptés : PDF, JPG, PNG, MP4, ZIP. Taille max par fichier : 50MB.</small>
|
||||
<small>Si vous voulez importer un dossier, créez une archive ZIP.</small>
|
||||
<input type="file" id="files" name="files[]" multiple accept=".pdf,.jpg,.jpeg,.png,.mp4,.zip">
|
||||
</fieldset>
|
||||
|
||||
<br>
|
||||
<input type="submit" name="go" value="Soumettre mon TFE">
|
||||
</form>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>Formulaire fait avec ❤ en PHP et <a href="https://github.com/kevquirk/simple.css">SimpleCSS</a>.</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,14 +0,0 @@
|
||||
.
|
||||
├── assets
|
||||
├── composer.json
|
||||
├── composer.lock
|
||||
├── data
|
||||
├── error.log
|
||||
├── formulaire.php
|
||||
├── index.php
|
||||
├── README.md
|
||||
├── struct.txt
|
||||
├── thanks.php
|
||||
└── vendor
|
||||
|
||||
4 directories, 8 files
|
||||
Binary file not shown.
11
apps/public/.gitignore
vendored
11
apps/public/.gitignore
vendored
@@ -1,11 +0,0 @@
|
||||
vendor/
|
||||
compose.lock
|
||||
|
||||
### Data et Mémoire###
|
||||
formulaire/data/yaml/*
|
||||
formulaire/data/content/*
|
||||
formulaire/data/cover/*
|
||||
|
||||
front-backend/data/yaml/*
|
||||
front-backend/data/content/*
|
||||
front-backend/data/cover/*
|
||||
@@ -1,44 +0,0 @@
|
||||
# PostERG - Site web public
|
||||
|
||||
Site web affichant publiquement les mémoires et travaux de fin d'études soumis par les étudiant.e.s de l'ERG.
|
||||
|
||||
## Fonctionnalités
|
||||
|
||||
- Affichage paginé des mémoires
|
||||
- Visualisation détaillée de chaque mémoire
|
||||
- Pages d'information (à propos, contact, licences)
|
||||
|
||||
## Technologies
|
||||
|
||||
- PHP
|
||||
- [Symfony YAML](https://symfony.com/doc/current/components/yaml.html) pour la lecture des métadonnées
|
||||
- CSS (Bulma)
|
||||
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
composer install
|
||||
```
|
||||
|
||||
## Lancement
|
||||
|
||||
```shell
|
||||
php -S 127.0.0.1:3001
|
||||
```
|
||||
|
||||
Puis ouvrir [127.0.0.1:3001](http://127.0.0.1:3001) dans votre navigateur.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
front-backend/
|
||||
├── assets/ # Fichiers CSS et ressources
|
||||
├── data/
|
||||
│ └── yaml/ # Fichiers YAML des mémoires
|
||||
├── inc/ # Fichiers inclus (header, footer)
|
||||
├── index.php # Page d'accueil avec liste paginée
|
||||
├── memoire.php # Page de détail d'un mémoire
|
||||
├── apropos.php # Page à propos
|
||||
├── contact.php # Page de contact
|
||||
└── licences.php # Page des licences
|
||||
```
|
||||
@@ -1,67 +0,0 @@
|
||||
<?php include 'inc/header.php'; ?>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h2 class="title is-2">À propos</h2>
|
||||
|
||||
<h2 class="title is-3">Travail en Cours.</h2>
|
||||
|
||||
<div class="content">
|
||||
<a href="https://pads.erg.be/p/POST-ERG_charteDeVosMEMOIRES">Chartes d'utilisation et fonctionnement de l'initiative.</a>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<p>Nous sommes un groupe d'étudiant.e.s en design numérique avec projet concernant les mémoires de l'année
|
||||
passée qui demande votre aide !
|
||||
|
||||
Qu'en est-il des mémoires après notre master ?
|
||||
Quelle est la visibilité de notre travail après notre départ de l'ERG ? Certains mémoires finissent à la
|
||||
bibliothèque exposés, mais lesquels et pourquoi ?
|
||||
Actuellement, la bibliothèque (BAUI) sert de lieux d'archives (collection de documents anciens, classés à des
|
||||
fins historiques ; lieu où les archives sont conservées) des mémoires pour l'erg, st-Luc et UCL mais pourquoi
|
||||
sont-ils si peu à être exposés ? </p>
|
||||
|
||||
<p>Actuellement, les mémoires sélectionnés sont ceux avec une grande disctinction (16/20). Cette note obtenue
|
||||
dépend de la cotation de lecture de mémoire et sa défense.</p>
|
||||
|
||||
<h3 class="title is-3">Mais pourquoi cette moyenne de 14/20 ?</h3>
|
||||
<h3 class="title is-3">Et où finissent les autres mémoires ?</h3>
|
||||
|
||||
<p>En l'occurence, la bibliothèque n'est pas un lieu de diffusion et de monstration " juste ", car les mémoires
|
||||
dépendent de la note attribuée en fin de Master et de la place disponible dans les étagères ; sans parler de
|
||||
l'état déplorable de certains mémoires due aux conditions de stockages : couverture plastifiée, stickers, etc
|
||||
- nous travaillons un visuel qui finalement sera " dégradé " lors de son exposition à la bibliothèque, si
|
||||
exposé. De plus, les mémoires sont visible en bibliothèque de manière tangible (style édition).</p>
|
||||
|
||||
<h3 class="title is-3">Qu'en t-il des formats numérique, audio ou vidéo ?</h3>
|
||||
|
||||
<p>De fait, notre recherche se pencherait sur un dispositif de partage/diffusion plus adéquat et en phase avec
|
||||
la multitude de format et forme de monstration plus contemporain.
|
||||
<br>
|
||||
Notre lieu d'archive/exposition prendrait la forme d'un site web, idéalement en ligne (ou en local en fonction
|
||||
du RE - propriété intellectuelle et droit d'auteur ?). Il contiendrait tout types de mémoire ainsi qu'une
|
||||
interrogation autour de sa licence et sa notion de partage.
|
||||
<br>
|
||||
En paralèlle, nous donnerons quelques tips et bon plans pour : " comment licencier son mémoire : pour protéger
|
||||
ses valeurs et notions de partage s'il y'a" . Dans un premier temps, nous allons collecter un maximum de
|
||||
mémoires et
|
||||
tenter de recontacter leur auteurice pour échanger avec eux et obtenir des pdf, vidéos, photos. Dans un second
|
||||
temps, nous trouverons un relais pour les futures étudiant.e.s et organiseront un formulaire qui publiera
|
||||
automatiquement les mémoires sur notre site.
|
||||
</p>
|
||||
|
||||
<h2 class="title is-2">Un projet depuis 2022</h2>
|
||||
|
||||
<p>
|
||||
Théo Hennequin<br>
|
||||
<a href="https://www.theohennequin.com">www.theohennequin.com</a><br>
|
||||
Théophile Gervreau-Mercier<br>
|
||||
<a href="https://tgm.happyngreen.fr">tgm.happyngreen.fr</a><br>
|
||||
Olivia Marly<br>
|
||||
<a href="mailto:oli98marly@gmail.com">oli98marly@gmail.com</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php include 'inc/footer.php'; ?>
|
||||
Binary file not shown.
349
apps/public/assets/normalize.css
vendored
349
apps/public/assets/normalize.css
vendored
@@ -1,349 +0,0 @@
|
||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
/* Document
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Correct the line height in all browsers.
|
||||
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/* Sections
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the `main` element consistently in IE.
|
||||
*/
|
||||
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background on active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Chrome 57-
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Change the font styles in all browsers.
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
|
||||
button,
|
||||
input { /* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select { /* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the padding in Firefox.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE 10+.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10.
|
||||
* 2. Remove the padding in IE 10.
|
||||
*/
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Add the correct display in Edge, IE 10+, and Firefox.
|
||||
*/
|
||||
|
||||
details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Misc
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10+.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
@@ -1,559 +0,0 @@
|
||||
/* ============================================
|
||||
POST-ERG - Minimalistic CSS
|
||||
============================================ */
|
||||
|
||||
/* Custom Font */
|
||||
@font-face {
|
||||
font-family: 'Combined';
|
||||
src: url("fonts/Combinedd.otf");
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
VARIABLES & BASE
|
||||
============================================ */
|
||||
|
||||
:root {
|
||||
--color-primary: #c104fc;
|
||||
--color-secondary: #4da870;
|
||||
--color-text: #333;
|
||||
--color-text-light: #666;
|
||||
--color-border: #ddd;
|
||||
--color-bg: #fff;
|
||||
--color-bg-light: #f9f9f9;
|
||||
--spacing-sm: 0.75rem;
|
||||
--spacing: 1.5rem;
|
||||
--spacing-lg: 3rem;
|
||||
--spacing-xl: 4rem;
|
||||
--border-radius: 8px;
|
||||
--max-width: 1400px;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
line-height: 1.7;
|
||||
color: var(--color-text);
|
||||
background: var(--color-bg-light);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
NAVBAR
|
||||
============================================ */
|
||||
|
||||
.navbar {
|
||||
font-family: 'Combined', sans-serif;
|
||||
background: linear-gradient(280deg, var(--color-secondary) 0%, var(--color-primary) 85%);
|
||||
padding: 2rem 3rem;
|
||||
color: white;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.navbar-brand h1 {
|
||||
margin: 0;
|
||||
font-size: 2.5rem;
|
||||
font-weight: normal;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.navbar-menu {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.navbar-item {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s;
|
||||
font-size: 1.1rem;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.navbar-item:hover {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
LAYOUT
|
||||
============================================ */
|
||||
|
||||
.section {
|
||||
padding: var(--spacing-xl) var(--spacing-lg);
|
||||
background: var(--color-bg-light);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: var(--max-width);
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--spacing-lg);
|
||||
}
|
||||
|
||||
/* Grid System */
|
||||
.columns {
|
||||
display: grid;
|
||||
gap: 2rem;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
}
|
||||
|
||||
.columns.is-multiline {
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
}
|
||||
|
||||
.column {
|
||||
min-width: 0; /* Fix overflow issues */
|
||||
}
|
||||
|
||||
.column.is-one-third {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.column.is-one-fifth {
|
||||
/* Will use auto-fill for responsive grid */
|
||||
}
|
||||
|
||||
/* Two-column layout for detail pages */
|
||||
.columns.is-variable {
|
||||
grid-template-columns: 1fr 2fr;
|
||||
gap: var(--spacing-lg);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.columns.is-variable {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
CARDS
|
||||
============================================ */
|
||||
|
||||
.card {
|
||||
background: var(--color-bg);
|
||||
border: 2px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
overflow: hidden;
|
||||
transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.card-link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card-link:hover .card {
|
||||
transform: translateY(-6px);
|
||||
box-shadow: 0 8px 24px rgba(193, 4, 252, 0.2);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.card-image {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background: var(--color-bg-light);
|
||||
}
|
||||
|
||||
.card-image img {
|
||||
width: 100%;
|
||||
height: 240px;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 1.75rem;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
TYPOGRAPHY
|
||||
============================================ */
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin: 0 0 0.75rem 0;
|
||||
line-height: 1.3;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.5rem;
|
||||
color: var(--color-text);
|
||||
margin-bottom: 0.75rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.title.is-1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.title.is-4 {
|
||||
font-size: 1.35rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.title.is-6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.1rem;
|
||||
color: var(--color-text-light);
|
||||
margin-bottom: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 1rem;
|
||||
line-height: 1.7;
|
||||
color: var(--color-text-light);
|
||||
}
|
||||
|
||||
.block {
|
||||
margin-top: auto; /* Push to bottom of card */
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
TAG
|
||||
============================================ */
|
||||
|
||||
.tag {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1rem;
|
||||
background: var(--color-bg-light);
|
||||
border: 2px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
font-size: 0.95rem;
|
||||
margin-bottom: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tag.is-link {
|
||||
background: rgba(193, 4, 252, 0.08);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.tag.is-light {
|
||||
background: var(--color-bg-light);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
FORMS
|
||||
============================================ */
|
||||
|
||||
.field {
|
||||
margin-bottom: var(--spacing);
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.input,
|
||||
.textarea,
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
border: 2px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
font-size: 1rem;
|
||||
font-family: inherit;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
.input:focus,
|
||||
.textarea:focus,
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px rgba(193, 4, 252, 0.1);
|
||||
}
|
||||
|
||||
.textarea {
|
||||
min-height: 150px;
|
||||
resize: vertical;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
BUTTONS
|
||||
============================================ */
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 0.85rem 2rem;
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: var(--border-radius);
|
||||
font-size: 1.05rem;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s, transform 0.1s, box-shadow 0.2s;
|
||||
font-family: inherit;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 2px 4px rgba(193, 4, 252, 0.2);
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: #a003d1;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(193, 4, 252, 0.3);
|
||||
}
|
||||
|
||||
.button:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 4px rgba(193, 4, 252, 0.2);
|
||||
}
|
||||
|
||||
.button.is-link {
|
||||
background: var(--color-primary);
|
||||
}
|
||||
|
||||
.button.is-light {
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
border: 2px solid var(--color-border);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.button.is-light:hover {
|
||||
background: var(--color-bg-light);
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
NOTIFICATIONS
|
||||
============================================ */
|
||||
|
||||
.notification {
|
||||
padding: 1.5rem 2rem;
|
||||
border-radius: var(--border-radius);
|
||||
margin-bottom: var(--spacing);
|
||||
border: 2px solid;
|
||||
font-size: 1.05rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.notification.is-danger {
|
||||
background: #fee;
|
||||
border-color: #fcc;
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
.notification.is-success {
|
||||
background: #efe;
|
||||
border-color: #cfc;
|
||||
color: #060;
|
||||
}
|
||||
|
||||
.notification.is-info {
|
||||
background: #eef;
|
||||
border-color: #ccf;
|
||||
color: #006;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
BOX
|
||||
============================================ */
|
||||
|
||||
.box {
|
||||
background: var(--color-bg);
|
||||
border: 2px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 2rem;
|
||||
margin-bottom: var(--spacing);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
MEDIA
|
||||
============================================ */
|
||||
|
||||
img,
|
||||
video,
|
||||
iframe,
|
||||
embed {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
embed {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
height: 700px;
|
||||
margin: 0 auto;
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
PAGINATION
|
||||
============================================ */
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: center;
|
||||
margin-top: var(--spacing-lg);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.pagination a,
|
||||
.pagination span {
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
text-decoration: none;
|
||||
color: var(--color-text);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.pagination a:hover {
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.pagination .current {
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
FOOTER
|
||||
============================================ */
|
||||
|
||||
.footer {
|
||||
background: var(--color-bg);
|
||||
border-top: 2px solid var(--color-border);
|
||||
padding: 3rem 2rem;
|
||||
text-align: center;
|
||||
margin-top: var(--spacing-xl);
|
||||
font-size: 1rem;
|
||||
color: var(--color-text-light);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
UTILITIES
|
||||
============================================ */
|
||||
|
||||
.has-text-centered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.is-flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.is-justify-content-space-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.is-align-items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
RESPONSIVE
|
||||
============================================ */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
:root {
|
||||
--spacing-lg: 1.5rem;
|
||||
--spacing-xl: 2rem;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
padding: 1.5rem 1rem;
|
||||
}
|
||||
|
||||
.navbar-brand h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.navbar-menu {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.navbar-item {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.columns {
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
embed {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.title.is-4 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.columns {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
padding: 1.25rem 1rem;
|
||||
}
|
||||
|
||||
.navbar-brand h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 1.5rem 1rem;
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
[1769594004,1769594004,1769594004,1769594004,1769594004]
|
||||
@@ -1 +0,0 @@
|
||||
[1769593359,1769593362,1769593367,1769593372,1769593375,1769593380]
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"require": {
|
||||
"symfony/polyfill-iconv": "^1.27",
|
||||
"symfony/yaml": "^6.2",
|
||||
"symfony/intl": "^6.2",
|
||||
"behat/transliterator": "^1.5"
|
||||
}
|
||||
}
|
||||
388
apps/public/composer.lock
generated
388
apps/public/composer.lock
generated
@@ -1,388 +0,0 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "e941923040be085b6ce94a2d66270369",
|
||||
"packages": [
|
||||
{
|
||||
"name": "behat/transliterator",
|
||||
"version": "v1.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Behat/Transliterator.git",
|
||||
"reference": "baac5873bac3749887d28ab68e2f74db3a4408af"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Behat/Transliterator/zipball/baac5873bac3749887d28ab68e2f74db3a4408af",
|
||||
"reference": "baac5873bac3749887d28ab68e2f74db3a4408af",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"chuyskywalker/rolling-curl": "^3.1",
|
||||
"php-yaoi/php-yaoi": "^1.0",
|
||||
"phpunit/phpunit": "^8.5.25 || ^9.5.19"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Behat\\Transliterator\\": "src/Behat/Transliterator"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Artistic-1.0"
|
||||
],
|
||||
"description": "String transliterator",
|
||||
"keywords": [
|
||||
"i18n",
|
||||
"slug",
|
||||
"transliterator"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Behat/Transliterator/issues",
|
||||
"source": "https://github.com/Behat/Transliterator/tree/v1.5.0"
|
||||
},
|
||||
"time": "2022-03-30T09:27:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/intl",
|
||||
"version": "v6.2.10",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/intl.git",
|
||||
"reference": "860c99e53149d22df1900d3aefdaeb17adb7669d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/intl/zipball/860c99e53149d22df1900d3aefdaeb17adb7669d",
|
||||
"reference": "860c99e53149d22df1900d3aefdaeb17adb7669d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/filesystem": "^5.4|^6.0",
|
||||
"symfony/finder": "^5.4|^6.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Intl\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Bernhard Schussek",
|
||||
"email": "bschussek@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Eriksen Costa",
|
||||
"email": "eriksen.costa@infranology.com.br"
|
||||
},
|
||||
{
|
||||
"name": "Igor Wiedler",
|
||||
"email": "igor@wiedler.ch"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Provides access to the localization data of the ICU library",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"i18n",
|
||||
"icu",
|
||||
"internationalization",
|
||||
"intl",
|
||||
"l10n",
|
||||
"localization"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/intl/tree/v6.2.10"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-04-14T16:23:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a",
|
||||
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"provide": {
|
||||
"ext-ctype": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-ctype": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.27-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Ctype\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gert de Pagter",
|
||||
"email": "BackEndTea@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for ctype functions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"ctype",
|
||||
"polyfill",
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-11-03T14:55:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-iconv",
|
||||
"version": "v1.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-iconv.git",
|
||||
"reference": "927013f3aac555983a5059aada98e1907d842695"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/927013f3aac555983a5059aada98e1907d842695",
|
||||
"reference": "927013f3aac555983a5059aada98e1907d842695",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"provide": {
|
||||
"ext-iconv": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-iconv": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.27-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Iconv\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for the Iconv extension",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"iconv",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-iconv/tree/v1.27.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-11-03T14:55:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v6.2.10",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
"reference": "61916f3861b1e9705b18cfde723921a71dd1559d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/61916f3861b1e9705b18cfde723921a71dd1559d",
|
||||
"reference": "61916f3861b1e9705b18cfde723921a71dd1559d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"symfony/polyfill-ctype": "^1.8"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/console": "<5.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/console": "^5.4|^6.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/console": "For validating YAML files using the lint command"
|
||||
},
|
||||
"bin": [
|
||||
"Resources/bin/yaml-lint"
|
||||
],
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Yaml\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Loads and dumps YAML files",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/yaml/tree/v6.2.10"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-04-28T13:25:36+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.3.0"
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php include 'inc/header.php'; ?>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h2 class="title is-2">Contact</h2>
|
||||
<div class="content">
|
||||
|
||||
<p>Laurent Leprince, <br>
|
||||
Bibliothèque d'architecture, d'ingénierie architecturale, d'urbanisme (BAIU) :<br>
|
||||
laurent.leprince@uclouvain.be</p>
|
||||
|
||||
<p>Xavier Gorgol, <br>
|
||||
Responsable des mémoires de l'ERG :<br>
|
||||
xavier.gorgol@erg.be<br></p>
|
||||
|
||||
<p>Brigitte Ledune,<br>
|
||||
Cours de suivi de mémoire : <br>
|
||||
brigitte.ledune@erg.be</p>
|
||||
|
||||
<?php include 'inc/footer.php'; ?>
|
||||
@@ -1,113 +0,0 @@
|
||||
[02-May-2023 18:18:59 UTC] PHP Fatal error: Uncaught Error: Call to undefined function yaml_parse_file() in /home/lockpick/Projects/posterg-website/index.php:17
|
||||
Stack trace:
|
||||
#0 {main}
|
||||
thrown in /home/lockpick/Projects/posterg-website/index.php on line 17
|
||||
[02-May-2023 18:19:05 UTC] PHP Fatal error: Uncaught Error: Call to undefined function yaml_parse_file() in /home/lockpick/Projects/posterg-website/index.php:17
|
||||
Stack trace:
|
||||
#0 {main}
|
||||
thrown in /home/lockpick/Projects/posterg-website/index.php on line 17
|
||||
[02-May-2023 18:19:09 UTC] PHP Fatal error: Uncaught Error: Call to undefined function yaml_parse_file() in /home/lockpick/Projects/posterg-website/index.php:17
|
||||
Stack trace:
|
||||
#0 {main}
|
||||
thrown in /home/lockpick/Projects/posterg-website/index.php on line 17
|
||||
[02-May-2023 18:52:59 UTC] PHP Warning: include(header.php): Failed to open stream: No such file or directory in /home/lockpick/Projects/posterg-website/index.php on line 20
|
||||
[02-May-2023 18:52:59 UTC] PHP Warning: include(): Failed opening 'header.php' for inclusion (include_path='.:') in /home/lockpick/Projects/posterg-website/index.php on line 20
|
||||
[02-May-2023 18:52:59 UTC] PHP Warning: include(footer.php): Failed to open stream: No such file or directory in /home/lockpick/Projects/posterg-website/index.php on line 38
|
||||
[02-May-2023 18:52:59 UTC] PHP Warning: include(): Failed opening 'footer.php' for inclusion (include_path='.:') in /home/lockpick/Projects/posterg-website/index.php on line 38
|
||||
[02-May-2023 18:53:19 UTC] PHP Warning: include(header.php): Failed to open stream: No such file or directory in /home/lockpick/Projects/posterg-website/index.php on line 20
|
||||
[02-May-2023 18:53:19 UTC] PHP Warning: include(): Failed opening 'header.php' for inclusion (include_path='.:') in /home/lockpick/Projects/posterg-website/index.php on line 20
|
||||
[02-May-2023 18:53:19 UTC] PHP Warning: include(footer.php): Failed to open stream: No such file or directory in /home/lockpick/Projects/posterg-website/index.php on line 38
|
||||
[02-May-2023 18:53:19 UTC] PHP Warning: include(): Failed opening 'footer.php' for inclusion (include_path='.:') in /home/lockpick/Projects/posterg-website/index.php on line 38
|
||||
[02-May-2023 18:53:20 UTC] PHP Warning: include(header.php): Failed to open stream: No such file or directory in /home/lockpick/Projects/posterg-website/index.php on line 20
|
||||
[02-May-2023 18:53:20 UTC] PHP Warning: include(): Failed opening 'header.php' for inclusion (include_path='.:') in /home/lockpick/Projects/posterg-website/index.php on line 20
|
||||
[02-May-2023 18:53:20 UTC] PHP Warning: include(footer.php): Failed to open stream: No such file or directory in /home/lockpick/Projects/posterg-website/index.php on line 38
|
||||
[02-May-2023 18:53:20 UTC] PHP Warning: include(): Failed opening 'footer.php' for inclusion (include_path='.:') in /home/lockpick/Projects/posterg-website/index.php on line 38
|
||||
[02-May-2023 18:53:38 UTC] PHP Warning: include(header.php): Failed to open stream: No such file or directory in /home/lockpick/Projects/posterg-website/index.php on line 20
|
||||
[02-May-2023 18:53:38 UTC] PHP Warning: include(): Failed opening 'header.php' for inclusion (include_path='.:') in /home/lockpick/Projects/posterg-website/index.php on line 20
|
||||
[02-May-2023 18:53:38 UTC] PHP Warning: include(footer.php): Failed to open stream: No such file or directory in /home/lockpick/Projects/posterg-website/index.php on line 38
|
||||
[02-May-2023 18:53:38 UTC] PHP Warning: include(): Failed opening 'footer.php' for inclusion (include_path='.:') in /home/lockpick/Projects/posterg-website/index.php on line 38
|
||||
[02-May-2023 18:53:39 UTC] PHP Warning: include(header.php): Failed to open stream: No such file or directory in /home/lockpick/Projects/posterg-website/index.php on line 20
|
||||
[02-May-2023 18:53:39 UTC] PHP Warning: include(): Failed opening 'header.php' for inclusion (include_path='.:') in /home/lockpick/Projects/posterg-website/index.php on line 20
|
||||
[02-May-2023 18:53:39 UTC] PHP Warning: include(footer.php): Failed to open stream: No such file or directory in /home/lockpick/Projects/posterg-website/index.php on line 38
|
||||
[02-May-2023 18:53:39 UTC] PHP Warning: include(): Failed opening 'footer.php' for inclusion (include_path='.:') in /home/lockpick/Projects/posterg-website/index.php on line 38
|
||||
[04-May-2023 19:41:18 UTC] PHP Warning: Undefined array key "title" in /home/lockpick/Projects/posterg-website/memoire.php on line 21
|
||||
[04-May-2023 19:41:18 UTC] PHP Warning: Undefined array key "author" in /home/lockpick/Projects/posterg-website/memoire.php on line 24
|
||||
[04-May-2023 19:41:32 UTC] PHP Warning: Undefined array key "title" in /home/lockpick/Projects/posterg-website/memoire.php on line 21
|
||||
[04-May-2023 19:41:32 UTC] PHP Warning: Undefined array key "author" in /home/lockpick/Projects/posterg-website/memoire.php on line 24
|
||||
[04-May-2023 20:09:45 UTC] PHP Warning: foreach() argument must be of type array|object, string given in /home/lockpick/Projects/posterg-website/memoire.php on line 42
|
||||
[04-May-2023 20:12:06 UTC] PHP Warning: foreach() argument must be of type array|object, string given in /home/lockpick/Projects/posterg-website/memoire.php on line 46
|
||||
[04-May-2023 20:12:49 UTC] PHP Warning: foreach() argument must be of type array|object, string given in /home/lockpick/Projects/posterg-website/memoire.php on line 46
|
||||
[04-May-2023 20:13:06 UTC] PHP Warning: foreach() argument must be of type array|object, string given in /home/lockpick/Projects/posterg-website/memoire.php on line 46
|
||||
[04-May-2023 20:13:20 UTC] PHP Warning: foreach() argument must be of type array|object, string given in /home/lockpick/Projects/posterg-website/memoire.php on line 46
|
||||
[04-May-2023 20:15:28 UTC] PHP Warning: foreach() argument must be of type array|object, string given in /home/lockpick/Projects/posterg-website/memoire.php on line 50
|
||||
[04-May-2023 20:16:10 UTC] PHP Warning: foreach() argument must be of type array|object, string given in /home/lockpick/Projects/posterg-website/memoire.php on line 70
|
||||
[04-May-2023 20:16:30 UTC] PHP Warning: foreach() argument must be of type array|object, string given in /home/lockpick/Projects/posterg-website/memoire.php on line 70
|
||||
[04-May-2023 20:16:47 UTC] PHP Warning: foreach() argument must be of type array|object, string given in /home/lockpick/Projects/posterg-website/memoire.php on line 70
|
||||
[04-May-2023 20:17:04 UTC] PHP Warning: foreach() argument must be of type array|object, string given in /home/lockpick/Projects/posterg-website/memoire.php on line 70
|
||||
[04-May-2023 20:17:41 UTC] PHP Warning: foreach() argument must be of type array|object, string given in /home/lockpick/Projects/posterg-website/memoire.php on line 70
|
||||
[04-May-2023 20:18:58 UTC] PHP Warning: foreach() argument must be of type array|object, string given in /home/lockpick/Projects/posterg-website/memoire.php on line 71
|
||||
[04-May-2023 20:48:21 UTC] PHP Warning: foreach() argument must be of type array|object, string given in /home/lockpick/Projects/posterg-website/memoire.php on line 74
|
||||
[04-May-2023 20:54:30 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[04-May-2023 20:55:03 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[04-May-2023 20:55:11 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[04-May-2023 20:55:14 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[04-May-2023 20:56:41 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[04-May-2023 21:02:29 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[04-May-2023 21:02:54 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[04-May-2023 21:25:39 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[05-May-2023 08:44:42 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[05-May-2023 08:46:07 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[05-May-2023 08:47:02 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[05-May-2023 08:47:15 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[05-May-2023 08:47:19 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[05-May-2023 08:47:22 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[05-May-2023 08:47:25 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[05-May-2023 08:47:31 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[05-May-2023 08:47:34 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[05-May-2023 08:48:11 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[05-May-2023 08:57:31 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[05-May-2023 08:57:40 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[05-May-2023 08:57:43 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[05-May-2023 08:57:48 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[05-May-2023 08:57:51 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[05-May-2023 08:58:15 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[05-May-2023 09:00:33 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[05-May-2023 09:02:53 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[05-May-2023 09:17:40 UTC] PHP Warning: Undefined variable $key in /home/lockpick/Projects/posterg-website/memoire.php on line 68
|
||||
[05-May-2023 09:43:22 UTC] PHP Warning: Undefined array key "description" in /home/lockpick/Projects/posterg-website/memoire.php on line 70
|
||||
[05-May-2023 09:45:04 UTC] PHP Warning: Undefined array key "description" in /home/lockpick/Projects/posterg-website/memoire.php on line 70
|
||||
[05-May-2023 09:49:04 UTC] PHP Warning: Undefined array key "resume" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 09:49:04 UTC] PHP Warning: Undefined array key "resume" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 09:49:04 UTC] PHP Warning: Undefined array key "resume" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 09:49:04 UTC] PHP Warning: Undefined array key "resume" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 09:49:04 UTC] PHP Warning: Undefined array key "resume" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:06:01 UTC] PHP Warning: Undefined array key "description" in /home/lockpick/Projects/posterg-website/memoire.php on line 70
|
||||
[05-May-2023 10:06:23 UTC] PHP Warning: Undefined array key "description" in /home/lockpick/Projects/posterg-website/memoire.php on line 70
|
||||
[05-May-2023 10:06:36 UTC] PHP Warning: Undefined array key "resume" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:06:36 UTC] PHP Warning: Undefined array key "resume" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:06:36 UTC] PHP Warning: Undefined array key "resume" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:06:36 UTC] PHP Warning: Undefined array key "resume" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:06:36 UTC] PHP Warning: Undefined array key "resume" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:06:42 UTC] PHP Warning: Undefined array key "description" in /home/lockpick/Projects/posterg-website/memoire.php on line 70
|
||||
[05-May-2023 10:06:44 UTC] PHP Warning: Undefined array key "resume" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:06:44 UTC] PHP Warning: Undefined array key "resume" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:06:44 UTC] PHP Warning: Undefined array key "resume" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:06:44 UTC] PHP Warning: Undefined array key "resume" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:06:44 UTC] PHP Warning: Undefined array key "resume" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:06:45 UTC] PHP Warning: Undefined array key "description" in /home/lockpick/Projects/posterg-website/memoire.php on line 70
|
||||
[05-May-2023 10:06:47 UTC] PHP Warning: Undefined array key "resume" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:06:47 UTC] PHP Warning: Undefined array key "resume" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:06:47 UTC] PHP Warning: Undefined array key "resume" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:06:47 UTC] PHP Warning: Undefined array key "resume" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:06:47 UTC] PHP Warning: Undefined array key "resume" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:07:02 UTC] PHP Warning: Undefined array key "description" in /home/lockpick/Projects/posterg-website/memoire.php on line 70
|
||||
[05-May-2023 10:07:16 UTC] PHP Warning: Undefined array key "description" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:07:16 UTC] PHP Warning: Undefined array key "description" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:07:16 UTC] PHP Warning: Undefined array key "description" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:07:16 UTC] PHP Warning: Undefined array key "description" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:07:16 UTC] PHP Warning: Undefined array key "description" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:08:03 UTC] PHP Warning: Undefined array key "description" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:08:03 UTC] PHP Warning: Undefined array key "description" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:08:03 UTC] PHP Warning: Undefined array key "description" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:08:03 UTC] PHP Warning: Undefined array key "description" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
[05-May-2023 10:08:03 UTC] PHP Warning: Undefined array key "description" in /home/lockpick/Projects/posterg-website/index.php on line 58
|
||||
@@ -1,30 +0,0 @@
|
||||
<!-- header.php -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="author" content="">
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Posterg</title>
|
||||
<link rel="stylesheet" href="assets/normalize.css">
|
||||
<link rel="stylesheet" href="assets/posterg.css?v=2">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="index.php">
|
||||
<h1 class="title is-1">Mémoire post-ERG/A life after ERG</h1>
|
||||
</a>
|
||||
</div>
|
||||
<div class="navbar-menu">
|
||||
<div class="navbar-end">
|
||||
<a href="search.php" class="navbar-item">Rechercher</a>
|
||||
<a href="apropos.php" class="navbar-item">À propos</a>
|
||||
<a href="contact.php" class="navbar-item">Contact</a>
|
||||
<a href="licences.php" class="navbar-item">Licences</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -1,87 +0,0 @@
|
||||
<?php
|
||||
ini_set('display_errors', 0);
|
||||
ini_set('log_errors', 1);
|
||||
ini_set('error_log', 'error.log');
|
||||
|
||||
require_once __DIR__ . '/../../shared/Database.php';
|
||||
|
||||
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
|
||||
$itemsPerPage = 10;
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
$offset = ($page - 1) * $itemsPerPage;
|
||||
$itemsToLoad = $db->getPublishedTheses($itemsPerPage, $offset);
|
||||
$totalItems = $db->countPublishedTheses();
|
||||
$totalPages = ceil($totalItems / $itemsPerPage);
|
||||
} catch (Exception $e) {
|
||||
error_log("Error loading theses: " . $e->getMessage());
|
||||
$itemsToLoad = [];
|
||||
$totalPages = 0;
|
||||
}
|
||||
|
||||
include 'inc/header.php'; ?>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="columns is-multiline">
|
||||
|
||||
<?php foreach ($itemsToLoad as $item): ?>
|
||||
<div class="column is-one-fifth">
|
||||
<a href="memoire.php?id=<?= $item['id']; ?>" class="card-link">
|
||||
<div class="card">
|
||||
<?php
|
||||
// Get cover image from thesis_files if available
|
||||
$coverImage = null;
|
||||
if (!empty($item['id'])) {
|
||||
$files = $db->getThesisFiles($item['id']);
|
||||
foreach ($files as $file) {
|
||||
$ext = strtolower(pathinfo($file['file_path'], PATHINFO_EXTENSION));
|
||||
if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif']) && $file['file_type'] === 'main') {
|
||||
$coverImage = $file['file_path'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<?php if ($coverImage): ?>
|
||||
<div class="card-image">
|
||||
<figure class="image ">
|
||||
<img src="<?= htmlspecialchars($coverImage); ?>" alt="Image preview">
|
||||
</figure>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="card-content">
|
||||
<h4 class="title is-4">
|
||||
<?= htmlspecialchars($item['title']); ?>
|
||||
</h4>
|
||||
<h2 class="subtitle">
|
||||
<?= htmlspecialchars($item['authors'] ?? 'Auteur inconnu'); ?>
|
||||
</h2>
|
||||
<h3 class="tag title is-6 is-link is-light">
|
||||
<?= htmlspecialchars($item['year']); ?>
|
||||
</h3>
|
||||
<p class="block content">
|
||||
<?php
|
||||
$excerpt_length = 150;
|
||||
$synopsis = $item['synopsis'] ?? '';
|
||||
$description_excerpt = strlen($synopsis) > $excerpt_length
|
||||
? substr($synopsis, 0, $excerpt_length) . '...'
|
||||
: $synopsis;
|
||||
?>
|
||||
<?= htmlspecialchars($description_excerpt); ?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php
|
||||
include 'inc/footer.php';
|
||||
?>
|
||||
@@ -1,18 +0,0 @@
|
||||
<?php include 'inc/header.php';?>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
Ce travail éditorial, concernant les licences de 2021-2022 est né d'une recherche menée par : <br> <br>
|
||||
Defez Aurélie <br>
|
||||
Gervreau-Mercier Théophile <br>
|
||||
Debaene Justine <br>
|
||||
Troadec Marie <br>
|
||||
Marly Olivia <br>
|
||||
Goldberg Jacquemain Elodie <br>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<?php include 'inc/footer.php';?>
|
||||
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
// Simple test script to verify database connection and queries
|
||||
require_once __DIR__ . '/../../shared/Database.php';
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
echo "✓ Database connection successful\n";
|
||||
|
||||
// Test counting theses
|
||||
$count = $db->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);
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Test script for search functionality
|
||||
* Run this to verify that search methods work correctly
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../../../shared/Database.php';
|
||||
|
||||
echo "=== Testing Search Feature ===\n\n";
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Test 1: Get all published theses
|
||||
echo "Test 1: Getting all published theses\n";
|
||||
$allTheses = $db->searchTheses([], 100, 0);
|
||||
echo "Found " . count($allTheses) . " published theses\n";
|
||||
foreach ($allTheses as $thesis) {
|
||||
echo " - [{$thesis['year']}] {$thesis['title']} by {$thesis['authors']}\n";
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Test 2: Full-text search
|
||||
echo "Test 2: Full-text search for 'urbain'\n";
|
||||
$results = $db->searchTheses(['query' => 'urbain']);
|
||||
echo "Found " . count($results) . " results\n";
|
||||
foreach ($results as $thesis) {
|
||||
echo " - {$thesis['title']}\n";
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Test 3: Search by year
|
||||
echo "Test 3: Search by year (2024)\n";
|
||||
$results = $db->searchTheses(['year' => 2024]);
|
||||
echo "Found " . count($results) . " results\n";
|
||||
foreach ($results as $thesis) {
|
||||
echo " - [{$thesis['year']}] {$thesis['title']}\n";
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Test 4: Search by orientation
|
||||
echo "Test 4: Search by orientation (Installation-Performance)\n";
|
||||
$results = $db->searchTheses(['orientation' => 'Installation-Performance']);
|
||||
echo "Found " . count($results) . " results\n";
|
||||
foreach ($results as $thesis) {
|
||||
echo " - {$thesis['title']} ({$thesis['orientation']})\n";
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Test 5: Search by AP program
|
||||
echo "Test 5: Search by AP program (Narration Spéculative)\n";
|
||||
$results = $db->searchTheses(['ap_program' => 'Narration Spéculative']);
|
||||
echo "Found " . count($results) . " results\n";
|
||||
foreach ($results as $thesis) {
|
||||
echo " - {$thesis['title']} ({$thesis['ap_program']})\n";
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Test 6: Search by keyword
|
||||
echo "Test 6: Search by keyword (performance)\n";
|
||||
$results = $db->searchTheses(['keyword' => 'performance']);
|
||||
echo "Found " . count($results) . " results\n";
|
||||
foreach ($results as $thesis) {
|
||||
echo " - {$thesis['title']}\n";
|
||||
echo " Keywords: {$thesis['keywords']}\n";
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Test 7: Combined search
|
||||
echo "Test 7: Combined search (query='performance' + year=2024)\n";
|
||||
$results = $db->searchTheses(['query' => 'performance', 'year' => 2024]);
|
||||
echo "Found " . count($results) . " results\n";
|
||||
foreach ($results as $thesis) {
|
||||
echo " - [{$thesis['year']}] {$thesis['title']}\n";
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Test 8: Get available years
|
||||
echo "Test 8: Getting available years\n";
|
||||
$years = $db->getAvailableYears();
|
||||
echo "Available years: " . implode(', ', $years) . "\n\n";
|
||||
|
||||
// Test 9: Get orientations
|
||||
echo "Test 9: Getting orientations\n";
|
||||
$orientations = $db->getOrientations();
|
||||
echo "Total orientations: " . count($orientations) . "\n";
|
||||
echo "Sample: " . $orientations[0]['name'] . ", " . $orientations[1]['name'] . ", ...\n\n";
|
||||
|
||||
// Test 10: Get keywords
|
||||
echo "Test 10: Getting used keywords\n";
|
||||
$keywords = $db->getUsedKeywords();
|
||||
echo "Total keywords in use: " . count($keywords) . "\n";
|
||||
$keywordNames = array_map(function($k) { return $k['keyword']; }, $keywords);
|
||||
echo "Keywords: " . implode(', ', array_slice($keywordNames, 0, 10)) . "...\n\n";
|
||||
|
||||
// Test 11: Count results
|
||||
echo "Test 11: Count search results\n";
|
||||
$count = $db->countSearchResults(['year' => 2024]);
|
||||
echo "Count for year 2024: $count\n\n";
|
||||
|
||||
// Test 12: Pagination
|
||||
echo "Test 12: Testing pagination\n";
|
||||
$page1 = $db->searchTheses([], 2, 0); // First 2 results
|
||||
$page2 = $db->searchTheses([], 2, 2); // Next 2 results
|
||||
echo "Page 1 (first 2):\n";
|
||||
foreach ($page1 as $thesis) {
|
||||
echo " - {$thesis['title']}\n";
|
||||
}
|
||||
echo "Page 2 (next 2):\n";
|
||||
foreach ($page2 as $thesis) {
|
||||
echo " - {$thesis['title']}\n";
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
echo "✅ All tests completed successfully!\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ Error: " . $e->getMessage() . "\n";
|
||||
echo "Stack trace:\n" . $e->getTraceAsString() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
@@ -1,306 +0,0 @@
|
||||
# Test Migration Summary
|
||||
|
||||
## ✅ Tests Reorganized Following PHP Standards
|
||||
|
||||
The test files have been reorganized to follow PHP testing best practices.
|
||||
|
||||
---
|
||||
|
||||
## What Changed
|
||||
|
||||
### Before (Non-Standard)
|
||||
```
|
||||
front-backend/
|
||||
├── test_search.php ❌ Tests in root
|
||||
├── test_security.php ❌ Would deploy to production
|
||||
├── test_security_updated.php ❌ No organization
|
||||
├── test_rate_limit.php ❌ Mixed with application code
|
||||
├── create_test_db.php ❌ Test fixtures in root
|
||||
├── Database_secure.php ❌ Duplicate code
|
||||
├── Database.php ✓ Application code
|
||||
└── RateLimit.php ✓ Application code
|
||||
```
|
||||
|
||||
### After (Standard)
|
||||
```
|
||||
front-backend/
|
||||
├── tests/ ✅ Dedicated test directory
|
||||
│ ├── Fixtures/ ✅ Test data & setup
|
||||
│ │ └── CreateTestDatabase.php
|
||||
│ ├── Integration/ ✅ Multi-component tests
|
||||
│ │ └── SearchTest.php
|
||||
│ ├── Security/ ✅ Security validation
|
||||
│ │ └── SecurityTest.php
|
||||
│ ├── Unit/ ✅ Individual component tests
|
||||
│ │ └── RateLimitTest.php
|
||||
│ └── README.md ✅ Test documentation
|
||||
├── run-tests.php ✅ Convenient test runner
|
||||
├── .gitignore ✅ Excludes cache, logs, etc.
|
||||
├── Database.php ✓ Application code
|
||||
└── RateLimit.php ✓ Application code
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Benefits Achieved
|
||||
|
||||
### ✅ Production Safety
|
||||
- **Tests excluded from deployment** via `justfile`
|
||||
- **No test code in production** - cleaner, more secure
|
||||
- **Smaller deployment size** - only application code deployed
|
||||
|
||||
### ✅ Better Organization
|
||||
- **Clear separation** - tests vs application code
|
||||
- **Logical grouping** - unit, integration, security, fixtures
|
||||
- **Standard structure** - other PHP developers will understand immediately
|
||||
|
||||
### ✅ Easier Testing
|
||||
- **Single command** - `php run-tests.php` runs everything
|
||||
- **Individual tests** - `php tests/Security/SecurityTest.php` for specific tests
|
||||
- **Better output** - formatted test results with summary
|
||||
|
||||
### ✅ Future-Ready
|
||||
- **PHPUnit compatible** - directory structure ready for migration
|
||||
- **CI/CD ready** - easy to integrate with GitHub Actions, etc.
|
||||
- **Scalable** - easy to add new tests in proper categories
|
||||
|
||||
---
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Run All Tests
|
||||
```bash
|
||||
cd /home/padlock/dev/posterg-website/front-backend
|
||||
php run-tests.php
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```
|
||||
╔════════════════════════════════════════════╗
|
||||
║ Running Front-Backend Tests ║
|
||||
╚════════════════════════════════════════════╝
|
||||
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Test Suite: Fixtures │
|
||||
└─────────────────────────────────────────┘
|
||||
✅ PASSED
|
||||
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Test Suite: Integration │
|
||||
└─────────────────────────────────────────┘
|
||||
✅ PASSED
|
||||
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Test Suite: Security │
|
||||
└─────────────────────────────────────────┘
|
||||
✅ PASSED
|
||||
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Test Suite: Unit │
|
||||
└─────────────────────────────────────────┘
|
||||
✅ PASSED
|
||||
|
||||
╔════════════════════════════════════════════╗
|
||||
║ Test Summary ║
|
||||
╠════════════════════════════════════════════╣
|
||||
║ Total: 4 ║
|
||||
║ Passed: 4 ✅ ║
|
||||
║ Failed: 0 ║
|
||||
╚════════════════════════════════════════════╝
|
||||
|
||||
✅ All tests passed!
|
||||
```
|
||||
|
||||
### Run Individual Tests
|
||||
```bash
|
||||
# Setup test database
|
||||
php tests/Fixtures/CreateTestDatabase.php
|
||||
|
||||
# Run specific test suite
|
||||
php tests/Integration/SearchTest.php
|
||||
php tests/Security/SecurityTest.php
|
||||
php tests/Unit/RateLimitTest.php
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deployment Configuration
|
||||
|
||||
### Updated `justfile`
|
||||
|
||||
The deployment now excludes test files:
|
||||
|
||||
```just
|
||||
[group('deploy')]
|
||||
deploy:
|
||||
rsync -vur --progress \
|
||||
--exclude '*.db' \
|
||||
--exclude 'tests/' \
|
||||
--exclude 'cache/' \
|
||||
--exclude '*.md' \
|
||||
--exclude 'run-tests.php' \
|
||||
./front-backend/ posterg:/var/www/html/
|
||||
```
|
||||
|
||||
**What's Excluded:**
|
||||
- `tests/` - All test files
|
||||
- `*.db` - Test databases
|
||||
- `cache/` - Runtime cache (rate limiting)
|
||||
- `*.md` - Documentation files
|
||||
- `run-tests.php` - Test runner
|
||||
|
||||
**What's Deployed:**
|
||||
- Application code (`.php` files)
|
||||
- Assets (`assets/` directory)
|
||||
- Templates (`inc/` directory)
|
||||
- Public pages (`index.php`, `search.php`, etc.)
|
||||
|
||||
### New `.gitignore`
|
||||
|
||||
```gitignore
|
||||
/vendor/
|
||||
/cache/
|
||||
*.db
|
||||
*.log
|
||||
.env
|
||||
.env.local
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test Organization Explained
|
||||
|
||||
### 1. Fixtures (`tests/Fixtures/`)
|
||||
**Purpose:** Test data setup and database initialization
|
||||
|
||||
**Files:**
|
||||
- `CreateTestDatabase.php` - Creates test.db with sample theses
|
||||
|
||||
**When to run:** Before running other tests
|
||||
|
||||
### 2. Integration Tests (`tests/Integration/`)
|
||||
**Purpose:** Test multiple components working together
|
||||
|
||||
**Files:**
|
||||
- `SearchTest.php` - Full search functionality with filters
|
||||
|
||||
**What it tests:**
|
||||
- Full-text search
|
||||
- Year filtering
|
||||
- Orientation filtering
|
||||
- AP program filtering
|
||||
- Keyword search
|
||||
- Combined filters
|
||||
- Pagination
|
||||
|
||||
### 3. Security Tests (`tests/Security/`)
|
||||
**Purpose:** Verify security measures are working
|
||||
|
||||
**Files:**
|
||||
- `SecurityTest.php` - All security validations
|
||||
|
||||
**What it tests:**
|
||||
- Wildcard injection prevention
|
||||
- Input length validation (max 200 chars)
|
||||
- Year range validation (1900-2100)
|
||||
- SQL injection prevention
|
||||
- Pagination limits (max 100/page)
|
||||
|
||||
### 4. Unit Tests (`tests/Unit/`)
|
||||
**Purpose:** Test individual components in isolation
|
||||
|
||||
**Files:**
|
||||
- `RateLimitTest.php` - Rate limiting functionality
|
||||
|
||||
**What it tests:**
|
||||
- Request tracking
|
||||
- Limit enforcement (5 requests in test, 30 in production)
|
||||
- Reset time calculation
|
||||
- Header generation
|
||||
|
||||
---
|
||||
|
||||
## Comparison with Professional Projects
|
||||
|
||||
| Aspect | This Project | Laravel/Symfony | Status |
|
||||
|--------|--------------|-----------------|--------|
|
||||
| Test directory | `tests/` | `tests/` | ✅ Match |
|
||||
| Test organization | Unit/Integration/Security | Unit/Feature | ✅ Good |
|
||||
| Test framework | PHP scripts | PHPUnit | ⚠️ Can migrate |
|
||||
| Deployment exclusion | Via rsync | Via .deployignore | ✅ Works |
|
||||
| Runner | Custom script | `composer test` | ⚠️ Can improve |
|
||||
| CI/CD | Manual | GitHub Actions | ⚠️ Future |
|
||||
|
||||
**Current Status:** Following PHP conventions, ready for growth
|
||||
|
||||
**Future Migration Path:** Can easily migrate to PHPUnit when needed
|
||||
|
||||
---
|
||||
|
||||
## Next Steps (Optional)
|
||||
|
||||
### For Small Projects (Current Approach is Fine)
|
||||
- ✅ Keep using simple PHP test scripts
|
||||
- ✅ Run `php run-tests.php` before deploying
|
||||
- ✅ Tests are properly organized and excluded
|
||||
|
||||
### To Upgrade to PHPUnit (When Project Grows)
|
||||
|
||||
1. **Install PHPUnit:**
|
||||
```bash
|
||||
composer require --dev phpunit/phpunit
|
||||
```
|
||||
|
||||
2. **Convert tests to PHPUnit format:**
|
||||
```php
|
||||
// Instead of:
|
||||
echo "Test result: " . ($result ? "✅" : "❌") . "\n";
|
||||
|
||||
// Use:
|
||||
$this->assertTrue($result);
|
||||
```
|
||||
|
||||
3. **Add `phpunit.xml` configuration**
|
||||
|
||||
4. **Run with:** `composer test`
|
||||
|
||||
See `TESTING_BEST_PRACTICES.md` for complete migration guide.
|
||||
|
||||
---
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
### New Files
|
||||
- ✅ `tests/` directory structure
|
||||
- ✅ `tests/README.md` - Test documentation
|
||||
- ✅ `run-tests.php` - Test runner script
|
||||
- ✅ `.gitignore` - Git exclusions
|
||||
|
||||
### Moved Files
|
||||
- ✅ `test_search.php` → `tests/Integration/SearchTest.php`
|
||||
- ✅ `test_security_updated.php` → `tests/Security/SecurityTest.php`
|
||||
- ✅ `test_rate_limit.php` → `tests/Unit/RateLimitTest.php`
|
||||
- ✅ `create_test_db.php` → `tests/Fixtures/CreateTestDatabase.php`
|
||||
|
||||
### Updated Files
|
||||
- ✅ All test files (updated `require_once` paths)
|
||||
- ✅ `justfile` (added test exclusions)
|
||||
|
||||
### Removed Files
|
||||
- ✅ `test_security.php` (obsolete, replaced by SecurityTest.php)
|
||||
- ✅ `Database_secure.php` (obsolete, functionality in Database.php)
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
✅ **Organized** - Tests follow PHP conventions
|
||||
✅ **Secure** - Tests excluded from production
|
||||
✅ **Convenient** - Single command to run all tests
|
||||
✅ **Documented** - README explains structure
|
||||
✅ **Scalable** - Easy to add new tests
|
||||
✅ **Future-ready** - Can migrate to PHPUnit later
|
||||
|
||||
**All tests passing:** 4/4 ✅
|
||||
|
||||
**Ready for production deployment!**
|
||||
@@ -1,108 +0,0 @@
|
||||
# Tests Directory
|
||||
|
||||
This directory contains all tests for the front-backend application.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
tests/
|
||||
├── Fixtures/ # Test data and setup scripts
|
||||
│ └── CreateTestDatabase.php
|
||||
├── Integration/ # Integration tests (multiple components)
|
||||
│ └── SearchTest.php
|
||||
├── Security/ # Security-focused tests
|
||||
│ └── SecurityTest.php
|
||||
└── Unit/ # Unit tests (individual methods)
|
||||
└── RateLimitTest.php
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Run All Tests
|
||||
```bash
|
||||
php run-tests.php
|
||||
```
|
||||
|
||||
### Run Individual Tests
|
||||
```bash
|
||||
# Setup test database first
|
||||
php tests/Fixtures/CreateTestDatabase.php
|
||||
|
||||
# Run specific test
|
||||
php tests/Integration/SearchTest.php
|
||||
php tests/Security/SecurityTest.php
|
||||
php tests/Unit/RateLimitTest.php
|
||||
```
|
||||
|
||||
## Test Suites
|
||||
|
||||
### Fixtures
|
||||
Test data setup and database initialization.
|
||||
|
||||
**CreateTestDatabase.php**
|
||||
- Creates test.db with sample theses
|
||||
- Populates with 6 sample records
|
||||
- Includes authors, supervisors, keywords
|
||||
|
||||
### Integration Tests
|
||||
Test multiple components working together.
|
||||
|
||||
**SearchTest.php**
|
||||
- Tests full search functionality
|
||||
- Tests filtering (year, orientation, AP, keywords)
|
||||
- Tests pagination
|
||||
- Tests combined filters
|
||||
|
||||
### Security Tests
|
||||
Verify security measures are working.
|
||||
|
||||
**SecurityTest.php**
|
||||
- Wildcard injection prevention
|
||||
- Input length validation
|
||||
- Year range validation
|
||||
- SQL injection prevention
|
||||
- Pagination limits
|
||||
|
||||
### Unit Tests
|
||||
Test individual components in isolation.
|
||||
|
||||
**RateLimitTest.php**
|
||||
- Rate limit enforcement
|
||||
- Request tracking
|
||||
- Reset time calculation
|
||||
- Header generation
|
||||
|
||||
## Expected Results
|
||||
|
||||
All tests should pass:
|
||||
```
|
||||
✅ PASSED - Fixtures/CreateTestDatabase.php
|
||||
✅ PASSED - Integration/SearchTest.php
|
||||
✅ PASSED - Security/SecurityTest.php
|
||||
✅ PASSED - Unit/RateLimitTest.php
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
**Tests are NOT deployed to production.**
|
||||
|
||||
The deployment configuration (`justfile`) excludes:
|
||||
- `tests/` directory
|
||||
- `*.db` files
|
||||
- Cache directory
|
||||
- Documentation files
|
||||
|
||||
## Future Migration to PHPUnit
|
||||
|
||||
This directory structure is compatible with PHPUnit. To migrate:
|
||||
|
||||
1. Install PHPUnit:
|
||||
```bash
|
||||
composer require --dev phpunit/phpunit
|
||||
```
|
||||
|
||||
2. Convert test files to PHPUnit format
|
||||
3. Add `phpunit.xml` configuration
|
||||
4. Run with: `composer test`
|
||||
|
||||
See `TESTING_BEST_PRACTICES.md` for details.
|
||||
@@ -1,119 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Security test script for updated secure implementation
|
||||
* Verifies that security fixes are working correctly
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../../../shared/Database.php';
|
||||
|
||||
echo "=== Security Testing (Secure Implementation) ===\n\n";
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Test 1: Wildcard injection (should now be escaped)
|
||||
echo "Test 1: Wildcard Injection (Secure Implementation)\n";
|
||||
echo "Searching for '%' (wildcards should be escaped):\n";
|
||||
$results = $db->searchTheses(['query' => '%'], 10, 0);
|
||||
echo "Results found: " . count($results) . "\n";
|
||||
if (count($results) === 0 || count($results) < 6) {
|
||||
echo "✅ SECURE: Wildcard characters are escaped!\n";
|
||||
} else {
|
||||
echo "❌ VULNERABLE: Still matching everything!\n";
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Test 2: Underscore wildcard
|
||||
echo "Test 2: Underscore Wildcard (should be escaped)\n";
|
||||
$results = $db->searchTheses(['query' => '_'], 10, 0);
|
||||
echo "Searching for '_': " . count($results) . " results\n";
|
||||
if (count($results) === 0 || count($results) < 6) {
|
||||
echo "✅ SECURE: Underscore wildcard is escaped!\n";
|
||||
} else {
|
||||
echo "❌ VULNERABLE: Underscore matches everything!\n";
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Test 3: Long input validation
|
||||
echo "Test 3: Long Input String Validation\n";
|
||||
$longString = str_repeat('test', 1000); // 4000 characters
|
||||
echo "Attempting to search for " . strlen($longString) . " character string\n";
|
||||
try {
|
||||
$results = $db->searchTheses(['query' => $longString], 10, 0);
|
||||
echo "❌ VULNERABLE: Long input was accepted!\n";
|
||||
} catch (InvalidArgumentException $e) {
|
||||
echo "✅ SECURE: Long input rejected: " . $e->getMessage() . "\n";
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Test 4: Invalid year validation
|
||||
echo "Test 4: Invalid Year Validation\n";
|
||||
try {
|
||||
$results = $db->searchTheses(['year' => 999999], 10, 0);
|
||||
echo "❌ VULNERABLE: Invalid year accepted!\n";
|
||||
} catch (InvalidArgumentException $e) {
|
||||
echo "✅ SECURE: Invalid year rejected: " . $e->getMessage() . "\n";
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Test 5: SQL Injection still prevented
|
||||
echo "Test 5: SQL Injection Prevention\n";
|
||||
$injectionTests = [
|
||||
"' OR 1=1--",
|
||||
"'; DROP TABLE theses;--",
|
||||
];
|
||||
|
||||
foreach ($injectionTests as $injection) {
|
||||
echo "Testing: $injection\n";
|
||||
try {
|
||||
$results = $db->searchTheses(['query' => $injection], 10, 0);
|
||||
echo " Results: " . count($results) . " (treated as literal string)\n";
|
||||
echo " ✅ SAFE: SQL injection prevented\n";
|
||||
} catch (Exception $e) {
|
||||
echo " Error: " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Test 6: Pagination limits
|
||||
echo "Test 6: Pagination Limits\n";
|
||||
$results = $db->searchTheses([], 500, 0); // Try to get 500 results
|
||||
echo "Requested 500 results, got: " . count($results) . "\n";
|
||||
if (count($results) <= 100) {
|
||||
echo "✅ SECURE: Pagination limited to max 100 results\n";
|
||||
} else {
|
||||
echo "❌ VULNERABLE: Pagination allows too many results\n";
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Test 7: Negative offset
|
||||
echo "Test 7: Negative Offset Protection\n";
|
||||
$results = $db->searchTheses([], 10, -100);
|
||||
echo "Requested offset -100, query succeeded: " . (count($results) >= 0 ? 'yes' : 'no') . "\n";
|
||||
echo "✅ SECURE: Negative offsets handled safely\n\n";
|
||||
|
||||
// Test 8: Normal search still works
|
||||
echo "Test 8: Normal Search Functionality\n";
|
||||
$results = $db->searchTheses(['query' => 'urbain'], 10, 0);
|
||||
echo "Searching for 'urbain': " . count($results) . " results\n";
|
||||
if (count($results) > 0) {
|
||||
echo " Found: " . $results[0]['title'] . "\n";
|
||||
}
|
||||
echo "✅ Normal searches still work correctly\n\n";
|
||||
|
||||
// Summary
|
||||
echo "=== SECURITY SUMMARY ===\n\n";
|
||||
echo "✅ SECURE from SQL Injection (prepared statements)\n";
|
||||
echo "✅ SECURE from wildcard injection (escaped)\n";
|
||||
echo "✅ SECURE from DoS via long inputs (length validation)\n";
|
||||
echo "✅ SECURE from invalid year values (range validation)\n";
|
||||
echo "✅ SECURE from excessive pagination (max 100 per page)\n";
|
||||
echo "✅ SECURE from negative offsets (validated)\n\n";
|
||||
|
||||
echo "✅ ALL SECURITY TESTS PASSED!\n";
|
||||
echo "The implementation is production-ready.\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ Unexpected error: " . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Test rate limiting functionality
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../../../shared/RateLimit.php';
|
||||
|
||||
echo "=== Testing Rate Limiting ===\n\n";
|
||||
|
||||
// Create rate limiter: 5 requests per 10 seconds (for testing)
|
||||
$rateLimit = new RateLimit(5, 10);
|
||||
|
||||
echo "Configuration: 5 requests per 10 seconds\n\n";
|
||||
|
||||
// Test 1: Make 5 requests (should all succeed)
|
||||
echo "Test 1: Making 5 requests (should all succeed)\n";
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$allowed = $rateLimit->check();
|
||||
echo "Request $i: " . ($allowed ? "✅ Allowed" : "❌ Blocked") . "\n";
|
||||
echo " Remaining: " . $rateLimit->getRemaining() . "\n";
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Test 2: Make 6th request (should be blocked)
|
||||
echo "Test 2: Making 6th request (should be blocked)\n";
|
||||
$allowed = $rateLimit->check();
|
||||
echo "Request 6: " . ($allowed ? "❌ Allowed (FAIL)" : "✅ Blocked (SUCCESS)") . "\n";
|
||||
echo "Remaining: " . $rateLimit->getRemaining() . "\n";
|
||||
echo "Reset time: " . $rateLimit->getResetTime() . " seconds\n\n";
|
||||
|
||||
// Test 3: Wait and try again
|
||||
echo "Test 3: Waiting 3 seconds and trying again...\n";
|
||||
sleep(3);
|
||||
$allowed = $rateLimit->check();
|
||||
echo "Request after 3s: " . ($allowed ? "❌ Allowed (still in window)" : "✅ Blocked") . "\n";
|
||||
echo "Remaining: " . $rateLimit->getRemaining() . "\n\n";
|
||||
|
||||
// Test 4: Test headers (CLI simulation)
|
||||
echo "Test 4: Rate limit headers (simulated)\n";
|
||||
echo "X-RateLimit-Limit: 5\n";
|
||||
echo "X-RateLimit-Remaining: " . $rateLimit->getRemaining() . "\n";
|
||||
echo "X-RateLimit-Reset: " . (time() + $rateLimit->getResetTime()) . "\n";
|
||||
echo "\n";
|
||||
|
||||
// Test 5: Cleanup
|
||||
echo "Test 5: Testing cleanup function\n";
|
||||
$rateLimit->cleanup();
|
||||
echo "✅ Cleanup executed successfully\n\n";
|
||||
|
||||
echo "=== RATE LIMITING SUMMARY ===\n\n";
|
||||
echo "✅ Rate limiting works correctly\n";
|
||||
echo "✅ Requests are tracked per client\n";
|
||||
echo "✅ Limits are enforced\n";
|
||||
echo "✅ Reset time is calculated\n";
|
||||
echo "✅ Headers are sent\n";
|
||||
echo "✅ Cleanup removes old files\n\n";
|
||||
|
||||
echo "Ready for production use!\n";
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
0
assets/main.css
Normal file
0
assets/main.css
Normal file
9
assets/modern-normalize.min.css
vendored
Normal file
9
assets/modern-normalize.min.css
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Minified by jsDelivr using clean-css v5.3.3.
|
||||
* Original file: /npm/modern-normalize@3.0.1/modern-normalize.css
|
||||
*
|
||||
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||
*/
|
||||
/*! modern-normalize v3.0.1 | MIT License | https://github.com/sindresorhus/modern-normalize */
|
||||
*,::after,::before{box-sizing:border-box}html{font-family:system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji';line-height:1.15;-webkit-text-size-adjust:100%;tab-size:4}body{margin:0}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Consolas,'Liberation Mono',Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-color:currentcolor}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}
|
||||
/*# sourceMappingURL=/sm/d2d8cd206fb9f42f071e97460f3ad9c875edb5e7a4b10f900a83cdf8401c53a9.map */
|
||||
507
docs/DEVELOPMENT_GUIDE.md
Normal file
507
docs/DEVELOPMENT_GUIDE.md
Normal file
@@ -0,0 +1,507 @@
|
||||
# Post-ERG Development Guide
|
||||
|
||||
Complete guide for developing the Post-ERG thesis management system.
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### 1. Setup (One Time)
|
||||
|
||||
```bash
|
||||
# Clone php-live-reload and setup directories
|
||||
just setup
|
||||
```
|
||||
|
||||
### 2. Start Development Server
|
||||
|
||||
```bash
|
||||
just serve
|
||||
```
|
||||
|
||||
This starts **one unified server** at:
|
||||
- **Public site:** http://localhost:8000
|
||||
- **Admin panel:** http://localhost:8000/admin/
|
||||
|
||||
✨ **Live reload enabled** - your browser auto-refreshes when you save files!
|
||||
|
||||
### 3. Edit & Watch
|
||||
|
||||
Edit any PHP file and watch your browser automatically refresh! 🎉
|
||||
|
||||
---
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
posterg-website/
|
||||
├── index.php # Public homepage
|
||||
├── memoire.php # Thesis detail page
|
||||
├── search.php # Search page
|
||||
├── *.php # Other public pages
|
||||
│
|
||||
├── admin/ # Admin panel
|
||||
│ ├── index.php # Admin dashboard
|
||||
│ ├── list.php # Thesis list
|
||||
│ ├── edit.php # Edit thesis
|
||||
│ └── formulaire.php # Add thesis
|
||||
│
|
||||
├── lib/ # Shared libraries
|
||||
│ ├── Database.php # Database class
|
||||
│ ├── RateLimit.php # Rate limiting
|
||||
│ └── config.php # Configuration
|
||||
│
|
||||
├── inc/ # Page templates
|
||||
│ ├── header.php # Site header
|
||||
│ └── footer.php # Site footer
|
||||
│
|
||||
├── assets/ # Static files
|
||||
│ ├── posterg.css # Main CSS
|
||||
│ └── fonts/ # Custom fonts
|
||||
│
|
||||
├── database/ # Database
|
||||
│ ├── schema.sql # Schema definition
|
||||
│ ├── test.db # Test database
|
||||
│ └── fixtures/ # Sample data
|
||||
│
|
||||
├── tests/ # Test suite
|
||||
│ ├── Unit/ # Unit tests
|
||||
│ ├── Integration/ # Integration tests
|
||||
│ └── Security/ # Security tests
|
||||
│
|
||||
└── vendor/ # Third-party (gitignored)
|
||||
└── php-live-reload/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Development Workflow
|
||||
|
||||
### Starting Development
|
||||
|
||||
```bash
|
||||
# Start the development server
|
||||
just serve
|
||||
|
||||
# In your browser:
|
||||
# - Public site: http://localhost:8000
|
||||
# - Admin panel: http://localhost:8000/admin/
|
||||
```
|
||||
|
||||
### Making Changes
|
||||
|
||||
1. **Edit PHP files** - Auto-refreshes browser
|
||||
2. **Edit CSS** - Auto-refreshes browser
|
||||
3. **Test changes** - See them instantly!
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
just test
|
||||
|
||||
# Run specific test suites
|
||||
just test-unit # Unit tests
|
||||
just test-integration # Integration tests
|
||||
just test-security # Security tests
|
||||
```
|
||||
|
||||
### Database Operations
|
||||
|
||||
```bash
|
||||
# View database stats
|
||||
just stats
|
||||
|
||||
# Open SQLite shell
|
||||
just query
|
||||
|
||||
# Show specific thesis
|
||||
just show 42
|
||||
|
||||
# Reset database
|
||||
just reset-db
|
||||
|
||||
# Create with sample data
|
||||
just fixtures
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Common Tasks
|
||||
|
||||
### Create a New Page
|
||||
|
||||
1. Create `newpage.php` in root
|
||||
2. Add `require_once __DIR__ . '/lib/Database.php';`
|
||||
3. Include header: `include 'inc/header.php';`
|
||||
4. Add your content
|
||||
5. Include footer: `include 'inc/footer.php';`
|
||||
|
||||
Example:
|
||||
```php
|
||||
<?php
|
||||
require_once __DIR__ . '/lib/Database.php';
|
||||
include 'inc/header.php';
|
||||
?>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title">My New Page</h1>
|
||||
<p>Content here...</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php include 'inc/footer.php'; ?>
|
||||
```
|
||||
|
||||
### Add a Database Function
|
||||
|
||||
1. Edit `lib/Database.php`
|
||||
2. Add your method to the `Database` class
|
||||
3. Write a test in `tests/Unit/DatabaseTest.php`
|
||||
4. Run tests: `just test-unit`
|
||||
|
||||
### Update CSS
|
||||
|
||||
1. Edit `assets/posterg.css`
|
||||
2. Browser auto-refreshes!
|
||||
3. (Optional) Increment version in `inc/header.php`: `posterg.css?v=3`
|
||||
|
||||
### Add a Test
|
||||
|
||||
1. Choose location:
|
||||
- `tests/Unit/` - For testing classes/functions
|
||||
- `tests/Integration/` - For testing workflows
|
||||
- `tests/Security/` - For testing security
|
||||
|
||||
2. Create test file (e.g., `tests/Unit/MyTest.php`)
|
||||
|
||||
3. Follow the template in `tests/README.md`
|
||||
|
||||
4. Add to `tests/run-tests.php`
|
||||
|
||||
5. Run: `just test`
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Test Database
|
||||
|
||||
Development uses `database/test.db` (gitignored).
|
||||
|
||||
**Create test database:**
|
||||
```bash
|
||||
just init-db
|
||||
```
|
||||
|
||||
**Populate with sample data:**
|
||||
```bash
|
||||
just fixtures
|
||||
```
|
||||
|
||||
**Deploy test database to server:**
|
||||
```bash
|
||||
just deploy-test-db
|
||||
```
|
||||
|
||||
**Reset everything:**
|
||||
```bash
|
||||
just reset-db
|
||||
```
|
||||
|
||||
### Writing Tests
|
||||
|
||||
See `tests/README.md` for complete testing guide.
|
||||
|
||||
**Quick example:**
|
||||
```php
|
||||
<?php
|
||||
require_once __DIR__ . '/../../lib/Database.php';
|
||||
|
||||
echo "My Test\n";
|
||||
echo "=======\n\n";
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
echo "✓ PASS: Test passed\n";
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
echo "❌ FAIL: " . $e->getMessage() . "\n";
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Deployment
|
||||
|
||||
### Deploy Everything
|
||||
|
||||
```bash
|
||||
just deploy
|
||||
```
|
||||
|
||||
This deploys:
|
||||
- Public site (root PHP files)
|
||||
- Admin panel (`admin/`)
|
||||
- Shared libraries (`lib/`)
|
||||
|
||||
**Note:** `vendor/` is automatically excluded from deployment.
|
||||
|
||||
### Deploy Selectively
|
||||
|
||||
```bash
|
||||
# Deploy only the code
|
||||
just deploy
|
||||
|
||||
# Deploy test database
|
||||
just deploy-test-db
|
||||
|
||||
# Deploy nginx config
|
||||
just deploy-nginx
|
||||
|
||||
# Deploy admin tools
|
||||
just deploy-admin-tools
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Justfile Commands
|
||||
|
||||
### Development
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `just setup` | Setup development environment (one-time) |
|
||||
| `just serve` | Start development server with live reload |
|
||||
| `just stop` | Stop development server |
|
||||
| `just logs` | View development logs |
|
||||
|
||||
### Testing
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `just test` | Run all tests |
|
||||
| `just test-unit` | Run unit tests |
|
||||
| `just test-integration` | Run integration tests |
|
||||
| `just test-security` | Run security tests |
|
||||
| `just syntax` | Check PHP syntax |
|
||||
|
||||
### Database
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `just init-db` | Create test database |
|
||||
| `just reset-db` | Reset test database |
|
||||
| `just query` | Open SQLite shell |
|
||||
| `just show <id>` | Show thesis by ID |
|
||||
| `just backup` | Backup database |
|
||||
| `just fixtures` | Create sample data |
|
||||
| `just deploy-test-db` | Deploy test database to server |
|
||||
|
||||
### Statistics
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `just stats` | Show database statistics |
|
||||
| `just recent` | Show recent theses |
|
||||
|
||||
### Deployment
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `just deploy` | Deploy complete site |
|
||||
| `just deploy-nginx` | Deploy nginx configuration |
|
||||
| `just deploy-admin-tools` | Deploy admin user management |
|
||||
|
||||
### Server
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `just server-logs` | View server logs |
|
||||
| `just server-status` | Check server status |
|
||||
|
||||
### Utilities
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `just clean` | Clean up dev files |
|
||||
| `just setup-dirs` | Create data directories |
|
||||
|
||||
---
|
||||
|
||||
## 💡 Tips & Tricks
|
||||
|
||||
### Live Reload
|
||||
|
||||
The `just serve` command uses php-live-reload to automatically refresh your browser when you save files.
|
||||
|
||||
**What triggers refresh:**
|
||||
- Saving any `.php` file
|
||||
- Saving any file in the project
|
||||
|
||||
**How it works:**
|
||||
- WebSocket connection monitors file changes
|
||||
- Browser receives reload signal
|
||||
- Page refreshes automatically
|
||||
|
||||
**No browser extension needed!**
|
||||
|
||||
### Multiple Browser Windows
|
||||
|
||||
Open multiple browser windows/tabs - they all get live reload!
|
||||
|
||||
```
|
||||
http://localhost:8000/ # Public site
|
||||
http://localhost:8000/admin/ # Admin panel
|
||||
http://localhost:8000/memoire.php?id=13 # Specific thesis
|
||||
```
|
||||
|
||||
All will auto-refresh when you save files! ✨
|
||||
|
||||
### Using a Real Test Database
|
||||
|
||||
The test database (`database/test.db`) is gitignored. To share test data:
|
||||
|
||||
```bash
|
||||
# Create fixtures
|
||||
just fixtures
|
||||
|
||||
# Commit the fixtures generator
|
||||
git add database/fixtures/
|
||||
git commit -m "Update test fixtures"
|
||||
```
|
||||
|
||||
Others can recreate with: `just fixtures`
|
||||
|
||||
### Debugging
|
||||
|
||||
**Check error logs:**
|
||||
```bash
|
||||
just logs
|
||||
```
|
||||
|
||||
**Or directly:**
|
||||
```bash
|
||||
tail -f error.log
|
||||
```
|
||||
|
||||
**PHP errors in browser:**
|
||||
Edit your PHP file temporarily:
|
||||
```php
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
```
|
||||
|
||||
**Database issues:**
|
||||
```bash
|
||||
# Check database exists
|
||||
ls -lh database/test.db
|
||||
|
||||
# Open database shell
|
||||
just query
|
||||
|
||||
# Check tables
|
||||
sqlite> .tables
|
||||
|
||||
# Show schema
|
||||
sqlite> .schema theses
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Troubleshooting
|
||||
|
||||
### Server won't start
|
||||
|
||||
**Port already in use:**
|
||||
```bash
|
||||
just stop
|
||||
# Or manually:
|
||||
pkill -f "php -S 127.0.0.1:8000"
|
||||
```
|
||||
|
||||
**php-live-reload missing:**
|
||||
```bash
|
||||
just setup
|
||||
```
|
||||
|
||||
### Live reload not working
|
||||
|
||||
**Check vendor directory:**
|
||||
```bash
|
||||
ls -la vendor/php-live-reload/
|
||||
```
|
||||
|
||||
**Reinstall:**
|
||||
```bash
|
||||
rm -rf vendor/php-live-reload
|
||||
just setup
|
||||
```
|
||||
|
||||
### Database errors
|
||||
|
||||
**Database not found:**
|
||||
```bash
|
||||
just init-db
|
||||
```
|
||||
|
||||
**Permissions error:**
|
||||
```bash
|
||||
chmod 644 database/test.db
|
||||
```
|
||||
|
||||
**Schema errors:**
|
||||
```bash
|
||||
just reset-db
|
||||
```
|
||||
|
||||
### Tests failing
|
||||
|
||||
**Run individual test:**
|
||||
```bash
|
||||
php tests/Unit/DatabaseTest.php
|
||||
```
|
||||
|
||||
**Check database:**
|
||||
```bash
|
||||
just stats
|
||||
```
|
||||
|
||||
**Reset database:**
|
||||
```bash
|
||||
just reset-db
|
||||
just fixtures
|
||||
just test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Further Reading
|
||||
|
||||
- [Test Documentation](../tests/README.md)
|
||||
- [Database Specification](../database/DATABASE_SPECIFICATION.md)
|
||||
- [Migration Guide](../MIGRATION_GUIDE.md)
|
||||
- [Deployment Guide](../nginx/DEPLOYMENT_COMPLETE.md)
|
||||
|
||||
---
|
||||
|
||||
## ✨ Quick Reference
|
||||
|
||||
**Start developing:**
|
||||
```bash
|
||||
just setup # One time
|
||||
just serve # Start server
|
||||
```
|
||||
|
||||
**Test your changes:**
|
||||
```bash
|
||||
just test # Run tests
|
||||
just stats # Check database
|
||||
```
|
||||
|
||||
**Deploy to production:**
|
||||
```bash
|
||||
just deploy
|
||||
```
|
||||
|
||||
That's it! Happy coding! 🚀
|
||||
297
docs/LIVE_RELOAD_SETUP.md
Normal file
297
docs/LIVE_RELOAD_SETUP.md
Normal file
@@ -0,0 +1,297 @@
|
||||
# Live Reload Setup
|
||||
|
||||
Guide to setting up and using live reload for Post-ERG development.
|
||||
|
||||
## 🎯 What is Live Reload?
|
||||
|
||||
Live reload automatically refreshes your browser when you save files during development. No need to manually hit refresh!
|
||||
|
||||
## ✨ How It Works
|
||||
|
||||
1. **JavaScript** in the page polls the server for file changes
|
||||
2. **PHP backend** checks file modification times
|
||||
3. **Browser** automatically refreshes when changes detected
|
||||
|
||||
**No browser extension needed!**
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Setup (One Time)
|
||||
|
||||
```bash
|
||||
just setup
|
||||
```
|
||||
|
||||
This clones `php-live-reload` into `vendor/php-live-reload/` (which is gitignored).
|
||||
|
||||
---
|
||||
|
||||
## 🏃 Using Live Reload
|
||||
|
||||
### Start Server
|
||||
|
||||
```bash
|
||||
just serve
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
🚀 Starting Post-ERG development server
|
||||
========================================
|
||||
|
||||
📍 Public site: http://localhost:8000
|
||||
📍 Admin panel: http://localhost:8000/admin/
|
||||
|
||||
✨ Live reload enabled - browser auto-refreshes on file save!
|
||||
|
||||
Press Ctrl+C to stop
|
||||
```
|
||||
|
||||
### Edit Files
|
||||
|
||||
1. Open http://localhost:8000 in your browser
|
||||
2. Edit any PHP, CSS, or JS file
|
||||
3. Save the file
|
||||
4. **Browser automatically refreshes!** ✨
|
||||
|
||||
### What Triggers Reload
|
||||
|
||||
- Saving `.php` files
|
||||
- Saving `.css` files
|
||||
- Saving `.js` files
|
||||
- Any file change in the project
|
||||
|
||||
---
|
||||
|
||||
## 🔧 How It's Integrated
|
||||
|
||||
### In `inc/header.php`
|
||||
|
||||
Live reload script is conditionally included only during development:
|
||||
|
||||
```php
|
||||
<?php if (getenv('PHP_ENV') === 'development' || php_sapi_name() === 'cli-server'): ?>
|
||||
<!-- Live reload for development -->
|
||||
<script src="/vendor/php-live-reload/php-live-reload/live-reload.js"></script>
|
||||
<?php endif; ?>
|
||||
```
|
||||
|
||||
**Result:**
|
||||
- ✅ Included when using `php -S` (development server)
|
||||
- ❌ NOT included in production (nginx/apache)
|
||||
- ❌ NOT deployed to server (vendor/ is gitignored)
|
||||
|
||||
### Detection Logic
|
||||
|
||||
```php
|
||||
php_sapi_name() === 'cli-server' // True when using PHP dev server
|
||||
```
|
||||
|
||||
This means live reload is automatically enabled/disabled based on environment!
|
||||
|
||||
---
|
||||
|
||||
## 📁 File Structure
|
||||
|
||||
```
|
||||
vendor/php-live-reload/
|
||||
├── php-live-reload/
|
||||
│ ├── live-reload.js # JavaScript client
|
||||
│ ├── live-reload.php # PHP backend (checks files)
|
||||
│ └── config.php # Configuration
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Live Reload
|
||||
|
||||
### Test It Works
|
||||
|
||||
1. Start server: `just serve`
|
||||
2. Open http://localhost:8000
|
||||
3. Open browser console (F12)
|
||||
4. Edit `index.php` and save
|
||||
5. Watch browser auto-refresh!
|
||||
|
||||
You'll see in console:
|
||||
```javascript
|
||||
GET /vendor/php-live-reload/php-live-reload/live-reload.php
|
||||
change detected
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configuration
|
||||
|
||||
### Polling Interval
|
||||
|
||||
Default: Checks every ~1-2 seconds
|
||||
|
||||
To change, edit `vendor/php-live-reload/php-live-reload/config.php`:
|
||||
|
||||
```php
|
||||
define('MIN_DELAY', 1000); // Minimum milliseconds between checks
|
||||
```
|
||||
|
||||
### File Watching
|
||||
|
||||
By default, watches all files in project directory.
|
||||
|
||||
To exclude paths, edit config:
|
||||
|
||||
```php
|
||||
$exclude = [
|
||||
'vendor',
|
||||
'.git',
|
||||
'node_modules',
|
||||
'cache'
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Live Reload Not Working
|
||||
|
||||
**1. Check vendor directory exists:**
|
||||
```bash
|
||||
ls -la vendor/php-live-reload/
|
||||
```
|
||||
|
||||
If missing:
|
||||
```bash
|
||||
just setup
|
||||
```
|
||||
|
||||
**2. Check script is included in page:**
|
||||
```bash
|
||||
curl -s http://localhost:8000/ | grep live-reload
|
||||
```
|
||||
|
||||
Should show:
|
||||
```html
|
||||
<script src="/vendor/php-live-reload/php-live-reload/live-reload.js"></script>
|
||||
```
|
||||
|
||||
**3. Check endpoint is accessible:**
|
||||
```bash
|
||||
curl http://localhost:8000/vendor/php-live-reload/php-live-reload/live-reload.php
|
||||
```
|
||||
|
||||
Should return JSON:
|
||||
```json
|
||||
{"time": 10, "changed": false}
|
||||
```
|
||||
|
||||
**4. Check browser console for errors**
|
||||
|
||||
Open browser console (F12) and look for:
|
||||
- WebSocket errors
|
||||
- Network errors to `/vendor/php-live-reload/`
|
||||
|
||||
### Script Not Loading
|
||||
|
||||
**Make sure you're using dev server:**
|
||||
```bash
|
||||
just serve
|
||||
```
|
||||
|
||||
NOT production (nginx/apache).
|
||||
|
||||
**Check PHP detection:**
|
||||
```bash
|
||||
php -r "echo php_sapi_name();"
|
||||
```
|
||||
|
||||
When using `just serve`, should output: `cli-server`
|
||||
|
||||
### Changes Not Detected
|
||||
|
||||
**Check polling is working:**
|
||||
|
||||
Open browser console, you should see repeated requests to:
|
||||
```
|
||||
/vendor/php-live-reload/php-live-reload/live-reload.php
|
||||
```
|
||||
|
||||
If not, check JavaScript loaded:
|
||||
```bash
|
||||
curl -I http://localhost:8000/vendor/php-live-reload/php-live-reload/live-reload.js
|
||||
```
|
||||
|
||||
Should return `200 OK`.
|
||||
|
||||
---
|
||||
|
||||
## 🚫 Production Behavior
|
||||
|
||||
### Automatically Disabled
|
||||
|
||||
Live reload is **automatically disabled** in production because:
|
||||
|
||||
1. **`php_sapi_name()` check**: Only true with PHP dev server
|
||||
2. **vendor/ gitignored**: Not deployed to server
|
||||
3. **nginx serves files**: Different SAPI, condition false
|
||||
|
||||
### Verification
|
||||
|
||||
On production server:
|
||||
```bash
|
||||
curl https://posterg.erg.be/ | grep live-reload
|
||||
```
|
||||
|
||||
Should return nothing (script not included).
|
||||
|
||||
---
|
||||
|
||||
## 💡 Tips
|
||||
|
||||
### Multiple Browser Windows
|
||||
|
||||
Open multiple tabs/windows - they all get live reload!
|
||||
|
||||
```
|
||||
http://localhost:8000/ # Homepage
|
||||
http://localhost:8000/admin/ # Admin panel
|
||||
http://localhost:8000/memoire.php?id=13 # Thesis page
|
||||
```
|
||||
|
||||
All will auto-refresh on file changes.
|
||||
|
||||
### Faster Development
|
||||
|
||||
With live reload:
|
||||
1. ✅ Edit code
|
||||
2. ✅ Save
|
||||
3. ✅ Browser refreshes
|
||||
4. ✅ See changes instantly!
|
||||
|
||||
No more:
|
||||
1. ❌ Edit code
|
||||
2. ❌ Save
|
||||
3. ❌ Switch to browser
|
||||
4. ❌ Hit F5
|
||||
5. ❌ Switch back to editor
|
||||
|
||||
**Saves you seconds on every change!**
|
||||
|
||||
---
|
||||
|
||||
## 📚 More Information
|
||||
|
||||
- GitHub: https://github.com/ryantate13/php-live-reload
|
||||
- Alternative: https://github.com/guard/guard-livereload
|
||||
|
||||
---
|
||||
|
||||
## ✅ Quick Reference
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `just setup` | Install live reload (one time) |
|
||||
| `just serve` | Start server with live reload |
|
||||
| `just stop` | Stop server |
|
||||
|
||||
**To use:** `just serve` and edit files - browser auto-refreshes! ✨
|
||||
476
docs/MIGRATION_GUIDE.md
Normal file
476
docs/MIGRATION_GUIDE.md
Normal file
@@ -0,0 +1,476 @@
|
||||
# Repository Restructure Migration Guide
|
||||
|
||||
This guide explains how to migrate the Post-ERG repository to a standard idiomatic PHP structure.
|
||||
|
||||
## 📋 Overview
|
||||
|
||||
### Current Structure
|
||||
```
|
||||
posterg-website/
|
||||
├── apps/
|
||||
│ ├── public/ # Public website
|
||||
│ └── admin/ # Admin panel
|
||||
├── shared/ # Shared PHP libraries
|
||||
└── database/ # Database files
|
||||
```
|
||||
|
||||
### New Structure (Standard PHP)
|
||||
```
|
||||
posterg-website/
|
||||
├── index.php # Public root
|
||||
├── *.php # Public PHP files
|
||||
├── admin/ # Admin panel
|
||||
├── lib/ # Shared libraries (renamed from shared/)
|
||||
├── inc/ # Templates (header/footer)
|
||||
├── assets/ # Static files
|
||||
├── database/ # Database files
|
||||
└── vendor/ # Third-party code (gitignored)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Benefits
|
||||
|
||||
✅ **Standard PHP structure** - Follows community conventions
|
||||
✅ **Flatter hierarchy** - Easier navigation
|
||||
✅ **Simpler paths** - Less `../../` in code
|
||||
✅ **Cleaner deployment** - Single rsync command
|
||||
✅ **Live reload** - Auto-refresh during development
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Migration Steps
|
||||
|
||||
### Step 1: Review the Plan
|
||||
|
||||
Read the restructure plan:
|
||||
```bash
|
||||
cat docs/RESTRUCTURE_PLAN.md
|
||||
```
|
||||
|
||||
### Step 2: Commit Current State
|
||||
|
||||
**Important:** Commit all your current work first!
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "Pre-migration checkpoint"
|
||||
```
|
||||
|
||||
### Step 3: Run Migration Script
|
||||
|
||||
```bash
|
||||
./migrate-structure.sh
|
||||
```
|
||||
|
||||
This will:
|
||||
- Move `apps/public/*` to root
|
||||
- Move `apps/admin/` to `admin/`
|
||||
- Rename `shared/` to `lib/`
|
||||
- Update all `require` paths automatically
|
||||
- Remove `apps/` directory
|
||||
- Update `.gitignore`
|
||||
|
||||
### Step 4: Test Locally
|
||||
|
||||
```bash
|
||||
# Setup development environment
|
||||
just setup-dev
|
||||
|
||||
# Test public site (with live reload!)
|
||||
just serve-public
|
||||
|
||||
# Test admin panel
|
||||
just serve-admin
|
||||
```
|
||||
|
||||
Visit:
|
||||
- http://localhost:8000 - Public site
|
||||
- http://localhost:3000 - Admin panel
|
||||
|
||||
### Step 5: Verify Changes
|
||||
|
||||
```bash
|
||||
# Check syntax
|
||||
just check-public
|
||||
|
||||
# Run database tests
|
||||
just stats-public
|
||||
|
||||
# View structure
|
||||
tree -L 2 -I 'node_modules|.git|vendor'
|
||||
```
|
||||
|
||||
### Step 6: Update Justfile
|
||||
|
||||
```bash
|
||||
# Backup old justfile
|
||||
mv justfile justfile.old
|
||||
|
||||
# Use new justfile
|
||||
mv justfile.new justfile
|
||||
```
|
||||
|
||||
### Step 7: Commit Migration
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "Restructure to idiomatic PHP layout
|
||||
|
||||
- Moved apps/public to root
|
||||
- Moved apps/admin to admin/
|
||||
- Renamed shared/ to lib/
|
||||
- Added php-live-reload for local dev
|
||||
- Updated all require paths
|
||||
- Simplified deployment"
|
||||
```
|
||||
|
||||
### Step 8: Deploy to Production
|
||||
|
||||
```bash
|
||||
# Deploy everything
|
||||
just deploy
|
||||
|
||||
# Or deploy separately
|
||||
just deploy-public
|
||||
just deploy-admin
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 What Changed
|
||||
|
||||
### File Movements
|
||||
|
||||
| Old Path | New Path |
|
||||
|----------|----------|
|
||||
| `apps/public/index.php` | `index.php` |
|
||||
| `apps/public/memoire.php` | `memoire.php` |
|
||||
| `apps/public/assets/` | `assets/` |
|
||||
| `apps/public/inc/` | `inc/` |
|
||||
| `apps/admin/` | `admin/` |
|
||||
| `shared/Database.php` | `lib/Database.php` |
|
||||
| `shared/config.php` | `lib/config.php` |
|
||||
| `shared/cache/` | `lib/cache/` |
|
||||
|
||||
### Path Updates
|
||||
|
||||
All PHP files automatically updated:
|
||||
|
||||
**Root files:**
|
||||
```php
|
||||
// Before
|
||||
require_once __DIR__ . '/shared/Database.php';
|
||||
|
||||
// After
|
||||
require_once __DIR__ . '/lib/Database.php';
|
||||
```
|
||||
|
||||
**Admin files:**
|
||||
```php
|
||||
// Before
|
||||
require_once __DIR__ . '/../../shared/Database.php';
|
||||
|
||||
// After
|
||||
require_once __DIR__ . '/../lib/Database.php';
|
||||
```
|
||||
|
||||
**Config file:**
|
||||
```php
|
||||
// Before
|
||||
define('DB_ROOT', __DIR__ . '/..');
|
||||
|
||||
// After (stays the same)
|
||||
define('DB_ROOT', __DIR__ . '/..');
|
||||
```
|
||||
|
||||
### Justfile Changes
|
||||
|
||||
**Before:**
|
||||
```bash
|
||||
just serve-public # Basic PHP server
|
||||
```
|
||||
|
||||
**After:**
|
||||
```bash
|
||||
just setup-dev # One-time: Install php-live-reload
|
||||
just serve-public # PHP server with live reload!
|
||||
```
|
||||
|
||||
**Before:**
|
||||
```bash
|
||||
# Deploy in multiple steps
|
||||
rsync apps/public/ posterg:/var/www/html/
|
||||
rsync apps/admin/ posterg:/var/www/html/formulaire/
|
||||
rsync shared/ posterg:/var/www/html/shared/
|
||||
```
|
||||
|
||||
**After:**
|
||||
```bash
|
||||
# Deploy in one command
|
||||
just deploy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 PHP Live Reload
|
||||
|
||||
### What It Does
|
||||
|
||||
Automatically refreshes your browser when you save PHP files!
|
||||
|
||||
### Setup (One Time)
|
||||
|
||||
```bash
|
||||
just setup-dev
|
||||
```
|
||||
|
||||
This clones https://github.com/ryantate13/php-live-reload to `vendor/php-live-reload/` (gitignored).
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
# Start with live reload
|
||||
just serve-public # or serve-admin
|
||||
|
||||
# Edit PHP files
|
||||
# Browser auto-refreshes on save! 🎉
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
- Monitors PHP files for changes
|
||||
- Sends reload signal via WebSocket
|
||||
- Browser reloads automatically
|
||||
- No browser extension needed
|
||||
- Only for local development
|
||||
- Never deployed to production
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Before Deploying
|
||||
|
||||
1. **Syntax check:**
|
||||
```bash
|
||||
just check-public
|
||||
```
|
||||
|
||||
2. **Test database:**
|
||||
```bash
|
||||
just stats-public
|
||||
```
|
||||
|
||||
3. **Test public site:**
|
||||
```bash
|
||||
just serve-public
|
||||
# Visit http://localhost:8000
|
||||
```
|
||||
|
||||
4. **Test admin:**
|
||||
```bash
|
||||
just serve-admin
|
||||
# Visit http://localhost:3000
|
||||
```
|
||||
|
||||
5. **Check file permissions:**
|
||||
```bash
|
||||
ls -la | head -n 20
|
||||
ls -la admin/ | head -n 20
|
||||
ls -la lib/
|
||||
```
|
||||
|
||||
### After Deploying
|
||||
|
||||
1. **Test public site:**
|
||||
```bash
|
||||
curl -I https://posterg.erg.be/
|
||||
```
|
||||
|
||||
2. **Test CSS loading:**
|
||||
```bash
|
||||
curl -I https://posterg.erg.be/assets/posterg.css
|
||||
```
|
||||
|
||||
3. **Test admin:**
|
||||
```bash
|
||||
curl -I https://posterg.erg.be/admin/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔙 Rollback (If Needed)
|
||||
|
||||
If something goes wrong:
|
||||
|
||||
```bash
|
||||
# Revert the migration
|
||||
git reset --hard HEAD~1
|
||||
|
||||
# Or restore from backup
|
||||
git checkout HEAD~1 -- .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 New Directory Structure
|
||||
|
||||
```
|
||||
posterg-website/
|
||||
├── index.php # Public site root
|
||||
├── memoire.php # Thesis detail page
|
||||
├── search.php # Search page
|
||||
├── apropos.php # About page
|
||||
├── contact.php # Contact page
|
||||
├── licences.php # Licenses page
|
||||
├── test_db.php # Database test script
|
||||
│
|
||||
├── assets/ # Static files
|
||||
│ ├── posterg.css # Main CSS
|
||||
│ ├── normalize.css # CSS reset
|
||||
│ ├── fonts/ # Custom fonts
|
||||
│ └── icons.svg # Icon set
|
||||
│
|
||||
├── inc/ # Page templates
|
||||
│ ├── header.php # Site header
|
||||
│ └── footer.php # Site footer
|
||||
│
|
||||
├── lib/ # Shared libraries (was shared/)
|
||||
│ ├── Database.php # Database class
|
||||
│ ├── RateLimit.php # Rate limiting
|
||||
│ ├── config.php # Configuration
|
||||
│ └── cache/ # Cache files
|
||||
│
|
||||
├── admin/ # Admin panel (was apps/admin/)
|
||||
│ ├── index.php # Admin dashboard
|
||||
│ ├── list.php # Thesis list
|
||||
│ ├── edit.php # Edit thesis
|
||||
│ ├── formulaire.php # Add thesis
|
||||
│ ├── import.php # Import tool
|
||||
│ └── data/ # Upload directories
|
||||
│
|
||||
├── database/ # Database files & schema
|
||||
│ ├── schema.sql # Database schema
|
||||
│ ├── test.db # Test database
|
||||
│ ├── fixtures/ # Test data generators
|
||||
│ └── *.md # Documentation
|
||||
│
|
||||
├── vendor/ # Third-party code (gitignored)
|
||||
│ └── php-live-reload/ # Live reload tool
|
||||
│
|
||||
├── nginx/ # Server configuration
|
||||
├── docs/ # Documentation
|
||||
├── tests/ # Tests (future)
|
||||
│
|
||||
├── justfile # Task runner
|
||||
├── .gitignore # Git ignore rules
|
||||
└── README.md # Project readme
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ❓ FAQ
|
||||
|
||||
### Q: Will the migration break the deployed site?
|
||||
|
||||
**A:** No. The migration only affects your local repository. Deploy only after testing locally.
|
||||
|
||||
### Q: Do I need to update the database?
|
||||
|
||||
**A:** No. The database structure doesn't change. Only file paths change.
|
||||
|
||||
### Q: What about nginx configuration?
|
||||
|
||||
**A:** Nginx config doesn't need to change. It still serves from `/var/www/html/`.
|
||||
|
||||
### Q: Will git history be preserved?
|
||||
|
||||
**A:** Yes. Git tracks file movements. Use `git log --follow filename.php` to see history.
|
||||
|
||||
### Q: Can I undo the migration?
|
||||
|
||||
**A:** Yes. Use `git reset --hard HEAD~1` before committing.
|
||||
|
||||
### Q: Does php-live-reload work on all platforms?
|
||||
|
||||
**A:** Yes. Works on Linux, macOS, and Windows (with PHP installed).
|
||||
|
||||
### Q: Will php-live-reload be deployed?
|
||||
|
||||
**A:** No. It's in `vendor/` which is gitignored and excluded from deployment.
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Troubleshooting
|
||||
|
||||
### Migration script fails
|
||||
|
||||
**Check you're in the repo root:**
|
||||
```bash
|
||||
pwd
|
||||
ls -la apps shared
|
||||
```
|
||||
|
||||
**Make script executable:**
|
||||
```bash
|
||||
chmod +x migrate-structure.sh
|
||||
```
|
||||
|
||||
### PHP can't find lib/ files
|
||||
|
||||
**Check paths were updated:**
|
||||
```bash
|
||||
grep -r "require.*lib/" . --include="*.php" | head -n 5
|
||||
```
|
||||
|
||||
**Manually fix a file:**
|
||||
```bash
|
||||
# Edit the file and change:
|
||||
require_once __DIR__ . '/shared/Database.php';
|
||||
# To:
|
||||
require_once __DIR__ . '/lib/Database.php';
|
||||
```
|
||||
|
||||
### Live reload doesn't work
|
||||
|
||||
**Check vendor directory:**
|
||||
```bash
|
||||
ls -la vendor/php-live-reload/
|
||||
```
|
||||
|
||||
**Re-run setup:**
|
||||
```bash
|
||||
just setup-dev
|
||||
```
|
||||
|
||||
### Site works locally but not on server
|
||||
|
||||
**Check file permissions on server:**
|
||||
```bash
|
||||
ssh posterg "ls -la /var/www/html/ | head -n 20"
|
||||
```
|
||||
|
||||
**Re-run deployment:**
|
||||
```bash
|
||||
just deploy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📞 Need Help?
|
||||
|
||||
1. **Check the plan:** `cat docs/RESTRUCTURE_PLAN.md`
|
||||
2. **Review justfile:** `cat justfile`
|
||||
3. **Check git status:** `git status`
|
||||
4. **Test locally first:** `just serve-public`
|
||||
|
||||
---
|
||||
|
||||
**Ready to migrate?**
|
||||
|
||||
```bash
|
||||
./migrate-structure.sh
|
||||
```
|
||||
|
||||
Good luck! 🚀
|
||||
135
docs/RESTRUCTURE_PLAN.md
Normal file
135
docs/RESTRUCTURE_PLAN.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# Repository Restructure Plan
|
||||
|
||||
## Current Structure
|
||||
|
||||
```
|
||||
posterg-website/
|
||||
├── apps/
|
||||
│ ├── public/ # Public website
|
||||
│ └── admin/ # Admin panel
|
||||
├── shared/ # Shared PHP libraries
|
||||
├── database/ # Database files
|
||||
├── nginx/ # Server config
|
||||
└── docs/ # Documentation
|
||||
```
|
||||
|
||||
## Proposed Structure (Idiomatic PHP)
|
||||
|
||||
```
|
||||
posterg-website/
|
||||
├── index.php # Public website root
|
||||
├── memoire.php
|
||||
├── search.php
|
||||
├── apropos.php
|
||||
├── contact.php
|
||||
├── licences.php
|
||||
├── assets/ # Static files (CSS, JS, images)
|
||||
│ ├── posterg.css
|
||||
│ ├── normalize.css
|
||||
│ └── fonts/
|
||||
├── inc/ # Page templates/partials
|
||||
│ ├── header.php
|
||||
│ └── footer.php
|
||||
├── lib/ # Shared libraries (renamed from shared/)
|
||||
│ ├── Database.php
|
||||
│ ├── RateLimit.php
|
||||
│ ├── config.php
|
||||
│ └── cache/
|
||||
├── admin/ # Admin panel (from apps/admin/)
|
||||
│ ├── index.php
|
||||
│ ├── list.php
|
||||
│ ├── edit.php
|
||||
│ └── ...
|
||||
├── database/ # Database files & schema
|
||||
│ ├── schema.sql
|
||||
│ ├── test.db
|
||||
│ └── ...
|
||||
├── vendor/ # Third-party dependencies (gitignored)
|
||||
│ └── php-live-reload/ # Local dev only
|
||||
├── nginx/ # Server configuration
|
||||
├── docs/ # Documentation
|
||||
├── tests/ # Tests (future)
|
||||
└── .gitignore # Updated
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
✅ **Standard PHP structure** - Follows common conventions
|
||||
✅ **Flatter hierarchy** - Easier to navigate
|
||||
✅ **Clear separation** - lib/ for code, inc/ for templates, assets/ for static files
|
||||
✅ **Simpler paths** - Less `../../` in require statements
|
||||
✅ **Vendor folder** - Standard for third-party code
|
||||
|
||||
## Migration Steps
|
||||
|
||||
### 1. Move public to root
|
||||
```bash
|
||||
mv apps/public/* .
|
||||
```
|
||||
|
||||
### 2. Move admin
|
||||
```bash
|
||||
mv apps/admin admin/
|
||||
rm -rf apps/
|
||||
```
|
||||
|
||||
### 3. Rename shared to lib
|
||||
```bash
|
||||
mv shared lib/
|
||||
```
|
||||
|
||||
### 4. Update all require paths
|
||||
- `require_once __DIR__ . '/shared/Database.php'` → `require_once __DIR__ . '/lib/Database.php'`
|
||||
- `require_once __DIR__ . '/../shared/Database.php'` → `require_once __DIR__ . '/../lib/Database.php'`
|
||||
|
||||
### 5. Setup vendor for local dev
|
||||
```bash
|
||||
mkdir -p vendor/
|
||||
echo "vendor/" >> .gitignore
|
||||
```
|
||||
|
||||
## Path Changes Summary
|
||||
|
||||
| Old Path | New Path |
|
||||
|----------|----------|
|
||||
| `apps/public/index.php` | `index.php` |
|
||||
| `apps/public/inc/header.php` | `inc/header.php` |
|
||||
| `apps/public/assets/posterg.css` | `assets/posterg.css` |
|
||||
| `apps/admin/index.php` | `admin/index.php` |
|
||||
| `shared/Database.php` | `lib/Database.php` |
|
||||
| `shared/config.php` | `lib/config.php` |
|
||||
| `shared/cache/` | `lib/cache/` |
|
||||
|
||||
## Deployment Changes
|
||||
|
||||
### Before
|
||||
```
|
||||
rsync apps/public/ posterg:/var/www/html/
|
||||
rsync apps/admin/ posterg:/var/www/html/formulaire/
|
||||
rsync shared/ posterg:/var/www/html/shared/
|
||||
```
|
||||
|
||||
### After
|
||||
```
|
||||
rsync --exclude 'vendor' --exclude 'tests' . posterg:/var/www/html/
|
||||
```
|
||||
|
||||
Much simpler!
|
||||
|
||||
## PHP Live Reload Setup
|
||||
|
||||
### For Local Development Only
|
||||
|
||||
1. Clone to `vendor/php-live-reload/` (gitignored)
|
||||
2. Include in local dev server
|
||||
3. Auto-refresh browser on file changes
|
||||
4. Never deployed to production
|
||||
|
||||
### Usage
|
||||
```bash
|
||||
# Setup (one time)
|
||||
just setup-dev
|
||||
|
||||
# Start dev server with live reload
|
||||
just serve-public # or serve-admin
|
||||
```
|
||||
299
docs/TEST_CENTRALIZATION.md
Normal file
299
docs/TEST_CENTRALIZATION.md
Normal file
@@ -0,0 +1,299 @@
|
||||
# Test Centralization Summary
|
||||
|
||||
All tests have been centralized into the `tests/` directory following standard testing conventions.
|
||||
|
||||
## 📁 New Test Structure
|
||||
|
||||
```
|
||||
tests/
|
||||
├── run-tests.php # Main test runner
|
||||
├── README.md # Test documentation
|
||||
├── Unit/ # Unit tests
|
||||
│ ├── DatabaseTest.php # Database connection & queries
|
||||
│ └── RateLimitTest.php # Rate limiting functionality
|
||||
├── Integration/ # Integration tests
|
||||
│ └── SearchTest.php # Search functionality
|
||||
└── Security/ # Security tests
|
||||
└── SecurityTest.php # SQL injection & XSS protection
|
||||
```
|
||||
|
||||
## ✅ What Was Done
|
||||
|
||||
### 1. Created Test Directory Structure
|
||||
- `tests/Unit/` - Tests for individual components
|
||||
- `tests/Integration/` - Tests for feature workflows
|
||||
- `tests/Security/` - Tests for security vulnerabilities
|
||||
|
||||
### 2. Moved & Created Tests
|
||||
|
||||
**Before:**
|
||||
- `test_db.php` (root) - Basic database test
|
||||
- `run-tests.php` (root) - Old test runner
|
||||
|
||||
**After:**
|
||||
- `tests/Unit/DatabaseTest.php` - Comprehensive database testing
|
||||
- `tests/Unit/RateLimitTest.php` - Rate limit testing
|
||||
- `tests/Integration/SearchTest.php` - Search functionality testing
|
||||
- `tests/Security/SecurityTest.php` - Security testing
|
||||
- `tests/run-tests.php` - New unified test runner
|
||||
- `tests/README.md` - Complete test documentation
|
||||
|
||||
### 3. Updated Justfile
|
||||
|
||||
**New Commands:**
|
||||
```bash
|
||||
just test # Run all tests
|
||||
just test-unit # Run unit tests only
|
||||
just test-integration # Run integration tests only
|
||||
just test-security # Run security tests only
|
||||
just syntax # Check PHP syntax
|
||||
```
|
||||
|
||||
**Removed:**
|
||||
- Old scattered test commands
|
||||
- Duplicate test logic
|
||||
|
||||
### 4. Removed Old Files
|
||||
- ✅ Deleted `test_db.php` from root
|
||||
- ✅ Deleted `run-tests.php` from root
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Running Tests
|
||||
|
||||
### Run All Tests (Recommended)
|
||||
|
||||
```bash
|
||||
just test
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
╔════════════════════════════════════════════╗
|
||||
║ Post-ERG Test Suite ║
|
||||
╚════════════════════════════════════════════╝
|
||||
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Database (Unit) │
|
||||
└─────────────────────────────────────────┘
|
||||
✓ PASS: Database connection successful
|
||||
✓ PASS: Found 16 published theses
|
||||
...
|
||||
✅ TEST PASSED
|
||||
|
||||
...
|
||||
|
||||
╔════════════════════════════════════════════╗
|
||||
║ Test Summary ║
|
||||
╠════════════════════════════════════════════╣
|
||||
║ Total: 4 ║
|
||||
║ Passed: 4 ✅ ║
|
||||
║ Failed: 0 ║
|
||||
╚════════════════════════════════════════════╝
|
||||
|
||||
✅ All tests passed!
|
||||
```
|
||||
|
||||
### Run Specific Test Suites
|
||||
|
||||
```bash
|
||||
# Unit tests only
|
||||
just test-unit
|
||||
|
||||
# Integration tests only
|
||||
just test-integration
|
||||
|
||||
# Security tests only
|
||||
just test-security
|
||||
|
||||
# Syntax check only
|
||||
just syntax
|
||||
```
|
||||
|
||||
### Run Individual Tests
|
||||
|
||||
```bash
|
||||
# Database test
|
||||
php tests/Unit/DatabaseTest.php
|
||||
|
||||
# Search test
|
||||
php tests/Integration/SearchTest.php
|
||||
|
||||
# Security test
|
||||
php tests/Security/SecurityTest.php
|
||||
|
||||
# Rate limit test
|
||||
php tests/Unit/RateLimitTest.php
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Test Coverage
|
||||
|
||||
### Unit Tests (2)
|
||||
|
||||
**DatabaseTest.php** - 4 assertions
|
||||
- ✅ Database connection
|
||||
- ✅ Count published theses
|
||||
- ✅ Get published theses
|
||||
- ✅ Get single thesis by ID
|
||||
|
||||
**RateLimitTest.php** - 5 assertions
|
||||
- ✅ RateLimit initialization
|
||||
- ✅ check() method returns boolean
|
||||
- ✅ sendHeaders() executes
|
||||
- ✅ getResetTime() returns valid value
|
||||
- ✅ cleanup() executes
|
||||
|
||||
### Integration Tests (1)
|
||||
|
||||
**SearchTest.php** - 3 assertions
|
||||
- ✅ Empty search query handling
|
||||
- ✅ Search for specific terms
|
||||
- ✅ Special characters in search
|
||||
|
||||
### Security Tests (1)
|
||||
|
||||
**SecurityTest.php** - 3 test groups
|
||||
- ✅ SQL injection protection (4 injection attempts blocked)
|
||||
- ✅ Invalid ID rejection (4 invalid IDs rejected)
|
||||
- ✅ XSS protection verification
|
||||
|
||||
**Total: 4 test files, 15 assertions**
|
||||
|
||||
---
|
||||
|
||||
## 📝 Test Results
|
||||
|
||||
All tests passing:
|
||||
|
||||
```
|
||||
✅ Database (Unit) - PASSED
|
||||
✅ Rate Limit (Unit) - PASSED
|
||||
✅ Search (Integration) - PASSED
|
||||
✅ Security - PASSED
|
||||
|
||||
Total: 4
|
||||
Passed: 4 ✅
|
||||
Failed: 0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Benefits
|
||||
|
||||
### Before Centralization
|
||||
- ❌ Tests scattered in root directory
|
||||
- ❌ No clear organization
|
||||
- ❌ Hard to run specific test types
|
||||
- ❌ No test documentation
|
||||
- ❌ Inconsistent test format
|
||||
|
||||
### After Centralization
|
||||
- ✅ All tests in `tests/` directory
|
||||
- ✅ Clear organization (Unit/Integration/Security)
|
||||
- ✅ Easy to run any combination
|
||||
- ✅ Comprehensive test documentation
|
||||
- ✅ Consistent test format and output
|
||||
- ✅ Single test runner
|
||||
- ✅ Beautiful formatted output
|
||||
|
||||
---
|
||||
|
||||
## 📚 Writing New Tests
|
||||
|
||||
### 1. Choose Test Type
|
||||
|
||||
- **Unit Test** → `tests/Unit/` - Tests single functions/classes
|
||||
- **Integration Test** → `tests/Integration/` - Tests feature workflows
|
||||
- **Security Test** → `tests/Security/` - Tests security measures
|
||||
|
||||
### 2. Use Template
|
||||
|
||||
```php
|
||||
<?php
|
||||
/**
|
||||
* Test Name
|
||||
* Description
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../lib/YourClass.php';
|
||||
|
||||
echo "Test Name\n";
|
||||
echo "=========\n\n";
|
||||
|
||||
try {
|
||||
echo "Test 1: Description\n";
|
||||
// ... test code ...
|
||||
echo "✓ PASS: Test passed\n\n";
|
||||
|
||||
echo "✅ All tests passed!\n";
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ FAIL: " . $e->getMessage() . "\n";
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Add to Test Runner
|
||||
|
||||
Edit `tests/run-tests.php` and add your test to the `$testFiles` array:
|
||||
|
||||
```php
|
||||
['name' => 'Your Test Name', 'path' => __DIR__ . '/Unit/YourTest.php'],
|
||||
```
|
||||
|
||||
### 4. Run It
|
||||
|
||||
```bash
|
||||
just test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 CI/CD Integration (Future)
|
||||
|
||||
Tests are ready for CI/CD integration:
|
||||
|
||||
```yaml
|
||||
# .github/workflows/test.yml
|
||||
name: Tests
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.4'
|
||||
- name: Run tests
|
||||
run: php tests/run-tests.php
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📖 Related Documentation
|
||||
|
||||
- [Test README](../tests/README.md) - Complete test documentation
|
||||
- [Database Specification](../database/DATABASE_SPECIFICATION.md)
|
||||
- [Security Documentation](SECURITY.md)
|
||||
|
||||
---
|
||||
|
||||
## ✨ Quick Reference
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `just test` | Run all tests |
|
||||
| `just test-unit` | Unit tests only |
|
||||
| `just test-integration` | Integration tests only |
|
||||
| `just test-security` | Security tests only |
|
||||
| `just syntax` | Check PHP syntax |
|
||||
| `php tests/run-tests.php` | Run test runner directly |
|
||||
|
||||
---
|
||||
|
||||
**All tests centralized and passing!** ✅
|
||||
38
inc/header.php
Normal file
38
inc/header.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<!-- header.php -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="author" content="">
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Posterg</title>
|
||||
<link rel="stylesheet" href="assets/normalize.css">
|
||||
<link rel="stylesheet" href="assets/posterg.css?v=2">
|
||||
<?php if (getenv('PHP_ENV') === 'development' || php_sapi_name() === 'cli-server'): ?>
|
||||
<!-- Live reload for development -->
|
||||
<script src="/vendor/php-live-reload/php-live-reload/live-reload.js"></script>
|
||||
<?php endif; ?>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<a class="title" href="index.php">
|
||||
<h1>posterg</h1>
|
||||
</a>
|
||||
<section>
|
||||
<p class="apropos">
|
||||
Ce site post-ERG a été créé pour répertorier et valoriser les mémoires de l'ERG - École de Recherches Graphique de Bruxelles.
|
||||
L’objectif est à la fois d’offrir une vitrine aux projets des ancien·nes étudiant·es et de mettre en lumière la diversité des disciplines et des parcours qui façonnent l’histoire de l’école à travers les âges, depuis près de 100 ans.
|
||||
</p>
|
||||
<p class="colophon">
|
||||
Design & développement : Olivia Marly, Théo Hennequin & Théophile Gervreau-Mercie
|
||||
Typographies : Ductus (Amélie Dumont), Hyphont-e
|
||||
</p>
|
||||
</section>
|
||||
<nav role="navigation" aria-label="main navigation">
|
||||
<button><a href="search.php"">Recherche</a></button>
|
||||
<button><a href=" search.php"">Partager</a></button>
|
||||
</nav>
|
||||
</header>
|
||||
95
index.php
Normal file
95
index.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
ini_set("display_errors", 0);
|
||||
ini_set("log_errors", 1);
|
||||
ini_set("error_log", "error.log");
|
||||
|
||||
require_once __DIR__ . "/lib/Database.php";
|
||||
|
||||
$page = isset($_GET["page"]) ? intval($_GET["page"]) : 1;
|
||||
$itemsPerPage = 10;
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
$offset = ($page - 1) * $itemsPerPage;
|
||||
$itemsToLoad = $db->getPublishedTheses($itemsPerPage, $offset);
|
||||
$totalItems = $db->countPublishedTheses();
|
||||
$totalPages = ceil($totalItems / $itemsPerPage);
|
||||
} catch (Exception $e) {
|
||||
error_log("Error loading theses: " . $e->getMessage());
|
||||
$itemsToLoad = [];
|
||||
$totalPages = 0;
|
||||
}
|
||||
|
||||
include "inc/header.php";
|
||||
?>
|
||||
<main>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="columns is-multiline">
|
||||
|
||||
<?php foreach ($itemsToLoad as $item): ?>
|
||||
<div class="column is-one-fifth">
|
||||
<a href="memoire.php?id=<?= $item["id"] ?>" class="card-link">
|
||||
<div class="card">
|
||||
<?php
|
||||
// Get cover image from thesis_files if available
|
||||
$coverImage = null;
|
||||
if (!empty($item["id"])) {
|
||||
$files = $db->getThesisFiles($item["id"]);
|
||||
foreach ($files as $file) {
|
||||
$ext = strtolower(
|
||||
pathinfo($file["file_path"], PATHINFO_EXTENSION),
|
||||
);
|
||||
if (
|
||||
in_array($ext, ["jpg", "jpeg", "png", "gif"]) &&
|
||||
$file["file_type"] === "main"
|
||||
) {
|
||||
$coverImage = $file["file_path"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<?php if ($coverImage): ?>
|
||||
<div class="card-image">
|
||||
<figure class="image ">
|
||||
<img src="<?= htmlspecialchars(
|
||||
$coverImage,
|
||||
) ?>" alt="Image preview">
|
||||
</figure>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="card-content">
|
||||
<h4 class="title is-4">
|
||||
<?= htmlspecialchars($item["title"]) ?>
|
||||
</h4>
|
||||
<h2 class="subtitle">
|
||||
<?= htmlspecialchars($item["authors"] ?? "Auteur inconnu") ?>
|
||||
</h2>
|
||||
<h3 class="tag title is-6 is-link is-light">
|
||||
<?= htmlspecialchars($item["year"]) ?>
|
||||
</h3>
|
||||
<p class="block content">
|
||||
<?php
|
||||
$excerpt_length = 150;
|
||||
$synopsis = $item["synopsis"] ?? "";
|
||||
$description_excerpt =
|
||||
strlen($synopsis) > $excerpt_length
|
||||
? substr($synopsis, 0, $excerpt_length) . "..."
|
||||
: $synopsis;
|
||||
?>
|
||||
<?= htmlspecialchars($description_excerpt) ?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<?php include "inc/footer.php";
|
||||
?>
|
||||
406
justfile
406
justfile
@@ -1,205 +1,299 @@
|
||||
# Post-ERG Justfile
|
||||
# Unified recipes for the complete site (public + admin)
|
||||
|
||||
# Default recipe - show available commands
|
||||
default:
|
||||
@just --list
|
||||
|
||||
# ============================================================================
|
||||
# Development Setup
|
||||
# ============================================================================
|
||||
|
||||
[group('dev')]
|
||||
setup:
|
||||
@echo "🛠️ Setting up development environment..."
|
||||
@bash setup-dev.sh
|
||||
|
||||
# ============================================================================
|
||||
# Development Server
|
||||
# ============================================================================
|
||||
|
||||
[group('dev')]
|
||||
serve:
|
||||
@echo "🚀 Starting Post-ERG development server"
|
||||
@echo "========================================"
|
||||
@echo ""
|
||||
@echo "📍 Public site: http://localhost:8000"
|
||||
@echo "📍 Admin panel: http://localhost:8000/admin/"
|
||||
@echo ""
|
||||
@if [ -d "vendor/php-live-reload" ]; then \
|
||||
echo "✨ Live reload enabled - browser auto-refreshes on file save!"; \
|
||||
else \
|
||||
echo "💡 Tip: Run 'just setup' to enable live reload"; \
|
||||
fi
|
||||
@echo ""
|
||||
@echo "Press Ctrl+C to stop"
|
||||
@echo ""
|
||||
@php -S 127.0.0.1:8000
|
||||
|
||||
[group('dev')]
|
||||
stop:
|
||||
@echo "🛑 Stopping development server..."
|
||||
@pkill -f "php -S 127.0.0.1:8000" 2>/dev/null && echo "✓ Server stopped" || echo "No server running"
|
||||
|
||||
[group('dev')]
|
||||
logs:
|
||||
@echo "📋 Development logs"
|
||||
@echo "==================="
|
||||
@echo ""
|
||||
@if [ -f error.log ]; then \
|
||||
echo "Application errors:"; \
|
||||
echo "------------------"; \
|
||||
tail -n 20 error.log; \
|
||||
else \
|
||||
echo "No error log found"; \
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Deploy Group
|
||||
# ============================================================================
|
||||
# Note: Regular deploy recipes exclude test.db and all *.db files by default
|
||||
# Use test-deploy explicitly to deploy the test database
|
||||
|
||||
[group('deploy')]
|
||||
deploy-public:
|
||||
rsync -vur --progress --exclude 'test.db' --exclude '*.db' --exclude 'tests/' --exclude 'cache/' --exclude '*.md' --exclude 'run-tests.php' ./apps/public/ posterg:/var/www/html/
|
||||
rsync -vur --progress --exclude 'test.db' ./shared/ posterg:/var/www/html/shared/
|
||||
@echo "Fixing shared library paths for production..."
|
||||
ssh posterg "cd /var/www/html && find . -maxdepth 1 -name '*.php' -type f -exec sed -i \"s|__DIR__ \. '/\.\./\.\./shared/|__DIR__ . '/shared/|g\" {} \;"
|
||||
deploy:
|
||||
@echo "📤 Deploying Post-ERG complete site"
|
||||
@echo "===================================="
|
||||
@echo ""
|
||||
@echo "Deploying public site..."
|
||||
rsync -vur --progress \
|
||||
--exclude 'vendor' \
|
||||
--exclude 'tests' \
|
||||
--exclude 'test.db' \
|
||||
--exclude '*.db' \
|
||||
--exclude 'cache' \
|
||||
--exclude '*.md' \
|
||||
--exclude '.git*' \
|
||||
--exclude '.DS_Store' \
|
||||
--exclude 'admin' \
|
||||
--exclude 'database' \
|
||||
--exclude 'nginx' \
|
||||
--exclude 'docs' \
|
||||
--exclude 'justfile*' \
|
||||
--exclude 'migrate-structure.sh' \
|
||||
--exclude 'setup-dev.sh' \
|
||||
./ posterg:/var/www/html/
|
||||
@echo ""
|
||||
@echo "Deploying admin panel..."
|
||||
rsync -vur --progress \
|
||||
--exclude 'test.db' \
|
||||
--exclude '*.db' \
|
||||
--exclude 'cache' \
|
||||
--exclude '*.md' \
|
||||
./admin/ posterg:/var/www/html/admin/
|
||||
@echo ""
|
||||
@echo "Deploying shared libraries..."
|
||||
rsync -vur --progress --exclude 'test.db' ./lib/ posterg:/var/www/html/lib/
|
||||
@echo ""
|
||||
@echo "Fixing permissions..."
|
||||
ssh posterg "chgrp -R posterg /var/www/html/inc && chmod 755 /var/www/html/inc && chmod 644 /var/www/html/inc/*"
|
||||
@echo "✓ Deployment complete"
|
||||
|
||||
[group('deploy')]
|
||||
deploy-admin:
|
||||
rsync -vur --progress --exclude 'test.db' --exclude '*.db' --exclude 'cache/' --exclude '*.md' ./apps/admin/ posterg:/var/www/html/admin/
|
||||
rsync -vur --progress --exclude 'test.db' ./shared/ posterg:/var/www/html/shared/
|
||||
@echo "Fixing shared library paths for production (admin)..."
|
||||
ssh posterg "cd /var/www/html/formulaire && find . -maxdepth 1 -name '*.php' -type f -exec sed -i \"s|__DIR__ \. '/\.\./\.\./shared/|__DIR__ . '/../shared/|g\" {} \;"
|
||||
@echo "✓ Admin paths fixed"
|
||||
|
||||
[group('deploy')]
|
||||
deploy: deploy-public deploy-admin
|
||||
ssh posterg "chgrp -R posterg /var/www/html/inc /var/www/html/lib && \
|
||||
chmod 755 /var/www/html/inc && chmod 644 /var/www/html/inc/* && \
|
||||
find /var/www/html/lib -type d -exec chmod 755 {} \; && \
|
||||
find /var/www/html/lib -type f -exec chmod 644 {} \;"
|
||||
@echo ""
|
||||
@echo "✅ Deployment complete (test.db excluded)"
|
||||
@echo "To deploy test database, run: just test-deploy"
|
||||
|
||||
[group('deploy')]
|
||||
deploy-database:
|
||||
@echo "Deploying database directory (excludes test.db by default)..."
|
||||
rsync -vur --progress --exclude 'test.db' --exclude '*.db-journal' ./database/ posterg:/var/www/html/database/
|
||||
@echo "✅ Database directory deployed (schema, fixtures, docs only)"
|
||||
|
||||
[group('deploy')]
|
||||
test-deploy:
|
||||
@echo "⚠️ Deploying test database (will overwrite remote test.db)"
|
||||
@echo "Creating database directory if needed..."
|
||||
ssh posterg "mkdir -p /var/www/html/database"
|
||||
rsync -vur --progress ./database/test.db posterg:/var/www/html/database/test.db
|
||||
@echo "Setting correct permissions..."
|
||||
ssh posterg "chgrp posterg /var/www/html/database /var/www/html/database/test.db && chmod 775 /var/www/html/database && chmod 660 /var/www/html/database/test.db"
|
||||
@echo "✅ Test database deployed and configured"
|
||||
|
||||
[group('deploy')]
|
||||
deploy-nginx:
|
||||
@echo "🚀 Deploying production nginx configuration..."
|
||||
rsync -vur --progress ./nginx/posterg.conf posterg:/tmp/posterg.conf
|
||||
rsync -vur --progress ./nginx/deploy-production.sh posterg:/tmp/deploy-production.sh
|
||||
@echo "✅ Files uploaded to server"
|
||||
@echo "✅ Deployment complete!"
|
||||
@echo ""
|
||||
@echo "Next steps on the server:"
|
||||
@echo " ssh posterg"
|
||||
@echo " sudo bash /tmp/deploy-production.sh"
|
||||
@echo ""
|
||||
@echo "This will:"
|
||||
@echo " • Fix file permissions (posterg group)"
|
||||
@echo " • Install nginx configuration"
|
||||
@echo " • Set up admin password (if needed)"
|
||||
@echo " • Test and reload nginx"
|
||||
|
||||
[group('deploy')]
|
||||
deploy-admin-tools:
|
||||
@echo "📤 Uploading admin user management tools..."
|
||||
rsync -vur --progress ./nginx/manage-admin-users.sh posterg:/tmp/manage-admin-users.sh
|
||||
@echo "✅ Script uploaded"
|
||||
@echo ""
|
||||
@echo "To manage admin users on the server:"
|
||||
@echo " ssh posterg"
|
||||
@echo " sudo bash /tmp/manage-admin-users.sh"
|
||||
@echo "🔍 Verify deployment:"
|
||||
@echo " • Public: https://posterg.erg.be/"
|
||||
@echo " • Admin: https://posterg.erg.be/admin/"
|
||||
|
||||
# ============================================================================
|
||||
# Public Site Development
|
||||
# Testing
|
||||
# ============================================================================
|
||||
|
||||
[group('public-dev')]
|
||||
serve-public:
|
||||
@echo "Starting public site on http://localhost:8002"
|
||||
@echo "Press Ctrl+C to stop"
|
||||
@cd apps/public && php -S 127.0.0.1:8002
|
||||
[group('test')]
|
||||
test:
|
||||
@echo "🧪 Running Post-ERG Test Suite"
|
||||
@echo "==============================="
|
||||
@echo ""
|
||||
@php tests/run-tests.php
|
||||
|
||||
[group('public-dev')]
|
||||
test-public:
|
||||
@echo "Testing public site..."
|
||||
@cd apps/public && php test_db.php
|
||||
[group('test')]
|
||||
test-unit:
|
||||
@echo "🧪 Unit Tests"
|
||||
@echo "============="
|
||||
@php tests/Unit/DatabaseTest.php
|
||||
@echo ""
|
||||
@php tests/Unit/RateLimitTest.php
|
||||
|
||||
[group('public-dev')]
|
||||
test-public-all:
|
||||
@echo "Running all public site tests..."
|
||||
@cd apps/public && php run-tests.php
|
||||
[group('test')]
|
||||
test-integration:
|
||||
@echo "🧪 Integration Tests"
|
||||
@echo "===================="
|
||||
@php tests/Integration/SearchTest.php
|
||||
|
||||
[group('public-dev')]
|
||||
stats-public:
|
||||
@echo "=== Public Database Statistics ==="
|
||||
[group('test')]
|
||||
test-security:
|
||||
@echo "🧪 Security Tests"
|
||||
@echo "================="
|
||||
@php tests/Security/SecurityTest.php
|
||||
|
||||
[group('test')]
|
||||
syntax:
|
||||
@echo "🔍 Checking PHP Syntax"
|
||||
@echo "======================"
|
||||
@find . -maxdepth 1 -name "*.php" -not -path "./vendor/*" -exec php -l {} \; | grep -v "No syntax errors"
|
||||
@find admin/ -name "*.php" -exec php -l {} \; 2>/dev/null | grep -v "No syntax errors" || true
|
||||
@find lib/ -name "*.php" -exec php -l {} \; | grep -v "No syntax errors"
|
||||
@echo "✅ All PHP files have valid syntax"
|
||||
|
||||
# ============================================================================
|
||||
# Database Management
|
||||
# ============================================================================
|
||||
|
||||
# ============================================================================
|
||||
# Database Statistics
|
||||
# ============================================================================
|
||||
|
||||
[group('stats')]
|
||||
stats:
|
||||
@echo "📊 Database Statistics"
|
||||
@echo "======================"
|
||||
@echo ""
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' total theses' FROM theses;"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' published theses' FROM theses WHERE is_published = 1;"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' authors' FROM authors;"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' supervisors' FROM supervisors;"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' keywords' FROM keywords;"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' files uploaded' FROM thesis_files;"
|
||||
|
||||
[group('public-dev')]
|
||||
recent-public:
|
||||
@echo "=== Recent Published Theses ==="
|
||||
[group('stats')]
|
||||
recent:
|
||||
@echo "📅 Recent Theses"
|
||||
@echo "================"
|
||||
@sqlite3 -column -header database/test.db "SELECT id, title, year, authors FROM v_theses_public ORDER BY year DESC, title LIMIT 10;"
|
||||
|
||||
[group('public-dev')]
|
||||
check-public:
|
||||
@echo "Checking public site PHP syntax..."
|
||||
@cd apps/public && find . -name "*.php" -not -path "./vendor/*" -not -path "./tests/*" -exec php -l {} \; | grep -v "No syntax errors"
|
||||
@echo "✓ All files have valid syntax"
|
||||
|
||||
[group('public-dev')]
|
||||
logs-public:
|
||||
@if [ -f apps/public/error.log ]; then tail -n 50 apps/public/error.log; else echo "No error log found"; fi
|
||||
|
||||
# ============================================================================
|
||||
# Admin Panel Development
|
||||
# Database Management
|
||||
# ============================================================================
|
||||
|
||||
[group('admin-dev')]
|
||||
init-test-db:
|
||||
@echo "Creating test database from schema..."
|
||||
[group('database')]
|
||||
init-db:
|
||||
@echo "📊 Creating test database from schema..."
|
||||
@sqlite3 database/test.db < database/schema.sql
|
||||
@echo "✓ Test database created"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' tables created' FROM sqlite_master WHERE type='table';"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' orientations loaded' FROM orientations;"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' AP programs loaded' FROM ap_programs;"
|
||||
|
||||
[group('admin-dev')]
|
||||
serve-admin: init-test-db
|
||||
@echo "Starting admin panel on http://localhost:3000"
|
||||
@echo "Press Ctrl+C to stop"
|
||||
@cd apps/admin && php -S 127.0.0.1:3000
|
||||
|
||||
[group('admin-dev')]
|
||||
serve-admin-only:
|
||||
@echo "Starting admin panel on http://localhost:3000"
|
||||
@echo "Press Ctrl+C to stop"
|
||||
@cd apps/admin && php -S 127.0.0.1:3000
|
||||
|
||||
[group('admin-dev')]
|
||||
cleanup-admin:
|
||||
@echo "Cleaning up admin test files..."
|
||||
[group('database')]
|
||||
reset-db:
|
||||
@echo "⚠️ Resetting database (will delete all data)..."
|
||||
@rm -f database/test.db
|
||||
@rm -f apps/admin/error.log
|
||||
@rm -rf apps/admin/data/theses/*
|
||||
@rm -rf apps/admin/data/covers/*
|
||||
@echo "✓ Cleanup complete"
|
||||
|
||||
[group('admin-dev')]
|
||||
reset-admin: cleanup-admin init-test-db
|
||||
@echo "✓ Admin test environment reset"
|
||||
|
||||
[group('admin-dev')]
|
||||
stats-admin:
|
||||
@echo "=== Admin Database Statistics ==="
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' theses' FROM theses;"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' authors' FROM authors;"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' supervisors' FROM supervisors;"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' keywords' FROM keywords;"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' files uploaded' FROM thesis_files;"
|
||||
|
||||
[group('admin-dev')]
|
||||
recent-admin:
|
||||
@echo "=== Recent Submissions ==="
|
||||
@sqlite3 -column -header database/test.db "SELECT identifier, title, year, submitted_at FROM theses ORDER BY submitted_at DESC LIMIT 5;"
|
||||
|
||||
[group('admin-dev')]
|
||||
setup-dirs:
|
||||
@mkdir -p apps/admin/data/theses
|
||||
@mkdir -p apps/admin/data/covers
|
||||
@mkdir -p apps/admin/data/yaml
|
||||
@touch apps/admin/data/theses/.gitkeep
|
||||
@touch apps/admin/data/covers/.gitkeep
|
||||
@echo "✓ Data directories created"
|
||||
|
||||
[group('admin-dev')]
|
||||
dev-admin: setup-dirs init-test-db serve-admin
|
||||
|
||||
# ============================================================================
|
||||
# Database Operations
|
||||
# ============================================================================
|
||||
@just init-db
|
||||
@echo "✓ Database reset complete"
|
||||
|
||||
[group('database')]
|
||||
query-db:
|
||||
query:
|
||||
@sqlite3 database/test.db
|
||||
|
||||
[group('database')]
|
||||
show-thesis id:
|
||||
show id:
|
||||
@echo "Thesis #{{id}}"
|
||||
@echo "=============="
|
||||
@sqlite3 -column -header database/test.db "SELECT * FROM v_theses_full WHERE id = {{id}};"
|
||||
|
||||
[group('database')]
|
||||
dump-db:
|
||||
backup:
|
||||
@echo "💾 Backing up database..."
|
||||
@sqlite3 database/test.db .dump > database/backup_$(date +%Y%m%d_%H%M%S).sql
|
||||
@echo "✓ Database dumped to database/backup_$(date +%Y%m%d_%H%M%S).sql"
|
||||
|
||||
[group('database')]
|
||||
create-fixtures:
|
||||
@echo "Creating test database with fixtures..."
|
||||
fixtures:
|
||||
@echo "🎭 Creating test database with fixtures..."
|
||||
@php database/fixtures/CreateTestDatabase.php
|
||||
|
||||
[group('database')]
|
||||
deploy-test-db:
|
||||
@echo "⚠️ Deploying test database to server (will overwrite remote test.db)"
|
||||
@echo "Creating database directory if needed..."
|
||||
ssh posterg "mkdir -p /var/www/html/database"
|
||||
rsync -vur --progress ./database/test.db posterg:/var/www/html/database/test.db
|
||||
@echo "Setting correct permissions..."
|
||||
ssh posterg "chgrp posterg /var/www/html/database /var/www/html/database/test.db && \
|
||||
chmod 775 /var/www/html/database && \
|
||||
chmod 660 /var/www/html/database/test.db"
|
||||
@echo "✅ Test database deployed"
|
||||
|
||||
# ============================================================================
|
||||
# Server Tools
|
||||
# ============================================================================
|
||||
|
||||
[group('server')]
|
||||
deploy-nginx:
|
||||
@echo "🔧 Deploying nginx configuration..."
|
||||
rsync -vur --progress ./nginx/posterg.conf posterg:/tmp/posterg.conf
|
||||
rsync -vur --progress ./nginx/deploy-production.sh posterg:/tmp/deploy-production.sh
|
||||
@echo "✅ Files uploaded to /tmp/ on server"
|
||||
@echo ""
|
||||
@echo "Next steps on the server:"
|
||||
@echo " ssh posterg"
|
||||
@echo " sudo bash /tmp/deploy-production.sh"
|
||||
|
||||
[group('server')]
|
||||
deploy-admin-tools:
|
||||
@echo "🔑 Uploading admin user management tools..."
|
||||
rsync -vur --progress ./nginx/manage-admin-users.sh posterg:/tmp/manage-admin-users.sh
|
||||
@echo "✅ Script uploaded"
|
||||
@echo ""
|
||||
@echo "To manage admin users:"
|
||||
@echo " ssh posterg"
|
||||
@echo " sudo bash /tmp/manage-admin-users.sh"
|
||||
|
||||
[group('server')]
|
||||
server-logs:
|
||||
@echo "📋 Server logs (last 50 lines)"
|
||||
@echo "=============================="
|
||||
@echo ""
|
||||
@echo "Nginx error log:"
|
||||
@echo "----------------"
|
||||
ssh posterg "sudo tail -50 /var/log/nginx/posterg_error.log" || echo "Cannot read logs (permission denied)"
|
||||
@echo ""
|
||||
@echo "Nginx access log:"
|
||||
@echo "-----------------"
|
||||
ssh posterg "sudo tail -20 /var/log/nginx/posterg_access.log" || echo "Cannot read logs (permission denied)"
|
||||
|
||||
[group('server')]
|
||||
server-status:
|
||||
@echo "🔍 Server Status"
|
||||
@echo "================"
|
||||
@ssh posterg "systemctl is-active nginx && echo '✓ Nginx running' || echo '✗ Nginx stopped'"
|
||||
@ssh posterg "systemctl is-active php8.4-fpm && echo '✓ PHP-FPM running' || echo '✗ PHP-FPM stopped'"
|
||||
@echo ""
|
||||
@echo "Site check:"
|
||||
@curl -s -o /dev/null -w " • Public: %{http_code}\n" https://posterg.erg.be/ || echo " • Public: offline"
|
||||
@curl -s -o /dev/null -w " • Admin: %{http_code}\n" https://posterg.erg.be/admin/ || echo " • Admin: offline"
|
||||
|
||||
# ============================================================================
|
||||
# Utility Commands
|
||||
# ============================================================================
|
||||
|
||||
[group('utils')]
|
||||
clean:
|
||||
@echo "🧹 Cleaning up development files..."
|
||||
@rm -f error.log
|
||||
@rm -f admin/error.log
|
||||
@rm -rf lib/cache/rate_limit/*
|
||||
@rm -f /tmp/posterg-*.log
|
||||
@rm -f /tmp/posterg-*.pid
|
||||
@echo "✓ Cleanup complete"
|
||||
|
||||
[group('utils')]
|
||||
setup-dirs:
|
||||
@echo "📁 Creating data directories..."
|
||||
@mkdir -p admin/data/theses
|
||||
@mkdir -p admin/data/covers
|
||||
@mkdir -p admin/data/yaml
|
||||
@mkdir -p lib/cache/rate_limit
|
||||
@touch admin/data/theses/.gitkeep
|
||||
@touch admin/data/covers/.gitkeep
|
||||
@echo "✓ Directories created"
|
||||
|
||||
205
justfile.old
Normal file
205
justfile.old
Normal file
@@ -0,0 +1,205 @@
|
||||
# Default recipe - show available commands
|
||||
default:
|
||||
@just --list
|
||||
|
||||
# ============================================================================
|
||||
# Deploy Group
|
||||
# ============================================================================
|
||||
# Note: Regular deploy recipes exclude test.db and all *.db files by default
|
||||
# Use test-deploy explicitly to deploy the test database
|
||||
|
||||
[group('deploy')]
|
||||
deploy-public:
|
||||
rsync -vur --progress --exclude 'test.db' --exclude '*.db' --exclude 'tests/' --exclude 'cache/' --exclude '*.md' --exclude 'run-tests.php' ./apps/public/ posterg:/var/www/html/
|
||||
rsync -vur --progress --exclude 'test.db' ./shared/ posterg:/var/www/html/shared/
|
||||
@echo "Fixing shared library paths for production..."
|
||||
ssh posterg "cd /var/www/html && find . -maxdepth 1 -name '*.php' -type f -exec sed -i \"s|__DIR__ \. '/\.\./\.\./shared/|__DIR__ . '/shared/|g\" {} \;"
|
||||
@echo "Fixing permissions..."
|
||||
ssh posterg "chgrp -R posterg /var/www/html/inc && chmod 755 /var/www/html/inc && chmod 644 /var/www/html/inc/*"
|
||||
@echo "✓ Deployment complete"
|
||||
|
||||
[group('deploy')]
|
||||
deploy-admin:
|
||||
rsync -vur --progress --exclude 'test.db' --exclude '*.db' --exclude 'cache/' --exclude '*.md' ./apps/admin/ posterg:/var/www/html/admin/
|
||||
rsync -vur --progress --exclude 'test.db' ./shared/ posterg:/var/www/html/shared/
|
||||
@echo "Fixing shared library paths for production (admin)..."
|
||||
ssh posterg "cd /var/www/html/formulaire && find . -maxdepth 1 -name '*.php' -type f -exec sed -i \"s|__DIR__ \. '/\.\./\.\./shared/|__DIR__ . '/../shared/|g\" {} \;"
|
||||
@echo "✓ Admin paths fixed"
|
||||
|
||||
[group('deploy')]
|
||||
deploy: deploy-public deploy-admin
|
||||
@echo ""
|
||||
@echo "✅ Deployment complete (test.db excluded)"
|
||||
@echo "To deploy test database, run: just test-deploy"
|
||||
|
||||
[group('deploy')]
|
||||
deploy-database:
|
||||
@echo "Deploying database directory (excludes test.db by default)..."
|
||||
rsync -vur --progress --exclude 'test.db' --exclude '*.db-journal' ./database/ posterg:/var/www/html/database/
|
||||
@echo "✅ Database directory deployed (schema, fixtures, docs only)"
|
||||
|
||||
[group('deploy')]
|
||||
test-deploy:
|
||||
@echo "⚠️ Deploying test database (will overwrite remote test.db)"
|
||||
@echo "Creating database directory if needed..."
|
||||
ssh posterg "mkdir -p /var/www/html/database"
|
||||
rsync -vur --progress ./database/test.db posterg:/var/www/html/database/test.db
|
||||
@echo "Setting correct permissions..."
|
||||
ssh posterg "chgrp posterg /var/www/html/database /var/www/html/database/test.db && chmod 775 /var/www/html/database && chmod 660 /var/www/html/database/test.db"
|
||||
@echo "✅ Test database deployed and configured"
|
||||
|
||||
[group('deploy')]
|
||||
deploy-nginx:
|
||||
@echo "🚀 Deploying production nginx configuration..."
|
||||
rsync -vur --progress ./nginx/posterg.conf posterg:/tmp/posterg.conf
|
||||
rsync -vur --progress ./nginx/deploy-production.sh posterg:/tmp/deploy-production.sh
|
||||
@echo "✅ Files uploaded to server"
|
||||
@echo ""
|
||||
@echo "Next steps on the server:"
|
||||
@echo " ssh posterg"
|
||||
@echo " sudo bash /tmp/deploy-production.sh"
|
||||
@echo ""
|
||||
@echo "This will:"
|
||||
@echo " • Fix file permissions (posterg group)"
|
||||
@echo " • Install nginx configuration"
|
||||
@echo " • Set up admin password (if needed)"
|
||||
@echo " • Test and reload nginx"
|
||||
|
||||
[group('deploy')]
|
||||
deploy-admin-tools:
|
||||
@echo "📤 Uploading admin user management tools..."
|
||||
rsync -vur --progress ./nginx/manage-admin-users.sh posterg:/tmp/manage-admin-users.sh
|
||||
@echo "✅ Script uploaded"
|
||||
@echo ""
|
||||
@echo "To manage admin users on the server:"
|
||||
@echo " ssh posterg"
|
||||
@echo " sudo bash /tmp/manage-admin-users.sh"
|
||||
|
||||
# ============================================================================
|
||||
# Public Site Development
|
||||
# ============================================================================
|
||||
|
||||
[group('public-dev')]
|
||||
serve-public:
|
||||
@echo "Starting public site on http://localhost:8002"
|
||||
@echo "Press Ctrl+C to stop"
|
||||
@cd apps/public && php -S 127.0.0.1:8002
|
||||
|
||||
[group('public-dev')]
|
||||
test-public:
|
||||
@echo "Testing public site..."
|
||||
@cd apps/public && php test_db.php
|
||||
|
||||
[group('public-dev')]
|
||||
test-public-all:
|
||||
@echo "Running all public site tests..."
|
||||
@cd apps/public && php run-tests.php
|
||||
|
||||
[group('public-dev')]
|
||||
stats-public:
|
||||
@echo "=== Public Database Statistics ==="
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' total theses' FROM theses;"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' published theses' FROM theses WHERE is_published = 1;"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' authors' FROM authors;"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' keywords' FROM keywords;"
|
||||
|
||||
[group('public-dev')]
|
||||
recent-public:
|
||||
@echo "=== Recent Published Theses ==="
|
||||
@sqlite3 -column -header database/test.db "SELECT id, title, year, authors FROM v_theses_public ORDER BY year DESC, title LIMIT 10;"
|
||||
|
||||
[group('public-dev')]
|
||||
check-public:
|
||||
@echo "Checking public site PHP syntax..."
|
||||
@cd apps/public && find . -name "*.php" -not -path "./vendor/*" -not -path "./tests/*" -exec php -l {} \; | grep -v "No syntax errors"
|
||||
@echo "✓ All files have valid syntax"
|
||||
|
||||
[group('public-dev')]
|
||||
logs-public:
|
||||
@if [ -f apps/public/error.log ]; then tail -n 50 apps/public/error.log; else echo "No error log found"; fi
|
||||
|
||||
# ============================================================================
|
||||
# Admin Panel Development
|
||||
# ============================================================================
|
||||
|
||||
[group('admin-dev')]
|
||||
init-test-db:
|
||||
@echo "Creating test database from schema..."
|
||||
@sqlite3 database/test.db < database/schema.sql
|
||||
@echo "✓ Test database created"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' tables created' FROM sqlite_master WHERE type='table';"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' orientations loaded' FROM orientations;"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' AP programs loaded' FROM ap_programs;"
|
||||
|
||||
[group('admin-dev')]
|
||||
serve-admin: init-test-db
|
||||
@echo "Starting admin panel on http://localhost:3000"
|
||||
@echo "Press Ctrl+C to stop"
|
||||
@cd apps/admin && php -S 127.0.0.1:3000
|
||||
|
||||
[group('admin-dev')]
|
||||
serve-admin-only:
|
||||
@echo "Starting admin panel on http://localhost:3000"
|
||||
@echo "Press Ctrl+C to stop"
|
||||
@cd apps/admin && php -S 127.0.0.1:3000
|
||||
|
||||
[group('admin-dev')]
|
||||
cleanup-admin:
|
||||
@echo "Cleaning up admin test files..."
|
||||
@rm -f database/test.db
|
||||
@rm -f apps/admin/error.log
|
||||
@rm -rf apps/admin/data/theses/*
|
||||
@rm -rf apps/admin/data/covers/*
|
||||
@echo "✓ Cleanup complete"
|
||||
|
||||
[group('admin-dev')]
|
||||
reset-admin: cleanup-admin init-test-db
|
||||
@echo "✓ Admin test environment reset"
|
||||
|
||||
[group('admin-dev')]
|
||||
stats-admin:
|
||||
@echo "=== Admin Database Statistics ==="
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' theses' FROM theses;"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' authors' FROM authors;"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' supervisors' FROM supervisors;"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' keywords' FROM keywords;"
|
||||
@sqlite3 database/test.db "SELECT COUNT(*) || ' files uploaded' FROM thesis_files;"
|
||||
|
||||
[group('admin-dev')]
|
||||
recent-admin:
|
||||
@echo "=== Recent Submissions ==="
|
||||
@sqlite3 -column -header database/test.db "SELECT identifier, title, year, submitted_at FROM theses ORDER BY submitted_at DESC LIMIT 5;"
|
||||
|
||||
[group('admin-dev')]
|
||||
setup-dirs:
|
||||
@mkdir -p apps/admin/data/theses
|
||||
@mkdir -p apps/admin/data/covers
|
||||
@mkdir -p apps/admin/data/yaml
|
||||
@touch apps/admin/data/theses/.gitkeep
|
||||
@touch apps/admin/data/covers/.gitkeep
|
||||
@echo "✓ Data directories created"
|
||||
|
||||
[group('admin-dev')]
|
||||
dev-admin: setup-dirs init-test-db serve-admin
|
||||
|
||||
# ============================================================================
|
||||
# Database Operations
|
||||
# ============================================================================
|
||||
|
||||
[group('database')]
|
||||
query-db:
|
||||
@sqlite3 database/test.db
|
||||
|
||||
[group('database')]
|
||||
show-thesis id:
|
||||
@sqlite3 -column -header database/test.db "SELECT * FROM v_theses_full WHERE id = {{id}};"
|
||||
|
||||
[group('database')]
|
||||
dump-db:
|
||||
@sqlite3 database/test.db .dump > database/backup_$(date +%Y%m%d_%H%M%S).sql
|
||||
@echo "✓ Database dumped to database/backup_$(date +%Y%m%d_%H%M%S).sql"
|
||||
|
||||
[group('database')]
|
||||
create-fixtures:
|
||||
@echo "Creating test database with fixtures..."
|
||||
@php database/fixtures/CreateTestDatabase.php
|
||||
1
lib/cache/rate_limit/ad921d60486366258809553a3db49a4a.json
vendored
Normal file
1
lib/cache/rate_limit/ad921d60486366258809553a3db49a4a.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[1770317579]
|
||||
1
lib/cache/rate_limit/f528764d624db129b32c21fbca0cb8d6.json
vendored
Normal file
1
lib/cache/rate_limit/f528764d624db129b32c21fbca0cb8d6.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[1770318922,1770318923,1770318924,1770318926,1770318930]
|
||||
@@ -5,7 +5,7 @@ ini_set('log_errors', 1);
|
||||
ini_set('error_log', 'error.log');
|
||||
|
||||
// Load required libraries and classes
|
||||
require_once __DIR__ . '/../../shared/Database.php';
|
||||
require_once __DIR__ . '/lib/Database.php';
|
||||
|
||||
// Check if an id parameter is provided in the URL
|
||||
if (isset($_GET['id'])) {
|
||||
169
scripts/migrate-structure.sh
Executable file
169
scripts/migrate-structure.sh
Executable file
@@ -0,0 +1,169 @@
|
||||
#!/bin/bash
|
||||
# Migrate repository structure to idiomatic PHP layout
|
||||
|
||||
set -e
|
||||
|
||||
echo "🔄 Migrating Post-ERG repository structure"
|
||||
echo "==========================================="
|
||||
echo ""
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Check we're in the right directory
|
||||
if [ ! -d "apps" ] || [ ! -d "shared" ]; then
|
||||
echo -e "${RED}Error: Must run from repository root${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}⚠️ This will restructure the repository!${NC}"
|
||||
echo ""
|
||||
echo "Changes:"
|
||||
echo " • Move apps/public/* to root"
|
||||
echo " • Move apps/admin/ to admin/"
|
||||
echo " • Rename shared/ to lib/"
|
||||
echo " • Update all require paths"
|
||||
echo " • Remove apps/ directory"
|
||||
echo ""
|
||||
read -p "Continue? [y/N] " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Cancelled"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📦 Step 1: Moving public files to root..."
|
||||
echo "--------------------------------------"
|
||||
|
||||
# Move public files to root (exclude those that already exist)
|
||||
for file in apps/public/*; do
|
||||
filename=$(basename "$file")
|
||||
if [ "$filename" != "tests" ] && [ "$filename" != "cache" ]; then
|
||||
if [ -e "$filename" ]; then
|
||||
echo " ⚠️ Skipping $filename (already exists)"
|
||||
else
|
||||
mv "$file" .
|
||||
echo " ✓ Moved $filename"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "📦 Step 2: Moving admin panel..."
|
||||
echo "--------------------------------------"
|
||||
|
||||
if [ -d "admin" ]; then
|
||||
echo " ⚠️ admin/ already exists, removing it first"
|
||||
rm -rf admin/
|
||||
fi
|
||||
|
||||
mv apps/admin admin/
|
||||
echo " ✓ Moved apps/admin/ to admin/"
|
||||
|
||||
echo ""
|
||||
echo "📦 Step 3: Renaming shared/ to lib/..."
|
||||
echo "--------------------------------------"
|
||||
|
||||
if [ -d "lib" ]; then
|
||||
echo " ⚠️ lib/ already exists, removing it first"
|
||||
rm -rf lib/
|
||||
fi
|
||||
|
||||
mv shared lib/
|
||||
echo " ✓ Renamed shared/ to lib/"
|
||||
|
||||
echo ""
|
||||
echo "📦 Step 4: Removing apps/ directory..."
|
||||
echo "--------------------------------------"
|
||||
|
||||
if [ -d "apps" ]; then
|
||||
rm -rf apps/
|
||||
echo " ✓ Removed apps/"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📦 Step 5: Updating require paths in root PHP files..."
|
||||
echo "--------------------------------------"
|
||||
|
||||
# Update root PHP files
|
||||
find . -maxdepth 1 -name "*.php" -type f | while read file; do
|
||||
if grep -q "shared/" "$file" 2>/dev/null; then
|
||||
sed -i "s|__DIR__ \. '/shared/|__DIR__ . '/lib/|g" "$file"
|
||||
sed -i "s|'shared/|'lib/|g" "$file"
|
||||
sed -i "s|\"shared/|\"lib/|g" "$file"
|
||||
echo " ✓ Updated $file"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "📦 Step 6: Updating require paths in admin/..."
|
||||
echo "--------------------------------------"
|
||||
|
||||
# Update admin PHP files
|
||||
find admin/ -name "*.php" -type f | while read file; do
|
||||
if grep -q "shared/" "$file" 2>/dev/null; then
|
||||
sed -i "s|__DIR__ \. '/\.\./\.\./shared/|__DIR__ . '/../lib/|g" "$file"
|
||||
sed -i "s|'../../shared/|'../lib/|g" "$file"
|
||||
sed -i "s|\"../../shared/|\"../lib/|g" "$file"
|
||||
echo " ✓ Updated $file"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "📦 Step 7: Updating lib/config.php paths..."
|
||||
echo "--------------------------------------"
|
||||
|
||||
if [ -f "lib/config.php" ]; then
|
||||
# Update database paths in config
|
||||
sed -i "s|__DIR__ \. '/\.\.'|__DIR__ . '/..'|g" lib/config.php
|
||||
echo " ✓ Updated lib/config.php"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📦 Step 8: Creating vendor directory..."
|
||||
echo "--------------------------------------"
|
||||
|
||||
mkdir -p vendor/
|
||||
echo " ✓ Created vendor/"
|
||||
|
||||
echo ""
|
||||
echo "📦 Step 9: Updating .gitignore..."
|
||||
echo "--------------------------------------"
|
||||
|
||||
if ! grep -q "^vendor/" .gitignore 2>/dev/null; then
|
||||
echo "vendor/" >> .gitignore
|
||||
echo " ✓ Added vendor/ to .gitignore"
|
||||
else
|
||||
echo " ⚠️ vendor/ already in .gitignore"
|
||||
fi
|
||||
|
||||
if ! grep -q "^\.DS_Store" .gitignore 2>/dev/null; then
|
||||
echo ".DS_Store" >> .gitignore
|
||||
echo " ✓ Added .DS_Store to .gitignore"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════"
|
||||
echo -e "${GREEN}✅ Migration complete!${NC}"
|
||||
echo "═══════════════════════════════════════"
|
||||
echo ""
|
||||
echo "📋 Next steps:"
|
||||
echo " 1. Review changes: git status"
|
||||
echo " 2. Test locally: just serve-public"
|
||||
echo " 3. Run tests: just test-public-all"
|
||||
echo " 4. Commit: git add -A && git commit -m 'Restructure to idiomatic PHP layout'"
|
||||
echo " 5. Deploy: just deploy"
|
||||
echo ""
|
||||
echo "📁 New structure:"
|
||||
echo " index.php - Public root"
|
||||
echo " admin/ - Admin panel"
|
||||
echo " lib/ - Shared libraries"
|
||||
echo " assets/ - Static files"
|
||||
echo " inc/ - Templates"
|
||||
echo " database/ - Database files"
|
||||
echo " vendor/ - Third-party (gitignored)"
|
||||
echo ""
|
||||
65
scripts/setup-dev.sh
Executable file
65
scripts/setup-dev.sh
Executable file
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
# Setup local development environment
|
||||
|
||||
set -e
|
||||
|
||||
echo "🛠️ Setting up Post-ERG development environment"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Create vendor directory
|
||||
if [ ! -d "vendor" ]; then
|
||||
mkdir -p vendor
|
||||
echo "✓ Created vendor/ directory"
|
||||
fi
|
||||
|
||||
# Clone php-live-reload
|
||||
if [ -d "vendor/php-live-reload" ]; then
|
||||
echo -e "${YELLOW}⚠️ php-live-reload already exists${NC}"
|
||||
read -p "Update it? [y/N] " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
rm -rf vendor/php-live-reload
|
||||
git clone --depth 1 https://github.com/ryantate13/php-live-reload.git vendor/php-live-reload
|
||||
echo "✓ Updated php-live-reload"
|
||||
fi
|
||||
else
|
||||
echo "📥 Cloning php-live-reload..."
|
||||
git clone --depth 1 https://github.com/ryantate13/php-live-reload.git vendor/php-live-reload
|
||||
echo "✓ Cloned php-live-reload"
|
||||
fi
|
||||
|
||||
# Create test database if needed
|
||||
if [ ! -f "database/test.db" ]; then
|
||||
echo ""
|
||||
echo "📊 Creating test database..."
|
||||
sqlite3 database/test.db < database/schema.sql
|
||||
echo "✓ Created test database"
|
||||
fi
|
||||
|
||||
# Create data directories
|
||||
echo ""
|
||||
echo "📁 Creating data directories..."
|
||||
mkdir -p admin/data/theses
|
||||
mkdir -p admin/data/covers
|
||||
mkdir -p admin/data/yaml
|
||||
touch admin/data/theses/.gitkeep
|
||||
touch admin/data/covers/.gitkeep
|
||||
echo "✓ Created data directories"
|
||||
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════"
|
||||
echo -e "${GREEN}✅ Development environment ready!${NC}"
|
||||
echo "═══════════════════════════════════════"
|
||||
echo ""
|
||||
echo "🚀 Start developing:"
|
||||
echo " just serve-public # Start public site with live reload"
|
||||
echo " just serve-admin # Start admin panel with live reload"
|
||||
echo ""
|
||||
echo "📝 The browser will auto-refresh when you save PHP files!"
|
||||
echo ""
|
||||
@@ -3,8 +3,8 @@ ini_set('display_errors', 0);
|
||||
ini_set('log_errors', 1);
|
||||
ini_set('error_log', 'error.log');
|
||||
|
||||
require_once __DIR__ . '/../../shared/Database.php';
|
||||
require_once __DIR__ . '/../../shared/RateLimit.php';
|
||||
require_once __DIR__ . '/lib/Database.php';
|
||||
require_once __DIR__ . '/lib/RateLimit.php';
|
||||
|
||||
// Rate limiting: 30 requests per minute
|
||||
$rateLimit = new RateLimit(30, 60);
|
||||
@@ -1 +0,0 @@
|
||||
[1769619847,1769619847,1769619847,1769619847,1769619847]
|
||||
@@ -1 +0,0 @@
|
||||
[1770299235]
|
||||
49
tests/Integration/SearchTest.php
Normal file
49
tests/Integration/SearchTest.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* Search Functionality Test
|
||||
* Tests search queries and results
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../lib/Database.php';
|
||||
|
||||
echo "Search Functionality Test\n";
|
||||
echo "=========================\n\n";
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Test 1: Search with empty query
|
||||
echo "Test 1: Empty Search Query\n";
|
||||
$results = $db->searchTheses('');
|
||||
if (is_array($results)) {
|
||||
echo "✓ PASS: Empty query handled (returned " . count($results) . " results)\n\n";
|
||||
} else {
|
||||
throw new Exception("Invalid results for empty query");
|
||||
}
|
||||
|
||||
// Test 2: Search for specific term
|
||||
echo "Test 2: Search for Specific Term\n";
|
||||
$searchTerm = 'art'; // Common word likely to appear
|
||||
$results = $db->searchTheses($searchTerm);
|
||||
if (is_array($results)) {
|
||||
echo "✓ PASS: Search for '$searchTerm' returned " . count($results) . " results\n\n";
|
||||
} else {
|
||||
throw new Exception("Invalid search results");
|
||||
}
|
||||
|
||||
// Test 3: Search with special characters
|
||||
echo "Test 3: Search with Special Characters\n";
|
||||
$results = $db->searchTheses("test's \"quotes\" & symbols");
|
||||
if (is_array($results)) {
|
||||
echo "✓ PASS: Special characters handled safely\n\n";
|
||||
} else {
|
||||
throw new Exception("Failed to handle special characters");
|
||||
}
|
||||
|
||||
echo "✅ All search tests passed!\n";
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ FAIL: " . $e->getMessage() . "\n";
|
||||
return false;
|
||||
}
|
||||
234
tests/README.md
Normal file
234
tests/README.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# Post-ERG Test Suite
|
||||
|
||||
Centralized test suite for the Post-ERG thesis management system.
|
||||
|
||||
## 📁 Structure
|
||||
|
||||
```
|
||||
tests/
|
||||
├── run-tests.php # Test runner (runs all tests)
|
||||
├── Unit/ # Unit tests
|
||||
│ ├── DatabaseTest.php # Database connection & queries
|
||||
│ └── RateLimitTest.php # Rate limiting functionality
|
||||
├── Integration/ # Integration tests
|
||||
│ └── SearchTest.php # Search functionality
|
||||
├── Security/ # Security tests
|
||||
│ └── SecurityTest.php # SQL injection & XSS protection
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## 🚀 Running Tests
|
||||
|
||||
### Run All Tests
|
||||
|
||||
```bash
|
||||
# Using justfile (recommended)
|
||||
just test
|
||||
|
||||
# Or directly
|
||||
php tests/run-tests.php
|
||||
```
|
||||
|
||||
### Run Individual Tests
|
||||
|
||||
```bash
|
||||
# Database test
|
||||
php tests/Unit/DatabaseTest.php
|
||||
|
||||
# Search test
|
||||
php tests/Integration/SearchTest.php
|
||||
|
||||
# Security test
|
||||
php tests/Security/SecurityTest.php
|
||||
|
||||
# Rate limit test
|
||||
php tests/Unit/RateLimitTest.php
|
||||
```
|
||||
|
||||
## ✅ Test Coverage
|
||||
|
||||
### Unit Tests
|
||||
|
||||
**DatabaseTest.php** - Tests basic database operations:
|
||||
- ✅ Database connection
|
||||
- ✅ Count published theses
|
||||
- ✅ Get published theses
|
||||
- ✅ Get single thesis by ID
|
||||
|
||||
**RateLimitTest.php** - Tests rate limiting:
|
||||
- ✅ RateLimit initialization
|
||||
- ✅ check() method
|
||||
- ✅ sendHeaders() method
|
||||
- ✅ getResetTime() method
|
||||
- ✅ cleanup() method
|
||||
|
||||
### Integration Tests
|
||||
|
||||
**SearchTest.php** - Tests search functionality:
|
||||
- ✅ Empty search query handling
|
||||
- ✅ Search for specific terms
|
||||
- ✅ Special characters in search
|
||||
|
||||
### Security Tests
|
||||
|
||||
**SecurityTest.php** - Tests security measures:
|
||||
- ✅ SQL injection protection
|
||||
- ✅ Invalid ID rejection
|
||||
- ✅ XSS protection (output escaping)
|
||||
|
||||
## 📝 Writing New Tests
|
||||
|
||||
### Test File Template
|
||||
|
||||
```php
|
||||
<?php
|
||||
/**
|
||||
* Test Name
|
||||
* Description of what this tests
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../lib/YourClass.php';
|
||||
|
||||
echo "Test Name\n";
|
||||
echo "=========\n\n";
|
||||
|
||||
try {
|
||||
// Test 1
|
||||
echo "Test 1: Description\n";
|
||||
// ... test code ...
|
||||
echo "✓ PASS: Test passed\n\n";
|
||||
|
||||
// Test 2
|
||||
echo "Test 2: Description\n";
|
||||
// ... test code ...
|
||||
echo "✓ PASS: Test passed\n\n";
|
||||
|
||||
echo "✅ All tests passed!\n";
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ FAIL: " . $e->getMessage() . "\n";
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
### Guidelines
|
||||
|
||||
1. **Return Value**: Return `true` for pass, `false` for fail
|
||||
2. **Output Format**: Use `✓ PASS:` for successes, `❌ FAIL:` for failures
|
||||
3. **Exceptions**: Catch and report exceptions clearly
|
||||
4. **Dependencies**: Require only what's needed via relative paths
|
||||
5. **Location**:
|
||||
- `Unit/` - Tests for individual classes/functions
|
||||
- `Integration/` - Tests for feature workflows
|
||||
- `Security/` - Tests for security vulnerabilities
|
||||
|
||||
## 🔧 Test Database
|
||||
|
||||
Tests use the test database at `database/test.db`.
|
||||
|
||||
### Setup Test Database
|
||||
|
||||
```bash
|
||||
# Create from schema
|
||||
just init-db
|
||||
|
||||
# Create with fixtures (sample data)
|
||||
just fixtures
|
||||
```
|
||||
|
||||
### Reset Test Database
|
||||
|
||||
```bash
|
||||
just reset-db
|
||||
```
|
||||
|
||||
## 📊 Expected Output
|
||||
|
||||
Successful test run:
|
||||
```
|
||||
╔════════════════════════════════════════════╗
|
||||
║ Post-ERG Test Suite ║
|
||||
╚════════════════════════════════════════════╝
|
||||
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Database (Unit) │
|
||||
└─────────────────────────────────────────┘
|
||||
|
||||
✓ PASS: Database connection successful
|
||||
✓ PASS: Found 16 published theses
|
||||
...
|
||||
✅ TEST PASSED
|
||||
|
||||
...
|
||||
|
||||
╔════════════════════════════════════════════╗
|
||||
║ Test Summary ║
|
||||
╠════════════════════════════════════════════╣
|
||||
║ Total: 4 ║
|
||||
║ Passed: 4 ✅ ║
|
||||
║ Failed: 0 ║
|
||||
╚════════════════════════════════════════════╝
|
||||
|
||||
✅ All tests passed!
|
||||
```
|
||||
|
||||
## 🐛 Debugging Failed Tests
|
||||
|
||||
### Check Logs
|
||||
|
||||
```bash
|
||||
# Application errors
|
||||
tail -f error.log
|
||||
|
||||
# Test output
|
||||
php tests/run-tests.php > test-output.txt 2>&1
|
||||
```
|
||||
|
||||
### Run Tests Individually
|
||||
|
||||
When a test fails, run it directly to see full output:
|
||||
|
||||
```bash
|
||||
php tests/Unit/DatabaseTest.php
|
||||
```
|
||||
|
||||
### Check Database
|
||||
|
||||
```bash
|
||||
# Open database
|
||||
just query
|
||||
|
||||
# Check stats
|
||||
just stats
|
||||
```
|
||||
|
||||
## 🔄 Continuous Testing
|
||||
|
||||
### Watch Mode (Future)
|
||||
|
||||
Could add file watching for auto-run:
|
||||
|
||||
```bash
|
||||
# Future: auto-run tests on file change
|
||||
just watch-tests
|
||||
```
|
||||
|
||||
### Pre-commit Hook (Future)
|
||||
|
||||
Add to `.git/hooks/pre-commit`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
php tests/run-tests.php
|
||||
```
|
||||
|
||||
## 📚 Related Documentation
|
||||
|
||||
- [Database Specification](../database/DATABASE_SPECIFICATION.md)
|
||||
- [Security Documentation](../docs/SECURITY.md)
|
||||
- [Development Guide](../MIGRATION_GUIDE.md)
|
||||
|
||||
---
|
||||
|
||||
**To run tests:** `just test`
|
||||
67
tests/Security/SecurityTest.php
Normal file
67
tests/Security/SecurityTest.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* Security Test Suite
|
||||
* Tests SQL injection protection and input sanitization
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../lib/Database.php';
|
||||
|
||||
echo "Security Test Suite\n";
|
||||
echo "===================\n\n";
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Test 1: SQL Injection in search
|
||||
echo "Test 1: SQL Injection Protection (Search)\n";
|
||||
$maliciousQueries = [
|
||||
"' OR '1'='1",
|
||||
"'; DROP TABLE theses; --",
|
||||
"1' UNION SELECT * FROM authors--",
|
||||
"<script>alert('xss')</script>",
|
||||
];
|
||||
|
||||
foreach ($maliciousQueries as $query) {
|
||||
try {
|
||||
$results = $db->searchTheses($query);
|
||||
echo " ✓ Blocked: " . substr($query, 0, 30) . "...\n";
|
||||
} catch (Exception $e) {
|
||||
// Exception is also acceptable (query blocked)
|
||||
echo " ✓ Exception: " . substr($query, 0, 30) . "...\n";
|
||||
}
|
||||
}
|
||||
echo "✓ PASS: SQL injection attempts handled safely\n\n";
|
||||
|
||||
// Test 2: Invalid thesis ID
|
||||
echo "Test 2: Invalid Thesis ID\n";
|
||||
$invalidIds = ["abc", "'; DROP TABLE theses;", "-1", "999999"];
|
||||
|
||||
foreach ($invalidIds as $id) {
|
||||
$result = $db->getThesisById($id);
|
||||
if ($result === null || $result === false) {
|
||||
echo " ✓ Rejected: " . $id . "\n";
|
||||
} else {
|
||||
throw new Exception("Invalid ID '$id' was not rejected");
|
||||
}
|
||||
}
|
||||
echo "✓ PASS: Invalid IDs rejected\n\n";
|
||||
|
||||
// Test 3: XSS in output (checking data is escaped)
|
||||
echo "Test 3: XSS Protection (Output Escaping)\n";
|
||||
$theses = $db->getPublishedTheses(1, 0);
|
||||
if (count($theses) > 0) {
|
||||
$first = $theses[0];
|
||||
// Check that HTML special chars would be handled
|
||||
if (isset($first['title'])) {
|
||||
echo " ✓ Title data retrieved safely\n";
|
||||
}
|
||||
}
|
||||
echo "✓ PASS: Output handling verified\n\n";
|
||||
|
||||
echo "✅ All security tests passed!\n";
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ FAIL: " . $e->getMessage() . "\n";
|
||||
return false;
|
||||
}
|
||||
58
tests/Unit/DatabaseTest.php
Normal file
58
tests/Unit/DatabaseTest.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* Database Connection Test
|
||||
* Tests basic database connectivity and query functionality
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../lib/Database.php';
|
||||
|
||||
echo "Database Connection Test\n";
|
||||
echo "========================\n\n";
|
||||
|
||||
try {
|
||||
// Test 1: Database connection
|
||||
echo "Test 1: Database Connection\n";
|
||||
$db = Database::getInstance();
|
||||
echo "✓ PASS: Database connection successful\n\n";
|
||||
|
||||
// Test 2: Count published theses
|
||||
echo "Test 2: Count Published Theses\n";
|
||||
$count = $db->countPublishedTheses();
|
||||
if ($count >= 0) {
|
||||
echo "✓ PASS: Found {$count} published theses\n\n";
|
||||
} else {
|
||||
throw new Exception("Invalid count returned");
|
||||
}
|
||||
|
||||
// Test 3: Get published theses
|
||||
echo "Test 3: Get Published Theses\n";
|
||||
$theses = $db->getPublishedTheses(5, 0);
|
||||
if (is_array($theses)) {
|
||||
echo "✓ PASS: Retrieved " . count($theses) . " theses\n\n";
|
||||
} else {
|
||||
throw new Exception("Invalid theses array returned");
|
||||
}
|
||||
|
||||
// Test 4: Get single thesis (if any exist)
|
||||
if (count($theses) > 0) {
|
||||
echo "Test 4: Get Single Thesis\n";
|
||||
$first = $theses[0];
|
||||
$thesis = $db->getThesisById($first['id']);
|
||||
|
||||
if ($thesis && isset($thesis['id'])) {
|
||||
echo "✓ PASS: Successfully retrieved thesis #{$first['id']}\n";
|
||||
echo " Title: " . $thesis['title'] . "\n";
|
||||
echo " Author(s): " . ($thesis['authors'] ?? 'N/A') . "\n";
|
||||
echo " Year: " . $thesis['year'] . "\n\n";
|
||||
} else {
|
||||
throw new Exception("Failed to retrieve thesis by ID");
|
||||
}
|
||||
}
|
||||
|
||||
echo "✅ All database tests passed!\n";
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ FAIL: " . $e->getMessage() . "\n";
|
||||
return false;
|
||||
}
|
||||
54
tests/Unit/RateLimitTest.php
Normal file
54
tests/Unit/RateLimitTest.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* Rate Limit Test
|
||||
* Tests rate limiting functionality
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../lib/RateLimit.php';
|
||||
|
||||
echo "Rate Limit Test\n";
|
||||
echo "===============\n\n";
|
||||
|
||||
try {
|
||||
// Test 1: Rate limit initialization
|
||||
echo "Test 1: Rate Limit Initialization\n";
|
||||
$rateLimit = new RateLimit(5, 60); // 5 requests per minute
|
||||
echo "✓ PASS: RateLimit object created\n\n";
|
||||
|
||||
// Test 2: Check method exists and works
|
||||
echo "Test 2: Check Method\n";
|
||||
$allowed = $rateLimit->check();
|
||||
if (is_bool($allowed)) {
|
||||
echo "✓ PASS: check() returns boolean (allowed: " . ($allowed ? 'yes' : 'no') . ")\n\n";
|
||||
} else {
|
||||
throw new Exception("check() did not return boolean");
|
||||
}
|
||||
|
||||
// Test 3: Headers method
|
||||
echo "Test 3: Send Headers Method\n";
|
||||
ob_start();
|
||||
$rateLimit->sendHeaders();
|
||||
ob_end_clean();
|
||||
echo "✓ PASS: sendHeaders() executed without error\n\n";
|
||||
|
||||
// Test 4: Get reset time
|
||||
echo "Test 4: Get Reset Time\n";
|
||||
$resetTime = $rateLimit->getResetTime();
|
||||
if (is_int($resetTime) && $resetTime >= 0) {
|
||||
echo "✓ PASS: getResetTime() returns valid value ($resetTime seconds)\n\n";
|
||||
} else {
|
||||
throw new Exception("Invalid reset time");
|
||||
}
|
||||
|
||||
// Test 5: Cleanup method
|
||||
echo "Test 5: Cleanup Method\n";
|
||||
$rateLimit->cleanup();
|
||||
echo "✓ PASS: cleanup() executed without error\n\n";
|
||||
|
||||
echo "✅ All rate limit tests passed!\n";
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ FAIL: " . $e->getMessage() . "\n";
|
||||
return false;
|
||||
}
|
||||
@@ -1,28 +1,29 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Simple test runner
|
||||
* Post-ERG Test Runner
|
||||
* Runs all tests in the tests/ directory
|
||||
*/
|
||||
|
||||
echo "╔════════════════════════════════════════════╗\n";
|
||||
echo "║ Running Front-Backend Tests ║\n";
|
||||
echo "║ Post-ERG Test Suite ║\n";
|
||||
echo "╚════════════════════════════════════════════╝\n\n";
|
||||
|
||||
$testFiles = [
|
||||
['name' => 'Fixtures', 'path' => __DIR__ . '/../../database/fixtures/CreateTestDatabase.php'],
|
||||
['name' => 'Integration', 'path' => __DIR__ . '/tests/Integration/SearchTest.php'],
|
||||
['name' => 'Security', 'path' => __DIR__ . '/tests/Security/SecurityTest.php'],
|
||||
['name' => 'Unit', 'path' => __DIR__ . '/tests/Unit/RateLimitTest.php'],
|
||||
['name' => 'Database (Unit)', 'path' => __DIR__ . '/Unit/DatabaseTest.php'],
|
||||
['name' => 'Rate Limit (Unit)', 'path' => __DIR__ . '/Unit/RateLimitTest.php'],
|
||||
['name' => 'Search (Integration)', 'path' => __DIR__ . '/Integration/SearchTest.php'],
|
||||
['name' => 'Security', 'path' => __DIR__ . '/Security/SecurityTest.php'],
|
||||
];
|
||||
|
||||
$totalTests = 0;
|
||||
$passedTests = 0;
|
||||
$failedTests = 0;
|
||||
$skippedTests = 0;
|
||||
|
||||
foreach ($testFiles as $test) {
|
||||
echo "┌─────────────────────────────────────────┐\n";
|
||||
echo "│ Test Suite: " . str_pad($test['name'], 27) . "│\n";
|
||||
echo "│ " . str_pad($test['name'], 41) . "│\n";
|
||||
echo "└─────────────────────────────────────────┘\n\n";
|
||||
|
||||
$totalTests++;
|
||||
@@ -30,40 +31,38 @@ foreach ($testFiles as $test) {
|
||||
$file = basename($path);
|
||||
|
||||
if (!file_exists($path)) {
|
||||
echo "⚠️ SKIP: $file (not found at $path)\n\n";
|
||||
echo "⚠️ SKIP: $file (not found)\n\n";
|
||||
$skippedTests++;
|
||||
continue;
|
||||
}
|
||||
|
||||
echo "Running: $file\n";
|
||||
echo str_repeat("─", 50) . "\n";
|
||||
|
||||
ob_start();
|
||||
$exitCode = 0;
|
||||
$testResult = false;
|
||||
|
||||
try {
|
||||
include $path;
|
||||
$testResult = include $path;
|
||||
|
||||
// Check if test returned false or had error indicators in output
|
||||
$output = ob_get_contents();
|
||||
if ($testResult === false ||
|
||||
strpos($output, '❌') !== false ||
|
||||
strpos($output, 'FAIL:') !== false) {
|
||||
$exitCode = 1;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo "❌ ERROR: " . $e->getMessage() . "\n";
|
||||
$exitCode = 1;
|
||||
echo "❌ EXCEPTION: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
$output = ob_get_clean();
|
||||
|
||||
if ($exitCode === 0 && (
|
||||
strpos($output, '❌') !== false ||
|
||||
strpos($output, 'FAIL') !== false ||
|
||||
strpos($output, 'Error:') !== false
|
||||
)) {
|
||||
$exitCode = 1;
|
||||
}
|
||||
|
||||
echo $output;
|
||||
|
||||
if ($exitCode === 0) {
|
||||
echo "\n✅ PASSED\n\n";
|
||||
if ($exitCode === 0 && $testResult !== false) {
|
||||
echo "\n✅ TEST PASSED\n\n";
|
||||
$passedTests++;
|
||||
} else {
|
||||
echo "\n❌ FAILED\n\n";
|
||||
echo "\n❌ TEST FAILED\n\n";
|
||||
$failedTests++;
|
||||
}
|
||||
}
|
||||
@@ -71,9 +70,12 @@ foreach ($testFiles as $test) {
|
||||
echo "╔════════════════════════════════════════════╗\n";
|
||||
echo "║ Test Summary ║\n";
|
||||
echo "╠════════════════════════════════════════════╣\n";
|
||||
echo "║ Total: " . str_pad($totalTests, 35) . "║\n";
|
||||
echo "║ Passed: " . str_pad($passedTests . " ✅", 36) . "║\n";
|
||||
echo "║ Failed: " . str_pad($failedTests . ($failedTests > 0 ? " ❌" : ""), 36) . "║\n";
|
||||
echo "║ Total: " . str_pad($totalTests, 34) . "║\n";
|
||||
echo "║ Passed: " . str_pad($passedTests . " ✅", 35) . "║\n";
|
||||
echo "║ Failed: " . str_pad($failedTests . ($failedTests > 0 ? " ❌" : ""), 35) . "║\n";
|
||||
if ($skippedTests > 0) {
|
||||
echo "║ Skipped: " . str_pad($skippedTests . " ⚠️", 36) . "║\n";
|
||||
}
|
||||
echo "╚════════════════════════════════════════════╝\n\n";
|
||||
|
||||
if ($failedTests > 0) {
|
||||
Reference in New Issue
Block a user