Initial commit
This commit is contained in:
7
.env.example
Normal file
7
.env.example
Normal file
@@ -0,0 +1,7 @@
|
||||
# Identifiants MySQL — utilisés par les conteneurs db et wordpress.
|
||||
# Copier ce fichier en .env et remplir avec de vraies valeurs avant `docker compose up`.
|
||||
|
||||
MYSQL_ROOT_PASSWORD=changeme-root
|
||||
MYSQL_DATABASE=thalim
|
||||
MYSQL_USER=thalim
|
||||
MYSQL_PASSWORD=changeme
|
||||
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# --- Secrets ---
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# --- WordPress install ---
|
||||
wp-data/
|
||||
|
||||
# --- Outils locaux ---
|
||||
.claude/
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# --- Archives / dumps ---
|
||||
*.tar.gz
|
||||
*.tgz
|
||||
*.zip
|
||||
*.sql
|
||||
*.sql.gz
|
||||
|
||||
# --- OS / éditeur ---
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.swp
|
||||
*~
|
||||
209
CLAUDE.md
Normal file
209
CLAUDE.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# CLAUDE.md
|
||||
|
||||
Guidance pour Claude Code sur ce dépôt.
|
||||
|
||||
## Project overview
|
||||
|
||||
Site WordPress du laboratoire THALIM (Théorie et Histoire des Arts et des Littératures de la Modernité). Stack Docker + thème Timber/Twig customisé + deux plugins maison (HAL importer, Newsletter). Le site est bilingue FR/EN via un système maison (Polylang a été retiré).
|
||||
|
||||
## Dev environment
|
||||
|
||||
```bash
|
||||
docker-compose up -d # WordPress, MySQL, phpMyAdmin
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
- WordPress : http://localhost:8020
|
||||
- phpMyAdmin : http://localhost:8021
|
||||
- MySQL : `localhost:3307` → `db:3306`
|
||||
- Identifiants dans `.env`
|
||||
- WordPress monté depuis `./wp-data/`
|
||||
|
||||
## Architecture
|
||||
|
||||
### Thème : `wp-data/wp-content/themes/thalim/`
|
||||
|
||||
Timber/Twig. Chaque `*.php` charge un Twig de `templates/`. `base.twig` est le layout, les autres l'étendent.
|
||||
|
||||
- `functions.php` — gros (≈1400 lignes) : setup, i18n, contexte Twig, AJAX, filtres de requête, customisations admin
|
||||
- `inc/` — helpers PHP par contexte (voir détails plus bas)
|
||||
- `templates/` + `templates/partials/` — Twig
|
||||
- `scss/` → `css/` — SASS compilé **manuellement** par l'utilisateur (CSS commité)
|
||||
- `js/` — scripts frontend et `adminDashboardMods.js` (admin)
|
||||
- `vendor/` — Composer (Timber 2.x). `composer install` après clone
|
||||
|
||||
### Plugins maison
|
||||
|
||||
- **`thalim-hal-importer/`** — import publications HAL (structure 254015). Admin : Outils → HAL Import. Voir le README du plugin pour le mapping doc types → catégories (10 types : ART, COUV, OUV, COMM, ISSUE, PROCEEDINGS, THESE, HDR, SON, VIDEO).
|
||||
- **`thalim-newsletter/`** — composition et export HTML des digests mensuels. Admin : Outils → Newsletter. Voir le README du plugin pour les constantes de catégories.
|
||||
|
||||
### Plugins WP requis
|
||||
|
||||
- **Pods** — types de contenu + champs personnalisés (tout est en Pods)
|
||||
- **Members** — gestion fine des rôles/capacités
|
||||
- **Simple Local Avatars** — avatars uploadés (vs Gravatar)
|
||||
- **Relevanssi** — recherche
|
||||
|
||||
## Multilingue (système maison)
|
||||
|
||||
Polylang a été **remplacé** par un système custom. Toute la logique vit dans `functions.php`.
|
||||
|
||||
- **Détection** : `thalim_current_language()` retourne `'en'` si `THALIM_ORIGINAL_URI` commence par `/en/`, sinon `'fr'`. Le préfixe `/en/` est strippé via `do_parse_request` avant la résolution d'URL WP, et `redirect_canonical` est désactivé sur les URLs `/en/` pour éviter que WP ne supprime le préfixe
|
||||
- **Champs texte bilingues** : convention `"FR // EN"` dans un même champ. `thalim_bilingual($value, $lang)` (exposé comme filtre Twig `|bilingual`) renvoie la bonne moitié. Utilisé partout : titres de posts, noms de termes de taxonomie, sous-titres, lieux, fonctions, types, etc.
|
||||
- **Champs longs séparés** : pour le corps des posts, c'est un champ Pods `body_en` à part. Idem pour `biographie_en`, `presentation_en`, `recherches_en_cours_en`, `resume_de_la_these_en`, `autres_domaines_de_recherches_en` sur les utilisateurs, et `presentation_en` / `presentation_detail_en` sur le post `contenu_general`
|
||||
- **URLs préfixées** : `thalim_en_url($url)` ajoute `/en` aux URLs internes en mode EN, attaché à `term_link`, `post_link`, `page_link`, `post_type_link`, `author_link`. Filtre Twig `|en_url` aussi disponible. No-op en admin (URI commence par `/wp-admin/`)
|
||||
- **Noms de catégories** : `thalim_cat_name($cat)` (filtre Twig `|cat_name`) lit le term meta `titre_anglais` en mode EN. Aussi appliqué au title de l'onglet navigateur via `document_title_parts`
|
||||
- **Menus** : slugs `Navigation` / `Navigation-en` et `Footer` / `Footer-en` (pas `menu-principal` — c'est un legacy obsolète)
|
||||
- **Contenu général** : un seul post de type `contenu_general` fournit les blocs UMR/thalim/siècles/présentation au layout via `gc` dans le contexte Twig
|
||||
- **Sélecteur de langue** : `thalim_language_switcher()` retourne `['fr' => ['slug','url','current_lang'], 'en' => …]` — remplace `pll_the_languages()`
|
||||
- **Override pour AJAX** : poser `$GLOBALS['thalim_lang_override'] = $lang` avant `thalim_current_language()` (utilisé par les handlers `load_more_posts` / `load_more_agenda` qui reçoivent la langue en POST)
|
||||
|
||||
## Dates et tri par événement
|
||||
|
||||
Les annonces ont une `date_de_debut` / `date_de_fin` (champ date Pods) ou un `datetime` (communications). Les listes et l'agenda doivent **prioriser ces dates sur `post_date`**.
|
||||
|
||||
- **Activation** : ajouter `'thalim_event_date_order' => true` aux args d'un `WP_Query` (ou Timber)
|
||||
- **Filtre de plage** : `'thalim_event_date_filter' => ['from' => 'YYYY-MM-DD', 'to' => 'YYYY-MM-DD']`
|
||||
- **Implémentation** : filtres `posts_join` + `posts_orderby` + `posts_where` dans `functions.php`. LEFT JOIN sur `postmeta` puis `CASE WHEN date_de_debut ELSE datetime ELSE post_date END DESC`. Les valeurs `0000-00-00...` sont traitées comme NULL
|
||||
- **Format d'affichage** : `thalim_format_date($raw, $lang)` dans `inc/single-helpers.php` renvoie `date_i18n('j F Y', $ts)`. Les abréviations de mois (3-lettres) pour les vignettes agenda sont codées en dur dans `functions.php` (`thalim_get_agenda_card_data()`) et dans `inc/single-helpers.php` (séances)
|
||||
- **Construction du `date_label`** : la logique « Le X de H1 à H2 / Du X au Y / Jusqu'au X / X à H » vit dans `thalim_get_agenda_card_data()` — à dupliquer prudemment si besoin ailleurs
|
||||
|
||||
## Helpers PHP (`inc/`)
|
||||
|
||||
| Fichier | Rôle |
|
||||
|---|---|
|
||||
| `single-helpers.php` | `thalim_get_single_data($post_id)` résout tous les champs Pods d'un post en tableau prêt pour Twig : dates formatées, images vs documents (split par mime-type), membres résolus en `{name, url}`, taxonomies (axes, étiquettes, programmes), séances triées en `seances_a_venir` / `seances_passees`, hiérarchie de catégorie, `type_label` et `fonction_label` dérivés des champs `type_*` / `fonction_*` ou (legacy) du `_pods_categorie`, liens externes (1–3), Canal-U / YouTube embeds. Inclut aussi `thalim_format_date()` (partagée). |
|
||||
| `author-helpers.php` | `thalim_get_author_data($user_id)` (profil membre) et `thalim_get_author_posts_by_category($user_id)` (posts liés au membre, groupés par catégorie primaire, plus un groupe spécial « séances de séminaire » via cat 12). Tri inter-groupes par `ordre_profil` (term meta) puis par nombre de posts |
|
||||
| `membres-helpers.php` | Page `/membres` : `thalim_get_membres_groups()` regroupe par slugs de rôle taxonomy (mapping codé en dur dans `$group_definitions` — voir « Page membres » plus bas) |
|
||||
| `post-card-helpers.php` | `thalim_get_card_data($post_id)` / `thalim_get_cards_data($posts)` : données pour `partials/post-card.twig`. Inclut la résolution catégorie parente pour le code couleur (`parent_slug`), la première image (medium), la `card_event_date`, et la redirection `#seance-{ID}` pour les séances de séminaire |
|
||||
| `pods-conditional-required.php` | Patch : Pods n'évalue pas sa propre logique conditionnelle côté serveur lors de la validation des champs *required*. On la rejoue dans `pods_api_pre_save_pod_item_post` et on désactive `required` sur les champs masqués |
|
||||
| `pods-save-error-handler.php` | Quand Pods déclenche un `wp_die()` à la sauvegarde admin, intercepter (`pods_error_die`), stocker tous les `pods_meta_*` dans un transient, rediriger vers `post.php?action=edit`, annuler le statut si le post passait à `publish`, et restaurer les champs côté React via `get_post_metadata` + JS dans `admin_footer`. **Mécanisme partagé** avec : |
|
||||
| `post-title-required.php` | Force un titre non-vide à la sauvegarde, réutilise le même mécanisme transient/restore |
|
||||
| `admin-users-filter.php` | Dropdown « Statut » sur `users.php`, filtre via meta `role_1`/`role_2`/`role_3` |
|
||||
|
||||
### Avatars (chaîne de fallback)
|
||||
|
||||
`thalim_get_user_avatar_url($user_id)` dans `functions.php` :
|
||||
|
||||
1. Simple Local Avatar (`simple_local_avatar` user meta) — résolu via `media_id` quand dispo (pour survivre aux changements de domaine), sinon URL réécrite
|
||||
2. Gravatar (HEAD request avec `d=404`) — résultat (positif ou négatif) caché 1 semaine en transient `thalim_gravatar_{ID}`
|
||||
3. Chaîne vide → templates fallback initiales
|
||||
|
||||
## Pages spécifiques
|
||||
|
||||
### Page Membres (`page-membres.php`)
|
||||
|
||||
Pilotée par `inc/membres-helpers.php`.
|
||||
|
||||
- Groupes basés sur les **slugs** de la taxonomy `role` (pas les term IDs — survit aux migrations)
|
||||
- Groupe « Direction » en tête, ordre fixe : `directeur` puis `directeur_adjoint`, lus depuis les post meta de la page `le-laboratoire`
|
||||
- Tri intra-groupe alphabétique sur le nom de famille via `Collator('fr_FR')` à `PRIMARY` (insensible accents/casse). Pour les noms composés (« Duclaux de l'Estoile »), `thalim_get_sort_key()` prend le premier mot du meta `last_name`
|
||||
- Cas spécial « Personnel d'accompagnement à la recherche » : les statuts contenant « Gestion et pilotage » remontent en tête
|
||||
- Chaque `<tr>` porte `data-name`, `data-status`, `data-affiliation`, `data-roles` (séparés par `|`), `data-avatar`, `data-domaines`, `data-autres-domaines` — consommés par `membresFilters.js` (filtre rôle + recherche texte sur name/status/affiliation, tri colonne), `membresPopover.js` (popover au hover), `seanceToggle.js` (collapse de groupe)
|
||||
- **Zébrage** : classe `.is-even-row` posée en JS après chaque filtre/tri — pas `nth-of-type`, qui compte les lignes masquées et casse l'alternance
|
||||
|
||||
### Single post
|
||||
|
||||
`single-helpers.php` fait quasi tout le travail. À noter :
|
||||
|
||||
- Hiérarchie de catégorie : `wp_get_post_categories()` → exclut cat 12 (séance) et 31, prend les ancêtres pour `parent_slug` (color theme) et `parent_name`
|
||||
- Posts en catégorie racine sans sous-catégorie → leur lien vers la liste devient `/category/{slug}/autres/` (rewrite rule dédiée)
|
||||
- `documents_joints` (Pods relationship file) est splitté par mime-type en `images` (taille `large`, marqué `portrait` si `h > w`) et `documents`. Les captions et titres passent par `|bilingual`
|
||||
- Toggle « afficher le titre des images en légende » : meta `afficher_le_titre_des_images_en_legende`
|
||||
- Séances de séminaire (`seances` meta = array de post IDs) sont **redirigées via `template_redirect`** vers le parent séminaire avec ancre `#seance-{ID}`. Les helpers post-card et agenda-card font la même substitution pour les liens
|
||||
|
||||
### Front page
|
||||
|
||||
`is_front_page()` charge : logo animé (`animatedLogo.js`, aussi sur 404), hero avec mots colorés (`coloredWordsHero.js`), Swiper d'annonces (`annoncesSwiper.js`), `messageLabo.js`, nuage de mots-clés (`keywordCloud.js`, exclut les tags marqués `ne_pas_afficher_dans_le_nuage`), `quickLinks.js`. Tags localisés dans `thalimTags` (window) via `wp_localize_script`.
|
||||
|
||||
### Catégorie / archives / agenda
|
||||
|
||||
`is_category()` charge Swiper + `agendaView.js` (slider d'événements à venir, scroll horizontal centré sur aujourd'hui via `today_offset` renvoyé par l'AJAX).
|
||||
|
||||
Toutes les pages d'archives (catégorie, taxonomie, tag, `/annonces`, search) chargent :
|
||||
- `infiniteScroll.js` — AJAX paginé via `wp_ajax_load_more_posts`. Params POST : `page`, `category`, `axe`, `date_from`/`date_to`, `taxonomy`/`term`, `filter_cat`/`filter_autres`, `exclude_cats`, `search`, `lang`. Recherche passe par Relevanssi (`'relevanssi' => true`, `orderby => 'relevance'`)
|
||||
- `categoryFilters.js` — UI filtres
|
||||
|
||||
Sur les pages de catégorie, les **posts épinglés** sont sortis du flux principal (`epingler_dans_la_categorie` = `1` ET (`date_de_fin_depinglage` vide OU ≥ aujourd'hui)) — affichés en tête, exclus de la pagination AJAX via `post__not_in`.
|
||||
|
||||
Sur les pages de taxonomie/tag, les **séances de séminaire (cat 12)** sont exclues : on liste les posts taggés, pas leurs séances.
|
||||
|
||||
## Restrictions / sécurité contenus
|
||||
|
||||
- **Vie du labo (cat 9)** : exclue des requêtes pour les non-connectés via `pre_get_posts`, redirige vers `/` si on visite la page de catégorie, retirée du menu via `wp_nav_menu_objects`
|
||||
- **Contributeurs** :
|
||||
- Liste admin filtrée : posts qu'ils ont écrits **OU** où ils sont dans `membres`/`autre_membres` (filtre `pre_get_posts` + override de `wp_count_posts` pour des compteurs cohérents)
|
||||
- Peuvent **éditer** les posts où ils figurent comme membre (filtre `user_has_cap` — accorde les caps primitives le temps du check, sans modifier la table des rôles). Gère le flow save : capture `post_ID` depuis `$_POST` quand WP teste `edit_others_posts` sans `$args[2]`
|
||||
- Login redirige vers `edit.php` (pas le dashboard). Dashboard et menu Outils masqués pour les non-admins
|
||||
- **Filtre catégorie admin** : par défaut WP inclut les sous-catégories, on force `include_children => false` (`thalim_exact_category_filter`)
|
||||
- **Axes filtres** : pour les contributeurs, restreints au premier groupe (le plus récent) via `thalim_get_axes_filter_groups()` slice
|
||||
|
||||
## Customisations admin
|
||||
|
||||
`js/adminDashboardMods.js` (≈900 lignes) + `css/admin.css`. L'admin est très modifié.
|
||||
|
||||
### Patterns transversaux
|
||||
|
||||
- **Cacher des éléments** : `css/admin.css` avec `display: none !important`. Pour les boutons TinyMCE, **sélecteurs stables uniquement** (`aria-label`, `.mce-i-bold`, `.mce-i-italic`) — jamais `#mceu_*` qui dépendent du nombre d'éditeurs sur la page
|
||||
- **Force du mode visuel** : filtre PHP `user_can_richedit` → `true` (sinon les users avec « désactiver l'éditeur visuel » dans leur profil sont bloqués en mode code, car nos CSS masquent les onglets Visual/Code). `ensureVisualMode(editorId)` (JS) retry jusqu'à 15× pour passer de `html-active` à `tmce-active`
|
||||
- **TinyMCE sur champs conditionnels** : un éditeur initialisé dans un élément `display:none` a son iframe cassée (vide, non interactive). `reinitEditor(editorId)` détruit et recrée l'instance quand le champ devient visible. Utilisé pour `body_en` (au premier clic onglet EN) et pour `reference_bibliographique` (via MutationObserver sur le `style` de la ligne Pods)
|
||||
- **Rename « Article » → « Annonce »** : `renameArticlesToAnnonces()` parcourt les nœuds texte des éléments connus (menu, h1, notices, submitbox, screen options, search) plus l'admin bar (côté PHP). Réplique de plusieurs casses
|
||||
|
||||
### Page d'édition de post
|
||||
|
||||
`initPostEditPage()` orchestre :
|
||||
|
||||
- Désactivation d'options de catégorie (`1`, `12`, `5`, `20`) dans le select Pods
|
||||
- Réordonnancement des metaboxes : `type-dannonce` en haut du `#side-sortables`, `affichage-sur-laccueil` et `thematique` en bas, `champs-contextuels` en haut du `#normal-sortables`, `documents-joints` juste après, `submitdiv` en bas
|
||||
- **Onglets FR/EN sur le corps** : barre de tabs injectée avant `#postdivrich`. La metabox `#pods-meta-body-en` est déplacée juste après l'éditeur natif (le déplacement DOM casse son iframe TinyMCE — réparé par `reinitEditor()` au premier clic EN). Phase 1 = DOM avant `t=100ms`, Phase 2 = wiring après
|
||||
- **Groupement des axes thématiques** : la liste des cases à cocher est regroupée par période (label = `annee_debut – annee_fin` ou « Axes antérieurs »), via `groupAxesCheckboxes()`. Données passées en `thalimAxesGroups` via `wp_localize_script`. Re-grouping déclenché si la ligne devient visible (MutationObserver)
|
||||
- **Visibilité conditionnelle des Pods boxes** : `updatePostboxVisibility()` cache une `.postbox` Pods si tous ses `<tr>` ont `display:none` (sauf `body-en` qui est piloté par les onglets). Re-déclenché au changement de catégorie
|
||||
- **Séparateur dans la grille Membres** : `<tr class="membres-grid-separator">` injecté en fin de tbody, affiché uniquement si une ligne `autre_*` est visible
|
||||
- **Popover date picker Gutenberg** : le composant Popover de Gutenberg se ferme sur outside-click via `focusout`, mais si le focus n'y entre jamais, il reste ouvert. `initDatePickerPopoverFix()` force le focus dès que `.components-popover__content` apparaît dans le DOM
|
||||
- **Info-popovers** (`INFO_TIPS` array) : boutons d'aide attachés à des sélecteurs spécifiques. Type `info` (cercle, neutre) ou `translate` (globe, vert) pour rappeler la convention `FR // EN`. Champ `page` (`post` | `user` | `taxonomy`) limite le scope
|
||||
|
||||
### Page de profil utilisateur
|
||||
|
||||
`initProfileEditors()` :
|
||||
|
||||
- Réordonne les sections natives WP en lignes deux-colonnes (`PROFILE_ORDER`)
|
||||
- Masque le `<h2>` « À propos du compte »
|
||||
- Renomme le label `Rôle` en `Rôle sur le site`
|
||||
- Force le mode visuel sur tous les WYSIWYG Pods (`load` du window)
|
||||
- `do_action('show_user_profile', new WP_User(0))` sur `user-new.php` pour que les champs Pods s'affichent à la création d'utilisateur. `user_register` rejoue `personal_options_update` pour sauver ces champs avec le nouvel ID
|
||||
- Auto-sync `display_name` ← `first_name + last_name` à chaque save (`profile_update`, `user_register`, et hook Pods)
|
||||
|
||||
### Pods modal (création de séance via popup)
|
||||
|
||||
Quand l'URL contient `pods_modal`, la catégorie est verrouillée sur `12` (séance de séminaire) et le select est désactivé pour les autres valeurs.
|
||||
|
||||
### Colonnes admin custom
|
||||
|
||||
- Taxonomy `programme_de_recherche` : remplace « Description » par « Type de programme » (term meta `type_de_programme`). Select de filtre injecté côté JS dans la search-form, filtré côté serveur via `pre_get_terms`
|
||||
- Taxonomy `post_tag` : remplace « Description » par « Exclure du nuage » (term meta `ne_pas_afficher_dans_le_nuage`)
|
||||
|
||||
### Autres tweaks admin
|
||||
|
||||
- Champ `etiquettes` (post_tag pick) : autocomplete déplafonnée (filtre `pods_form_ui_field_pick_autocomplete_limit` retourne `-1`)
|
||||
- Admin bar : retire `wp-logo`, `customize`, `wpforms-menu`, et rename « article » → « annonce » dans tous les nodes
|
||||
|
||||
## Search panel (`templates/partials/search-panel.twig`)
|
||||
|
||||
Réutilisée dans `search.twig`. L'icône loupe `iconoir-search` est un `<button type="submit" class="search-panel__icon-btn">` (positionné en absolute dans `.search-panel__input-wrap` qui est relative — styles dans `scss/_header.scss`).
|
||||
|
||||
## Rewrite rules
|
||||
|
||||
- `/category/{slug}/autres` → posts directement dans la catégorie racine (sans sous-catégorie). Query var `thalim_direct_posts=1`. Utilisé pour le lien depuis les `post-card` quand la catégorie est racine
|
||||
|
||||
## Conventions Pods
|
||||
|
||||
- **Membres / autre_membres** : champs `pick` (user) multi-valeur sur les posts. `membres` = membres Thalim, `autre_membres` = externes. Les helpers résolvent en `[{name, url}]`. Quand `membres` est vide, `autre_membres` est utilisé en fallback pour les cards
|
||||
- **Liens externes** : convention `lien_externe_1/2/3` + `titre_du_lien_externe_1/2/3` (bilingual). Fallback titre : nom de domaine
|
||||
- **Champs `type_*`** (`type_colloque_journee_d_etude`, `type_soutenance`, etc.) : un seul est rempli par post, le premier non-vide donne le `type_label`. Idem pour `fonction_*` et `autre_fonction_*`. Pour les posts anciens sans ces champs, fallback sur un mapping `_pods_categorie ID → label` codé en dur dans `single-helpers.php`
|
||||
- **Axes thématiques** (taxonomy `axe_thematique`) : term meta `annee_debut`, `annee_fin`, `ordre_daffichage`. Un axe est « courant » si `annee_fin >= année courante`. Affiché dans le menu de navigation (`axes_courants` dans le contexte)
|
||||
|
||||
## Workflow
|
||||
|
||||
- **NE PAS compiler le SCSS/CSS.** L'utilisateur le fait manuellement. Éditer uniquement les `.scss`
|
||||
- **CSS commité** dans le dépôt (`css/*.css`) pour que le site fonctionne après clone
|
||||
- **SQL direct OK** pour les tweaks ponctuels — phpMyAdmin sur http://localhost:8021
|
||||
62
README.md
Normal file
62
README.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# thalim-stack
|
||||
|
||||
Stack Docker pour le développement local du site WordPress du laboratoire **THALIM** (Théorie et Histoire des Arts et des Littératures de la Modernité).
|
||||
|
||||
Ce repo ne contient **que** la stack (docker-compose, env, script de bootstrap, documentation). Le thème et les plugins personnalisés vivent dans leurs propres repos et sont clonés dans `wp-data/wp-content/` par le script de bootstrap :
|
||||
|
||||
- [`thalim-theme`](https://figureslibres.io/valentin_le_moign/thalim-theme) — thème Timber/Twig personnalisé
|
||||
- [`thalim-plugin-hal-importer`](https://figureslibres.io/valentin_le_moign/thalim-plugin-hal-importer) — import des publications depuis l'archive ouverte HAL
|
||||
- [`thalim-plugin-newsletter`](https://figureslibres.io/valentin_le_moign/thalim-plugin-newsletter) — composition et export des newsletters mensuelles
|
||||
|
||||
## Prérequis
|
||||
|
||||
- Docker + Docker Compose
|
||||
- Git
|
||||
|
||||
## Démarrage rapide
|
||||
|
||||
```bash
|
||||
git clone gitea@figureslibres.io:valentin_le_moign/thalim-stack.git
|
||||
cd thalim-stack
|
||||
|
||||
# 1. Configurer les secrets
|
||||
cp .env.example .env
|
||||
# puis éditer .env
|
||||
|
||||
# 2. Lancer la stack (l'image WordPress peuple wp-data/ au premier démarrage)
|
||||
docker compose up -d
|
||||
|
||||
# 3. Cloner le thème + les plugins personnalisés
|
||||
./bootstrap.sh
|
||||
```
|
||||
|
||||
## Services
|
||||
|
||||
| Service | URL | Notes |
|
||||
| ----------- | ------------------------- | ---------------------------------- |
|
||||
| WordPress | http://localhost:8020 | |
|
||||
| phpMyAdmin | http://localhost:8021 | |
|
||||
| MySQL | `localhost:3307` | mappé sur `3306` dans le conteneur |
|
||||
|
||||
## Structure du dépôt
|
||||
|
||||
```
|
||||
.
|
||||
├── docker-compose.yml # stack (mysql 5.7, wordpress:latest, phpmyadmin)
|
||||
├── .env.example # gabarit des secrets — copier en .env
|
||||
├── bootstrap.sh # clone le thème et les plugins dans wp-data/
|
||||
├── CLAUDE.md # documentation architecture (lue par Claude Code)
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Sauvegarde / restauration
|
||||
|
||||
Les données MySQL sont dans un volume Docker nommé (`db_data`) — pas dans le repo. Pour sauvegarder :
|
||||
|
||||
```bash
|
||||
docker compose exec db mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" > backup.sql
|
||||
```
|
||||
|
||||
## Voir aussi
|
||||
|
||||
Voir `CLAUDE.md` pour l'architecture détaillée du thème et des plugins.
|
||||
50
bootstrap.sh
Executable file
50
bootstrap.sh
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
# bootstrap.sh — clone le thème et les plugins personnalisés THALIM
|
||||
# dans l'arborescence WordPress montée par docker compose.
|
||||
#
|
||||
# Pré-requis :
|
||||
# - `docker compose up -d` a déjà été lancé au moins une fois (pour que
|
||||
# wp-data/ soit peuplé par l'image WordPress)
|
||||
# - tu as accès au Gitea interne
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GITEA_BASE="${GITEA_BASE:-gitea@figureslibres.io:valentin_le_moign}"
|
||||
THEME_REPO="${GITEA_BASE}/thalim-theme.git"
|
||||
HAL_REPO="${GITEA_BASE}/thalim-plugin-hal-importer.git"
|
||||
NL_REPO="${GITEA_BASE}/thalim-plugin-newsletter.git"
|
||||
|
||||
THEMES_DIR="wp-data/wp-content/themes"
|
||||
PLUGINS_DIR="wp-data/wp-content/plugins"
|
||||
|
||||
if [[ ! -d "wp-data/wp-content" ]]; then
|
||||
echo "✘ wp-data/wp-content/ introuvable."
|
||||
echo " Lance d'abord : docker compose up -d (puis attends quelques secondes)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
clone_if_missing() {
|
||||
local repo="$1"
|
||||
local dest="$2"
|
||||
local name
|
||||
name="$(basename "$dest")"
|
||||
|
||||
if [[ -d "$dest/.git" ]]; then
|
||||
echo "→ $name déjà cloné, je passe."
|
||||
elif [[ -d "$dest" ]]; then
|
||||
echo "⚠ $dest existe mais n'est pas un repo git — renommé en ${dest}.bak"
|
||||
mv "$dest" "${dest}.bak"
|
||||
git clone "$repo" "$dest"
|
||||
else
|
||||
git clone "$repo" "$dest"
|
||||
fi
|
||||
}
|
||||
|
||||
clone_if_missing "$THEME_REPO" "$THEMES_DIR/thalim"
|
||||
clone_if_missing "$HAL_REPO" "$PLUGINS_DIR/thalim-hal-importer"
|
||||
clone_if_missing "$NL_REPO" "$PLUGINS_DIR/thalim-newsletter"
|
||||
|
||||
echo
|
||||
echo "✔ Clonage terminé."
|
||||
echo " Pense à installer les dépendances Composer du thème :"
|
||||
echo " (cd $THEMES_DIR/thalim && composer install)"
|
||||
55
docker-compose.yml
Normal file
55
docker-compose.yml
Normal file
@@ -0,0 +1,55 @@
|
||||
services:
|
||||
db:
|
||||
image: mysql:5.7
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
ports:
|
||||
- "3307:3306"
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
|
||||
MYSQL_DATABASE: ${MYSQL_DATABASE}
|
||||
MYSQL_USER: ${MYSQL_USER}
|
||||
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
||||
networks:
|
||||
- wpdb
|
||||
|
||||
wordpress:
|
||||
container_name: wordpress
|
||||
depends_on:
|
||||
- db
|
||||
image: wordpress:latest
|
||||
ports:
|
||||
- "8020:80"
|
||||
environment:
|
||||
WORDPRESS_DB_HOST: db:3306
|
||||
WORDPRESS_DB_USER: ${MYSQL_USER}
|
||||
WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
|
||||
WORDPRESS_DEBUG: 1
|
||||
volumes:
|
||||
- ./wp-data:/var/www/html
|
||||
networks:
|
||||
- web
|
||||
- wpdb
|
||||
|
||||
phpmyadmin:
|
||||
container_name: phpmyadmin
|
||||
image: phpmyadmin:latest
|
||||
depends_on:
|
||||
- db
|
||||
ports:
|
||||
- "8021:80"
|
||||
environment:
|
||||
PMA_HOST: db
|
||||
PMA_PORT: 3306
|
||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
|
||||
networks:
|
||||
- web
|
||||
- wpdb
|
||||
|
||||
volumes:
|
||||
db_data: {}
|
||||
|
||||
networks:
|
||||
web:
|
||||
external: true
|
||||
wpdb:
|
||||
Reference in New Issue
Block a user