diff --git a/app/public/admin/add.php b/app/public/admin/add.php index a0e664e..4d09a76 100644 --- a/app/public/admin/add.php +++ b/app/public/admin/add.php @@ -1,13 +1,14 @@ getAllSettings(); -// Form help blocks -$helpBlocks = Database::getInstance()->getAllFormHelpBlocks(); -$helpFn = fn(string $key) => empty($helpBlocks[$key]['enabled']) ? '' : ($helpBlocks[$key]['content'] ?? ''); +$helpBlocks = Database::getInstance()->getAllFormHelpBlocks(); -function withAutofocus(string $fieldName, array $attrs = []): array { - global $autofocusField; - if ($autofocusField === $fieldName) { - $attrs['autofocus'] = true; - } - return $attrs; -} +// Shared form variables from the bootstrap helper +extract(FormBootstrap::adminFormVariables( + mode: 'add', + formAction: 'actions/formulaire.php', + hiddenFields: '', + formData: $formData, + siteSettings: $siteSettings, + helpBlocks: $helpBlocks, + options: [ + 'existingWebsiteUrl' => $formData['website_url'] ?? '', + 'existingWebsiteLabel' => $formData['website_label'] ?? '', + 'checkedFormats' => $formData['formats'] ?? [], + ], +)); -function old($key, $default = "") { - global $formData; - if (!isset($formData[$key])) return $default; - if (is_array($formData[$key])) return $formData[$key]; // Return raw array for callers that handle it - if ($formData[$key] === null) return $default; - return htmlspecialchars((string)$formData[$key]); -} - -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; -} - -$isAdmin = true; -$bodyClass = 'admin-body'; -$extraCss = ['/assets/css/form-base.css']; -$extraCssAdmin = ['/assets/css/form-admin.css', '/assets/css/filepond.min.css', '/assets/css/filepond-plugin-image-preview.min.css']; -$extraJs = ['/assets/js/vendor/filepond.min.js', '/assets/js/vendor/filepond-plugin-file-validate-type.min.js', '/assets/js/vendor/filepond-plugin-file-validate-size.min.js', '/assets/js/vendor/filepond-plugin-image-preview.min.js', '/assets/js/vendor/filepond-plugin-image-exif-orientation.min.js', '/assets/js/app/file-upload-filepond.js', '/assets/js/app/beforeunload-guard.js', '/assets/js/app/pill-search.js', '/assets/js/app/jury-autocomplete.js']; +// Asset arrays and page chrome +$isAdmin = true; +$bodyClass = 'admin-body'; +extract(FormBootstrap::adminAssetArrays()); require_once APP_ROOT . '/templates/head.php'; include APP_ROOT . '/templates/header.php'; include APP_ROOT . '/templates/admin/add.php'; diff --git a/app/public/admin/edit.php b/app/public/admin/edit.php index 97d8a80..dba02c4 100644 --- a/app/public/admin/edit.php +++ b/app/public/admin/edit.php @@ -1,47 +1,167 @@ getAllFormHelpBlocks(); -$helpFn = fn(string $key) => empty($helpBlocks[$key]['enabled']) ? '' : ($helpBlocks[$key]['content'] ?? ''); - -function old($key, $default = "") { - global $formData; - if (!isset($formData[$key])) return $default; - if (is_array($formData[$key])) return $formData[$key]; // Return raw array for callers that handle it - if ($formData[$key] === null) return $default; - return htmlspecialchars((string)$formData[$key]); -} +$formData = []; +$siteSettings = Database::getInstance()->getAllSettings(); +$helpBlocks = Database::getInstance()->getAllFormHelpBlocks(); try { $ctrl = ThesisEditController::create(); $view = $ctrl->load($thesisId); extract($view); } catch (Exception $e) { - error_log("Error loading edit page: " . $e->getMessage()); - die("Erreur lors du chargement: " . $e->getMessage()); + error_log('Error loading edit page: ' . $e->getMessage()); + die('Erreur lors du chargement: ' . $e->getMessage()); } -$isAdmin = true; $bodyClass = 'admin-body'; -$extraCss = ['/assets/css/form-base.css']; -$extraCssAdmin = ['/assets/css/form-admin.css', '/assets/css/filepond.min.css', '/assets/css/filepond-plugin-image-preview.min.css']; -$extraJs = ['/assets/js/vendor/filepond.min.js', '/assets/js/vendor/filepond-plugin-file-validate-type.min.js', '/assets/js/vendor/filepond-plugin-file-validate-size.min.js', '/assets/js/vendor/filepond-plugin-image-preview.min.js', '/assets/js/vendor/filepond-plugin-image-exif-orientation.min.js', '/assets/js/app/file-upload-filepond.js', '/assets/js/app/beforeunload-guard.js', '/assets/js/app/pill-search.js', '/assets/js/app/jury-autocomplete.js']; +// Merge flash repopulation data (from redirects after validation errors) +// with current thesis data so old() can fall back to DB values. +$formData = array_merge($formData, [ + 'titre' => $thesis['title'], + 'subtitle' => $thesis['subtitle'] ?? '', + 'auteurice' => $thesis['authors'] ?? '', + 'mail' => $contactInterne ?? '', + 'contact_visible' => $currentContactVisible ?? '', + 'synopsis' => $thesis['synopsis'] ?? '', + 'tag' => $thesis['keywords'] ?? '', + 'année' => $thesis['year'], + 'orientation' => $thesis['orientation'], + 'ap' => $thesis['ap_program'], + 'finality' => $thesis['finality_type'], + 'lien' => $thesis['baiu_link'] ?? '', + 'contact_public' => $contactPublic ?? false, + 'contact_interne' => $contactInterne ?? '', +]); + +// Build jury arrays +$juryPromoteurs = []; +$juryPromoteursUlb = []; +$lecteursInternes = []; +$lecteursExternes = []; +$juryPromoteur = null; +$juryPromoteurUlb = null; + +foreach ($jury as $jm) { + if ($jm['role'] === 'president') continue; + if ($jm['role'] === 'promoteur') { + if (($jm['is_ulb'] ?? 0) == 1) { + $juryPromoteursUlb[] = $jm; + } else { + $juryPromoteurs[] = $jm; + } + } elseif ($jm['role'] === 'lecteur') { + if (($jm['is_external'] ?? 0) == 1) { + $lecteursExternes[] = $jm; + } else { + $lecteursInternes[] = $jm; + } + } +} +if (!empty($juryPromoteurs) && $juryPromoteur === null) $juryPromoteur = $juryPromoteurs[0]['name']; +if (!empty($juryPromoteursUlb) && $juryPromoteurUlb === null) $juryPromoteurUlb = $juryPromoteursUlb[0]['name']; + +// Build existing website URL / label from current files +$existingWebsiteUrl = ''; +$existingWebsiteLabel = ''; +foreach ($currentFiles as $f) { + if ($f['file_type'] === 'website') { + $existingWebsiteUrl = $f['file_path'] ?? ''; + $existingWebsiteLabel = $f['display_label'] ?? ''; + break; + } +} + +// Languages — either from flash repopulation or current thesis data +$formData['languages'] = $formData['languages'] ?? $currentLanguages ?? []; + +// Compute "other" languages (not in the predefined checkbox list) +$predefinedLangIds = array_column($languages, 'id'); +$otherLangIds = array_diff($currentLanguages ?? [], $predefinedLangIds); +$selectedOtherLanguages = []; +if (!empty($otherLangIds)) { + $allLangs = Database::getInstance()->getAllLanguages(); + $allLangMap = []; + foreach ($allLangs as $al) { + $allLangMap[(int)$al['id']] = $al['name']; + } + foreach ($otherLangIds as $lid) { + $lid = (int)$lid; + if (isset($allLangMap[$lid])) { + $selectedOtherLanguages[] = $allLangMap[$lid]; + } + } + sort($selectedOtherLanguages, SORT_NATURAL | SORT_FLAG_CASE); +} + +// Tags — either from flash repopulation or current thesis data +$keywordsStr = $thesis['keywords'] ?? ''; +$currentTags = $keywordsStr !== '' ? array_map('trim', explode(',', $keywordsStr)) : []; +if (!empty($formData['tag']) && is_array($formData['tag'])) { + $currentTags = $formData['tag']; +} + +// Formats — either from flash repopulation or current thesis data +$checkedFormats = $formData['formats'] ?? $currentFormats ?? []; +$formData['formats'] = $checkedFormats; + +// Shared form variables from the bootstrap helper +extract(FormBootstrap::adminFormVariables( + mode: 'edit', + formAction: '/admin/actions/edit.php', + hiddenFields: + '' + . '', + formData: $formData, + siteSettings: $siteSettings, + helpBlocks: $helpBlocks, + options: [ + 'filesMode' => 'edit', + 'existingWebsiteUrl' => $existingWebsiteUrl, + 'existingWebsiteLabel' => $existingWebsiteLabel, + 'checkedFormats' => $checkedFormats, + 'juryPromoteurs' => $juryPromoteurs, + 'juryPromoteursUlb' => $juryPromoteursUlb, + 'lecteursInternes' => $lecteursInternes, + 'lecteursExternes' => $lecteursExternes, + 'juryPromoteur' => $juryPromoteur, + 'juryPromoteurUlb' => $juryPromoteurUlb, + 'currentRaw' => $currentRaw ?? [], + 'currentCover' => $currentCover ?? null, + 'currentFiles' => $currentFiles ?? [], + 'currentContextNote' => $currentContextNote ?? null, + 'currentContactVisible' => $currentContactVisible ?? null, + 'contactInterne' => $contactInterne ?? null, + 'contactPublic' => $contactPublic ?? false, + 'showCoverPreview' => true, + 'showExistingFiles' => true, + 'defaultAccessTypeId' => $currentAccessTypeId ?? 2, + ], +)); + +// Inject thesis-derived values into formData for the template +$formData['access_type_id'] = $currentAccessTypeId; +$formData['license_id'] = $currentLicenseId; +$formData['license_custom'] = $currentRaw['license_custom'] ?? ''; +$formData['cc2r'] = $currentRaw['cc2r'] ?? false; + +// Asset arrays and page chrome +$isAdmin = true; +$bodyClass = 'admin-body'; +extract(FormBootstrap::adminAssetArrays()); require_once APP_ROOT . '/templates/head.php'; include APP_ROOT . '/templates/header.php'; include APP_ROOT . '/templates/admin/edit.php'; diff --git a/app/src/Form/FormBootstrap.php b/app/src/Form/FormBootstrap.php new file mode 100644 index 0000000..4234d16 --- /dev/null +++ b/app/src/Form/FormBootstrap.php @@ -0,0 +1,183 @@ + ['/assets/css/form-base.css'], + 'extraCssAdmin' => [ + '/assets/css/form-admin.css', + '/assets/css/filepond.min.css', + '/assets/css/filepond-plugin-image-preview.min.css', + ], + 'extraJs' => [ + '/assets/js/vendor/filepond.min.js', + '/assets/js/vendor/filepond-plugin-file-validate-type.min.js', + '/assets/js/vendor/filepond-plugin-file-validate-size.min.js', + '/assets/js/vendor/filepond-plugin-image-preview.min.js', + '/assets/js/vendor/filepond-plugin-image-exif-orientation.min.js', + '/assets/js/app/file-upload-filepond.js', + '/assets/js/app/beforeunload-guard.js', + '/assets/js/app/pill-search.js', + '/assets/js/app/jury-autocomplete.js', + ], + ]; + } + + // ── Helpers ─────────────────────────────────────────────────────────────── + + /** + * Build an `old()` callable for admin forms (add/edit). + * + * Returns `htmlspecialchars`-escaped string values, or the raw array for + * array-typed keys. Uses the $formData array from the caller's scope + * (which may be $_SESSION['form_data'] with an edit-data overlay). + */ + public static function adminOld(array &$formData): callable + { + return function (string $key, string $default = '') use (&$formData): string { + if (!isset($formData[$key])) return $default; + if (is_array($formData[$key])) return $formData[$key]; + if ($formData[$key] === null) return $default; + return htmlspecialchars((string)$formData[$key]); + }; + } + + /** + * Build an `old()` callable for share/partage forms. + * + * Returns raw values (no escaping) — callers must apply htmlspecialchars() + * when rendering. Supports colon-delimited keys (e.g. "key:0"). + */ + public static function shareOld(array &$formData): callable + { + return function (string $key, string $default = '') use (&$formData) { + $parts = explode(':', $key); + $value = $formData; + foreach ($parts as $part) { + if (is_array($value) && array_key_exists($part, $value)) { + $value = $value[$part]; + } else { + return $default; + } + } + return $value; + }; + } + + // ── Common form variables ───────────────────────────────────────────────── + + /** + * Return the standard variable set needed by form partials for an admin + * add/edit page. Returns an array suitable for extract(). + * + * @param string $mode 'add' | 'edit' + * @param string $formAction URL string + * @param string $hiddenFields Raw HTML for hidden inputs + * @param array $formData Merged form data for repopulation + * @param array $siteSettings from getAllSettings() + * @param array $helpBlocks from getAllFormHelpBlocks() + * @param array $options Overrides for individual keys (juryPromoteur, etc.) + */ + public static function adminFormVariables( + string $mode, + string $formAction, + string $hiddenFields, + array &$formData, + array $siteSettings, + array $helpBlocks, + array $options = [] + ): array { + $helpFn = function (string $key) use ($helpBlocks): string { + return empty($helpBlocks[$key]['enabled']) ? '' : ($helpBlocks[$key]['content'] ?? ''); + }; + + $autofocusField = App::consumeAutofocus(); + + // Controls + $showContact = false; + $showBackoffice = ($mode === 'add' || $mode === 'edit'); + + // Licence / access toggles: admin always enables all three + $libreEnabled = true; + $interneEnabled = true; + $interditEnabled = true; + $generalitiesHtml = $helpFn('fieldset_generalites'); + $defaultAccessTypeId = $options['defaultAccessTypeId'] ?? 2; + + return array_merge([ + // Base + 'mode' => $mode, + 'formAction' => $formAction, + 'hiddenFields' => $hiddenFields, + 'errorFieldName' => $autofocusField, + 'synopsisExtra' => $options['synopsisExtra'] ?? '', + + // Helpers + 'helpFn' => $helpFn, + 'helpBlocks' => $helpBlocks, + 'oldFn' => self::adminOld($formData), + 'withAutofocusFn' => function (string $field, array $attrs = []) use ($autofocusField): array { + if ($autofocusField === $field) $attrs['autofocus'] = true; + return $attrs; + }, + + // Jury + 'juryPromoteur' => null, + 'juryPromoteurs' => [], + 'juryPromoteurUlb' => null, + 'juryPromoteursUlb' => [], + 'lecteursInternes' => [], + 'lecteursExternes' => [], + 'showPromoteurUlb' => true, + 'promoteurUlbConditional' => false, + + // Licence / access + 'libreEnabled' => $libreEnabled, + 'interneEnabled' => $interneEnabled, + 'interditEnabled' => $interditEnabled, + 'generalitiesHtml' => $generalitiesHtml, + 'defaultAccessTypeId' => $defaultAccessTypeId, + + // Optional sections + 'showContact' => $showContact, + 'showBackoffice' => $showBackoffice, + 'showCoverPreview' => false, + 'showExistingFiles' => false, + 'showEmailConfirmation' => false, + 'showFlash' => false, + 'showIntroHelp' => false, + + // Files + 'filesMode' => $options['filesMode'] ?? 'add', + + // Website + 'existingWebsiteUrl' => $options['existingWebsiteUrl'] ?? '', + 'existingWebsiteLabel' => $options['existingWebsiteLabel'] ?? '', + 'checkedFormatsForSiteWeb' => $options['checkedFormats'] ?? [], + + // Backoffice (empty for add, populated for edit by caller) + 'currentRaw' => [], + 'contactInterne' => null, + 'contactPublic' => false, + 'currentContextNote' => null, + 'currentContactVisible' => null, + + // Files (edit mode) + 'currentCover' => null, + 'currentFiles' => [], + ], $options); + } +} diff --git a/app/templates/admin/add.php b/app/templates/admin/add.php index ba723fc..767e7cf 100644 --- a/app/templates/admin/add.php +++ b/app/templates/admin/add.php @@ -2,53 +2,7 @@

Ajouter un TFE

'; - $errorFieldName = $autofocusField ?? null; - $withAutofocusFn = function (string $field, array $attrs = []) use ($autofocusField) { - if ($autofocusField === $field) $attrs['autofocus'] = true; - return $attrs; - }; - - $synopsisExtra = ''; - - // Jury: fresh add (all empty) - $juryPromoteur = null; - $juryPromoteurs = []; - $juryPromoteurUlb = null; - $juryPromoteursUlb = []; - $lecteursInternes = []; - $lecteursExternes = []; - $showPromoteurUlb = true; - $promoteurUlbConditional = false; - - // Licence / access - $libreEnabled = ($siteSettings['access_type_libre_enabled'] ?? '0') === '1'; - $interneEnabled = ($siteSettings['access_type_interne_enabled'] ?? '1') === '1'; - $interditEnabled = ($siteSettings['access_type_interdit_enabled'] ?? '1') === '1'; - $generalitiesHtml = $helpFn('fieldset_generalites'); - $defaultAccessTypeId = 2; - - // Optional sections - $showContact = false; // Admin: contact visibility controlled by filling 'mail' field in fieldset-tfe-info - $showBackoffice = true; - - // Files: add mode - $filesMode = 'add'; - - // Website URL (repopulation) - $existingWebsiteUrl = $formData['website_url'] ?? ''; - $existingWebsiteLabel = $formData['website_label'] ?? ''; - - // Backoffice (add mode: null → falls back to formData) - $currentRaw = []; - $contactInterne = null; - $contactPublic = false; - $currentContextNote = null; - $currentContactVisible = null; - + // All form variables are already in scope from FormBootstrap::adminFormVariables(). include APP_ROOT . '/templates/partials/form/form.php'; ?> diff --git a/app/templates/admin/edit.php b/app/templates/admin/edit.php index 1d0617a..54766e3 100644 --- a/app/templates/admin/edit.php +++ b/app/templates/admin/edit.php @@ -2,146 +2,7 @@

Modifier un TFE

$thesis['title'], - 'subtitle' => $thesis['subtitle'] ?? '', - 'auteurice' => $thesis['authors'] ?? '', - 'mail' => $contactInterne ?? '', - 'contact_visible' => $currentContactVisible ?? '', - 'synopsis' => $thesis['synopsis'] ?? '', - 'tag' => $thesis['keywords'] ?? '', - 'année' => $thesis['year'], - 'orientation' => $thesis['orientation'], - 'ap' => $thesis['ap_program'], - 'finality' => $thesis['finality_type'], - 'lien' => $thesis['baiu_link'] ?? '', - 'contact_public' => $contactPublic ?? false, - 'contact_interne' => $contactInterne ?? '', - ]); - $oldFn = fn(string $key, string $default = '') => - isset($editFormData[$key]) && !is_array($editFormData[$key]) - ? htmlspecialchars((string)$editFormData[$key]) : $default; - - $withAutofocusFn = function (string $field, array $attrs = []) use ($autofocusField) { - if ($autofocusField === $field) $attrs['autofocus'] = true; - return $attrs; - }; - - // ── Shared form variables ────────────────────────────────────────────────── - $mode = 'edit'; - $formAction = '/admin/actions/edit.php'; - $hiddenFields = '' - . ''; - $errorFieldName = $autofocusField ?? null; - - $synopsisExtra = ''; - $formData = $editFormData; - - // Jury data - $juryPromoteur = null; - $juryPromoteurs = []; - $juryPromoteurUlb = null; - $juryPromoteursUlb = []; - $lecteursInternes = []; - $lecteursExternes = []; - foreach ($jury as $jm) { - if ($jm['role'] === 'president') { - continue; - } - if ($jm['role'] === 'promoteur') { - if (($jm['is_ulb'] ?? 0) == 1) { - $juryPromoteursUlb[] = $jm; - } else { - $juryPromoteurs[] = $jm; - } - } elseif ($jm['role'] === 'lecteur') { - if (($jm['is_external'] ?? 0) == 1) { - $lecteursExternes[] = $jm; - } else { - $lecteursInternes[] = $jm; - } - } - } - // Backwards compat: if only one, also set the scalar for simple templates - if (!empty($juryPromoteurs) && $juryPromoteur === null) { - $juryPromoteur = $juryPromoteurs[0]['name']; - } - if (!empty($juryPromoteursUlb) && $juryPromoteurUlb === null) { - $juryPromoteurUlb = $juryPromoteursUlb[0]['name']; - } - $showPromoteurUlb = true; - $promoteurUlbConditional = false; - - // Licence / access — always all enabled for admin - $libreEnabled = true; - $interneEnabled = true; - $interditEnabled = true; - $generalitiesHtml = $helpFn('fieldset_generalites'); - $defaultAccessTypeId = $currentAccessTypeId ?? 2; - $formData['access_type_id'] = $currentAccessTypeId; - $formData['license_id'] = $currentLicenseId; - $formData['license_custom'] = $currentRaw['license_custom'] ?? ''; - $formData['cc2r'] = $currentRaw['cc2r'] ?? false; - - // Optional sections - $showContact = false; // Admin: contact visibility controlled by filling 'mail' field in fieldset-tfe-info - $showBackoffice = true; - - // Files: edit mode - $filesMode = 'edit'; - $currentCover = $currentCover ?? null; - $currentFiles = $currentFiles ?? []; - $currentContextNote = $currentContextNote ?? null; - - // Website URL from existing files - $existingWebsiteUrl = ''; - $existingWebsiteLabel = ''; - foreach ($currentFiles as $f) { - if ($f['file_type'] === 'website') { - $existingWebsiteUrl = $f['file_path'] ?? ''; - $existingWebsiteLabel = $f['display_label'] ?? ''; - break; - } - } - - // Languages — either from flash repopulation or current thesis data - $formData['languages'] = $formData['languages'] ?? $currentLanguages ?? []; - - // Compute "other" languages (those not in the predefined checkbox list) - $predefinedLangIds = array_column($languages, 'id'); - $otherLangIds = array_diff($currentLanguages ?? [], $predefinedLangIds); - $selectedOtherLanguages = []; - if (!empty($otherLangIds)) { - $allLangs = Database::getInstance()->getAllLanguages(); - $allLangMap = []; - foreach ($allLangs as $al) { - $allLangMap[(int)$al['id']] = $al['name']; - } - foreach ($otherLangIds as $lid) { - $lid = (int)$lid; - if (isset($allLangMap[$lid])) { - $selectedOtherLanguages[] = $allLangMap[$lid]; - } - } - // Sort alphabetically - sort($selectedOtherLanguages, SORT_NATURAL | SORT_FLAG_CASE); - } - - // Tags — either from flash repopulation or current thesis data - $keywordsStr = $thesis['keywords'] ?? ''; - $currentTags = $keywordsStr !== '' ? array_map('trim', explode(',', $keywordsStr)) : []; - // If formData has tag[], use that instead - if (!empty($formData['tag']) && is_array($formData['tag'])) { - $currentTags = $formData['tag']; - } - - // Formats — either from flash repopulation or current thesis data - $checkedFormats = $formData['formats'] ?? $currentFormats ?? []; - // Populate formData.formats for checkbox-list partial - $formData['formats'] = $checkedFormats; - $checkedFormatsForSiteWeb = $checkedFormats; - + // All form variables are already in scope from FormBootstrap::adminFormVariables(). include APP_ROOT . '/templates/partials/form/form.php'; ?>