Initial commit

This commit is contained in:
2026-05-12 23:32:48 +02:00
commit 7784ed070a
6 changed files with 408 additions and 0 deletions

7
.env.example Normal file
View 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
View 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
View 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 (13), 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
View 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
View 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
View 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: