# PARTIE IV — Pour le développeur intégrateur

# CHAPITRE 23 — Le catalogue produit dynamique en profondeur

<span style="white-space: pre-wrap;">Le catalogue produit dynamique transforme votre table </span>`<span class="editor-theme-code">llx_product</span>`<span style="white-space: pre-wrap;"> en pages web générées automatiquement. Ce chapitre détaille l'ensemble du fonctionnement côté développeur : architecture, classes, configuration et points d'extension.</span>

### <span style="color: rgb(35, 111, 161);">Architecture en couches</span>

<table id="bkmrk-coucher%C3%B4lestudioprod" style="width: 100%; border-collapse: collapse; margin: 1rem 0px; font-size: 0.95em;"><colgroup><col></col><col></col></colgroup><tbody><tr style="background: rgb(25, 5, 45); color: rgb(254, 252, 232);"><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Couche

</th><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Rôle

</th></tr><tr><td style="padding: 0.6rem 1rem; border: 1px solid rgb(229, 231, 235);">**StudioProductCatalog**

</td><td style="padding: 0.6rem 1rem; border: 1px solid rgb(229, 231, 235);">Lit les produits Dolibarr (filtres, tris, multilingue) et expose un format normalisé.

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.6rem 1rem; border: 1px solid rgb(229, 231, 235);">**StudioSolutionWrapper**

</td><td style="padding: 0.6rem 1rem; border: 1px solid rgb(229, 231, 235);"><span style="white-space: pre-wrap;">Génère et supprime les wrappers Apache </span>

`<span class="editor-theme-code">solution-<ref>.php</span>`

<span style="white-space: pre-wrap;"> en fonction des produits publiés.</span>

</td></tr><tr><td style="padding: 0.6rem 1rem; border: 1px solid rgb(229, 231, 235);">**Le gabarit solution-detail (page&lt;X&gt;.tpl.php)**

</td><td style="padding: 0.6rem 1rem; border: 1px solid rgb(229, 231, 235);">Le gabarit unique qui affiche un produit. Une seule page Dolibarr Website pour N produits.

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.6rem 1rem; border: 1px solid rgb(229, 231, 235);">**Trigger PRODUCT et CATEGORY**

</td><td style="padding: 0.6rem 1rem; border: 1px solid rgb(229, 231, 235);">Régénère automatiquement les wrappers à chaque modification de produit ou de catégorie.

</td></tr><tr><td style="padding: 0.6rem 1rem; border: 1px solid rgb(229, 231, 235);">**Tâche planifiée horaire**

</td><td style="padding: 0.6rem 1rem; border: 1px solid rgb(229, 231, 235);">Filet de sécurité : relance la génération si un trigger a été manqué.

</td></tr></tbody></table>

### <span style="color: rgb(35, 111, 161);">Le modèle de données produit</span>

Chaque produit Dolibarr utilisé dans le catalogue exploite plusieurs tables :

<table id="bkmrk-tabledonn%C3%A9esllx_prod" style="width: 100%; border-collapse: collapse; margin: 1rem 0px; font-size: 0.95em;"><colgroup><col></col><col></col></colgroup><tbody><tr style="background: rgb(25, 5, 45); color: rgb(254, 252, 232);"><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Table

</th><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Données

</th></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">llx_product</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Champs natifs : ref, label, description, prix, tosell.

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">llx_product_lang</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Traductions natives (label, description par langue).

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">llx_product_extrafields</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Champs personnalisés canoniques : tagline, hero\_image, badge, cta\_label, cta\_url, deployment, compatibility, support, languages, features (JSON), pricing\_tiers (JSON), infrasstudio\_published.

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">llx_infrasstudio_product_translation</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Surcharges par langue des champs personnalisés traduisibles.

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">llx_categorie_product</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Liens entre produits et catégories (utilisés pour la cartographie d'univers).

</td></tr></tbody></table>

### <span style="color: rgb(35, 111, 161);">Provisionner les champs personnalisés</span>

Les onze champs personnalisés nécessaires ne sont pas livrés en standard avec Dolibarr. Pour les créer en une commande, utilisez le script de provisionnement générique :

```
php htdocs/custom/infrasstudio/scripts/preset_default.php \
    htdocs/custom/infrasstudio/presets/keaticweb.json
```

Le fichier JSON livre les définitions par défaut (voir Chapitre 28 pour le format). Vous pouvez créer votre propre preset si vous avez besoin d'autres champs personnalisés.

### <span style="color: rgb(35, 111, 161);">Utiliser StudioProductCatalog</span>

La classe expose plusieurs méthodes pour interroger les produits :

```
dol_include_once('/infrasstudio/class/studioproductcatalog.class.php');
$catalog = new StudioProductCatalog($db);

// Tous les produits publiés, dans la langue courante
$products = $catalog->fetchPublishedProducts(array(), $iso2);

// Filtres
$products = $catalog->fetchPublishedProducts(array(
    'univers' => 'supply-chain',
    'type'    => 'saas',
), $iso2);

// Un produit par référence
$product = $catalog->fetchProductBySlug('supplyflow-pro', $iso2);

// Univers utilisés (pour le filtre du catalogue)
$univers = $catalog->fetchUsedUnivers();
```

##### <span style="color: rgb(22, 145, 121);">Format normalisé d'un produit</span>

```
array(
    'id'           => 5,
    'ref'          => 'supplyflow-pro',
    'label'        => 'SupplyFlow Pro',                    // résolu selon la langue
    'description'  => '<p>Description...</p>',
    'price'        => 290.00,
    'badge'        => 'Nouveau',
    'hero_image'   => '/medias/...',
    'cta_label'    => 'Demander une démo',
    'cta_url'      => '/contact',
    'tagline'      => 'Optimisez votre supply chain...',
    'deployment'   => 'Cloud SaaS',
    'compatibility'=> 'SAP, Oracle, Sage',
    'support'      => '24/7',
    'languages'    => 'fr,en,de',
    'features'     => array('Prévision IA', 'Multi-entrepôts'),
    'pricing_tiers'=> array(...),
    'univers'      => 'supply-chain',
    'type'         => 'saas',
)
```

### <span style="color: rgb(35, 111, 161);">Cartographie catégorie vers univers</span>

Les produits sont rattachés à des catégories Dolibarr. Le module associe chaque catégorie à un univers (concept éditorial : Supply Chain, Health, Legal, etc.).

##### **Cartographie par défaut**

```
// Les six univers livrés par défaut
'supply-chain'        => array(...catégories supply chain),
'health'              => array(...catégories santé),
'legal'               => array(...catégories juridique),
'digital-humanities'  => array(...catégories sciences humaines),
'transversal'         => array(...catégories outils transversaux),
'fsm'                 => array(...catégories field service)
```

##### **Surcharge par JSON**

Pour personnaliser, définissez la constante :

```
// Via dolibarr_set_const ou via la page de configuration admin
$conf->global->INFRASSTUDIO_PRODUCT_UNIVERS_MAP = json_encode(array(
    'mon-univers' => array(12, 13, 14),  // identifiants de catégories
    'autre'       => array(15, 16),
));
```

### <span style="color: rgb(35, 111, 161);">StudioSolutionWrapper</span>

Cette classe gère la génération des wrappers Apache à partir des produits publiés.

##### **Génération manuelle**

```
dol_include_once('/infrasstudio/class/studiosolutionwrapper.class.php');
require_once DOL_DOCUMENT_ROOT . '/website/class/website.class.php';

$website = new Website($db);
$website->fetch(0, 'monsite');

$wrapper = new StudioSolutionWrapper($db);
$stats = $wrapper->rebuildAll($website, '/var/www/monsite');
print_r($stats);
// array('created' => 3, 'updated' => 2, 'deleted' => 1, 'skipped' => 5)
```

##### **Anatomie d'un wrapper généré**

```
<?php
// /var/www/monsite/solution-supplyflow-pro.php
// Generated by StudioSolutionWrapper — do not edit manually.
$solution_ref = 'supplyflow-pro';
$infrasstudio_current_product_ref = 'supplyflow-pro';
global $dolibarr_main_data_root, $conf;
if (empty($dolibarr_main_data_root)) {
    $res = include './page42.tpl.php';   // page solution-detail
} else {
    $res = include $dolibarr_main_data_root
        . ($conf->entity > 1 ? '/' . $conf->entity : '')
        . '/website/monsite/page42.tpl.php';
}
if ($res === false) { http_response_code(500); print 'Failed to make include'; }
```

<p class="callout success">**Sécurité anti-collision —**<span style="white-space: pre-wrap;"> Le module ne touche qu'aux wrappers qu'il a lui-même créés (présence du marqueur « StudioSolutionWrapper » dans l'en-tête du fichier). Les pages Dolibarr standards portant un slug commençant par </span>`<span class="editor-theme-code">solution-</span>`<span style="white-space: pre-wrap;"> sont préservées.</span></p>

### <span style="color: rgb(35, 111, 161);">Configuration du générateur de wrappers</span>

<table id="bkmrk-constanter%C3%B4leinfrass" style="width: 100%; border-collapse: collapse; margin: 1rem 0px; font-size: 0.95em;"><colgroup><col></col><col></col></colgroup><tbody><tr style="background: rgb(25, 5, 45); color: rgb(254, 252, 232);"><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Constante

</th><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Rôle

</th></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">INFRASSTUDIO_WEBSITE_KEY</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Référence du site cible.

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">INFRASSTUDIO_PUBLIC_DOCROOT</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);"><span style="white-space: pre-wrap;">Docroot Apache absolu (par exemple </span>

`<span class="editor-theme-code">/var/www/monsite</span>`

).

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">INFRASSTUDIO_SITE_<id>_WRAPPER_PREFIX</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);"><span style="white-space: pre-wrap;">Préfixe des wrappers (par défaut </span>

`<span class="editor-theme-code">solution-</span>`

<span style="white-space: pre-wrap;">). Exemples : </span>

`<span class="editor-theme-code">produit-</span>`

<span style="white-space: pre-wrap;">, </span>

`<span class="editor-theme-code">service-</span>`

.

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">INFRASSTUDIO_SITE_<id>_WRAPPER_TEMPLATE_PAGEURL</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);"><span style="white-space: pre-wrap;">Slug du gabarit solution-detail (par défaut </span>

`<span class="editor-theme-code">solution-detail</span>`

).

</td></tr></tbody></table>

### <span style="color: rgb(35, 111, 161);">Le gabarit solution-detail</span>

<span style="white-space: pre-wrap;">Il s'agit d'une page Dolibarr Website classique (avec son slug et son tpl.php), mais utilisée par tous les wrappers. Elle reçoit la variable globale </span>`<span class="editor-theme-code">$solution_ref</span>`<span style="white-space: pre-wrap;"> qui identifie le produit à afficher.</span>

##### **Squelette type**

```
<?php
// page42.tpl.php (slug='solution-detail', type_container='page')
require_once __DIR__ . '/master.inc.php';
require_once DOL_DOCUMENT_ROOT . '/core/lib/website.lib.php';
require_once DOL_DOCUMENT_ROOT . '/core/website.inc.php';

dol_include_once('/infrasstudio/class/studioproductcatalog.class.php');
$catalog = new StudioProductCatalog($db);
$iso2 = infrasstudio_current_lang();
$product = $catalog->fetchProductBySlug($solution_ref, $iso2);
if (!$product) {
    http_response_code(404);
    print '<h1>Produit introuvable</h1>';
    exit;
}

ob_start();
try {
?>
<html lang="<?php echo $iso2; ?>">
<head>
    <title><?php echo dol_escape_htmltag($product['label']); ?></title>
</head>
<body>
    <section class="hero">
        <h1><?php echo dol_escape_htmltag($product['label']); ?></h1>
        <p><?php echo dol_escape_htmltag($product['tagline']); ?></p>
        <img src="<?php echo $product['hero_image']; ?>" alt="...">
    </section>
    <section class="features">
        <ul>
        <?php foreach ($product['features'] as $feature): ?>
            <li><?php echo dol_escape_htmltag($feature); ?></li>
        <?php endforeach; ?>
        </ul>
    </section>
</body>
</html>
<?php
} catch (Exception $e) { print $e->getMessage(); }
include dol_buildpath('/infrasstudio/core/tpl/website_output.tpl.php', 0);
```

**Mélanger slots et données produit —**<span style="white-space: pre-wrap;"> Vous pouvez combiner les deux : les zones éditoriales (par exemple un titre marketing au-dessus de la grille) en slots, et les zones produit (label, prix, fonctionnalités) lues via </span>`<span class="editor-theme-code">StudioProductCatalog</span>`. Les deux cohabitent sans conflit.

### <span style="color: rgb(35, 111, 161);">Le trigger PRODUCT et CATEGORY</span>

Le module installe un trigger qui écoute :

- `<span class="editor-theme-code">PRODUCT_CREATE</span>`
- `<span class="editor-theme-code">PRODUCT_MODIFY</span>`
- `<span class="editor-theme-code">PRODUCT_DELETE</span>`
- `<span class="editor-theme-code">PRODUCT_PRICE_MODIFY</span>`
- `<span class="editor-theme-code">CATEGORY_LINK</span>`<span style="white-space: pre-wrap;"> (uniquement si l'objet lié est un produit)</span>
- `<span class="editor-theme-code">CATEGORY_UNLINK</span>`

<span style="white-space: pre-wrap;">À chaque événement, le trigger appelle </span>`<span class="editor-theme-code">StudioSolutionWrapper::rebuildAll()</span>`<span style="white-space: pre-wrap;"> sur le site configuré.</span>

<p class="callout info">**Note —**<span style="white-space: pre-wrap;"> Si </span>`<span class="editor-theme-code">INFRASSTUDIO_WEBSITE_KEY</span>`<span style="white-space: pre-wrap;"> ou </span>`<span class="editor-theme-code">INFRASSTUDIO_PUBLIC_DOCROOT</span>`<span style="white-space: pre-wrap;"> ne sont pas configurées, le trigger se termine silencieusement. Ni bruit, ni erreur. Cette discrétion convient aux instances Dolibarr qui n'utilisent pas le catalogue dynamique.</span></p>

### <span style="color: rgb(35, 111, 161);">La tâche planifiée horaire (filet de sécurité)</span>

<span style="white-space: pre-wrap;">Une tâche planifiée s'exécute toutes les heures et appelle </span>`<span class="editor-theme-code">StudioSolutionWrapper::rebuildAllConfigured()</span>`. Elle lit les constantes et relance la régénération.

Cette tâche apporte les bénéfices suivants :

- Rattrapage si un trigger a été manqué (import en masse, modification SQL directe, etc.).
- Synchronisation après une migration de serveur.
- Garantie de cohérence sans intervention manuelle.

### <span style="color: rgb(35, 111, 161);">Reconstruction manuelle en ligne de commande</span>

```
php htdocs/custom/infrasstudio/scripts/rebuild_solution_wrappers.php \
    <ref-site-ou-id> <docroot-public> [entity]

# Exemple
php htdocs/custom/infrasstudio/scripts/rebuild_solution_wrappers.php \
    monsite /var/www/monsite 2
```

### <span style="color: rgb(35, 111, 161);">Récapitulatif</span>

**Vous savez désormais :**

- Comprendre l'architecture du catalogue (StudioProductCatalog, StudioSolutionWrapper, gabarit, trigger, tâche planifiée).
- Provisionner les champs personnalisés produit avec le preset générique.
- <span style="white-space: pre-wrap;">Interroger les produits via </span>`<span class="editor-theme-code">StudioProductCatalog</span>`.
- <span style="white-space: pre-wrap;">Cartographier les catégories Dolibarr vers vos univers via </span>`<span class="editor-theme-code">INFRASSTUDIO_PRODUCT_UNIVERS_MAP</span>`.
- <span style="white-space: pre-wrap;">Comprendre comment </span>`<span class="editor-theme-code">StudioSolutionWrapper::rebuildAll()</span>`<span style="white-space: pre-wrap;"> génère les wrappers Apache.</span>
- Configurer le préfixe et le slug du gabarit par site.
- <span style="white-space: pre-wrap;">Écrire le gabarit </span>`<span class="editor-theme-code">solution-detail</span>`<span style="white-space: pre-wrap;"> qui sert tous les produits.</span>
- Comprendre le rôle du trigger automatique et de la tâche planifiée de sécurité.
- Lancer une reconstruction manuelle en ligne de commande.

**Fin de la Partie IV —**<span style="white-space: pre-wrap;"> Vous maîtrisez désormais l'intégration côté développeur : préparer un site, annoter avec des slots, comprendre la grammaire, brancher des données Dolibarr, gérer le multilingue, créer vos gabarits et exploiter le catalogue dynamique. Vous êtes en mesure de livrer un site clé en main.</span>

La Partie V aborde l'administration et la maintenance. Si vous êtes administrateur Dolibarr, c'est votre prochaine destination.

</body></html>

# CHAPITRE 22 — Créer ses propres gabarits de page

Lorsque votre client clique sur « + Nouvelle page » dans le Studio, il choisit un gabarit de départ. Ce chapitre vous explique comment créer vos propres gabarits adaptés à la charte de votre site.

### <span style="color: rgb(35, 111, 161);">À quoi sert un gabarit</span>

Un gabarit est un squelette de page qui comporte :

- Du HTML structuré (sections, classes CSS, balises sémantiques).
- Des slots pré-déclarés pour les zones éditables.
- Des valeurs par défaut, pour disposer d'une page complète dès la création.
- Des métadonnées (titre par défaut, slug, type de conteneur).

Lorsqu'un client crée une page depuis un gabarit, le module :

1. <span style="white-space: pre-wrap;">Crée une entrée dans la table </span>`<span class="editor-theme-code">llx_website_page</span>`.
2. <span style="white-space: pre-wrap;">Génère un fichier </span>`<span class="editor-theme-code">page<N>.tpl.php</span>`<span style="white-space: pre-wrap;"> à partir du squelette.</span>
3. <span style="white-space: pre-wrap;">Crée le wrapper Apache </span>`<span class="editor-theme-code"><slug>.php</span>`.
4. Lance un rescan des slots pour les détecter immédiatement.

### <span style="color: rgb(35, 111, 161);">Structure d'un gabarit</span>

<span style="white-space: pre-wrap;">Un gabarit est un dossier dans </span>`<span class="editor-theme-code">htdocs/custom/infrasstudio/templates/</span>`<span style="white-space: pre-wrap;"> :</span>

```
templates/mon-gabarit/
├── meta.php             # Métadonnées du gabarit
└── skeleton.tpl.php     # Le squelette HTML avec slots
```

### <span style="color: rgb(35, 111, 161);">Le fichier meta.php</span>

Il retourne un tableau de configuration :

```
<?php
return array(
    'code'           => 'mon-gabarit',                    // identifiant unique
    'label'          => 'Mon Gabarit',                    // affiché dans l'assistant
    'category'       => 'page',                           // page | landing | blog
    'description'    => "Description courte affichée sous le titre.",
    'icon'           => 'fa-file-lines',                  // icône FontAwesome
    'type_container' => 'page',                           // type Dolibarr Website
    'default_slug'   => 'nouvelle-page',                  // slug suggéré
);
```

<table id="bkmrk-champdescriptioncode" style="width: 100%; border-collapse: collapse; margin: 1rem 0px; font-size: 0.95em;"><colgroup><col></col><col></col></colgroup><tbody><tr style="background: rgb(25, 5, 45); color: rgb(254, 252, 232);"><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Champ

</th><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Description

</th></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">code</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Identifiant unique. Doit correspondre au nom du dossier.

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">label</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Texte affiché dans la grille de sélection de l'assistant.

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">category</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Permet le regroupement visuel (page, landing, blog).

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">icon</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Classe FontAwesome de l'icône de la tuile.

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">type_container</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);"><span style="white-space: pre-wrap;">Valeur écrite dans </span>

`<span class="editor-theme-code">llx_website_page.type_container</span>`

<span style="white-space: pre-wrap;">. Valeurs standards : </span>

`<span class="editor-theme-code">page</span>`

<span style="white-space: pre-wrap;">, </span>

`<span class="editor-theme-code">blogpost</span>`

<span style="white-space: pre-wrap;">, </span>

`<span class="editor-theme-code">other</span>`

<span style="white-space: pre-wrap;">, </span>

`<span class="editor-theme-code">menu</span>`

.

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">default_slug</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Slug suggéré dans le formulaire. Le client peut le modifier.

</td></tr></tbody></table>

### <span style="color: rgb(35, 111, 161);">Le fichier skeleton.tpl.php</span>

Il s'agit du squelette HTML, identique à un fichier tpl.php standard, à la différence qu'il contient des marqueurs qui seront remplacés au moment de la création.

##### **Marqueurs disponibles**

<table id="bkmrk-marqueurremplac%C3%A9-par" style="width: 100%; border-collapse: collapse; margin: 1rem 0px; font-size: 0.95em;"><colgroup><col></col><col></col></colgroup><tbody><tr style="background: rgb(25, 5, 45); color: rgb(254, 252, 232);"><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Marqueur

</th><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Remplacé par

</th></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">@@PAGEID@@</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">L'identifiant Dolibarr de la nouvelle page (par exemple 42).

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">@@PAGEURL@@</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);"><span style="white-space: pre-wrap;">Le slug URL final (par exemple </span>

`<span class="editor-theme-code">about-keatic</span>`

).

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">@@ISO2@@</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Le code ISO2 de la langue principale (

`<span class="editor-theme-code">fr</span>`

<span style="white-space: pre-wrap;">, </span>

`<span class="editor-theme-code">en</span>`

).

</td></tr></tbody></table>

### <span style="color: rgb(35, 111, 161);">Exemple complet — gabarit page-libre</span>

##### **meta.php**

```
<?php
return array(
    'code'           => 'page-libre',
    'label'          => 'Page libre',
    'category'       => 'page',
    'description'    => 'Une page simple avec un titre et un grand champ de texte riche.',
    'icon'           => 'fa-file-lines',
    'type_container' => 'page',
    'default_slug'   => 'nouvelle-page',
);
```

##### **skeleton.tpl.php**

```
<?php // BEGIN PHP
$websitekey = basename(__DIR__);
if (! defined('USEDOLIBARRSERVER') && ! defined('USEDOLIBARREDITOR')) {
    require_once __DIR__ . '/master.inc.php';
}
require_once DOL_DOCUMENT_ROOT . '/core/lib/website.lib.php';
require_once DOL_DOCUMENT_ROOT . '/core/website.inc.php';
ob_start();
try {
// END PHP ?>
<html lang="@@ISO2@@">
<head>
    <title>{{slot:page_title|type=text|default=Nouvelle page|label=Titre SEO|group=seo}}</title>
    <meta name="description" content="{{slot:page_meta_description|type=text|default=|label=Meta description|group=seo}}" />
    <link rel="canonical" href="<?php echo $website->virtualhost; ?>/@@PAGEURL@@.php" />
</head>
<body>
    <?php includeContainer('header'); ?>
    <main class="container">
        <h1>{{slot:page_h1|type=text|default=Titre principal|label=H1|group=hero}}</h1>
        <div class="content">
            {{slot:page_body|type=richtext|default=<p>Contenu de la page.</p>|label=Contenu}}
        </div>
    </main>
    <?php includeContainer('footer'); ?>
</body>
</html>
<?php // BEGIN PHP
} catch (Exception $e) { print $e->getMessage(); }
include dol_buildpath('/infrasstudio/core/tpl/website_output.tpl.php', 0);
// END PHP ?>
```

**Le bloc final —**<span style="white-space: pre-wrap;"> N'oubliez pas la ligne </span>`<span class="editor-theme-code">include dol_buildpath('/infrasstudio/core/tpl/website_output.tpl.php', 0);</span>`. C'est elle qui déclenche la résolution des slots et shortcodes au moment du rendu.

### <span style="color: rgb(35, 111, 161);">Localiser vos gabarits hors du module</span>

Si vous souhaitez livrer des gabarits avec votre projet client sans modifier le module, définissez la constante :

```
// htdocs/conf/conf.php ou via dolibarr_set_const
$conf->global->INFRASSTUDIO_TEMPLATE_EXTRA_DIR = '/var/www/monsite/templates';
```

<span style="white-space: pre-wrap;">Le module scanne ce répertoire en complément de </span>`<span class="editor-theme-code">htdocs/custom/infrasstudio/templates/</span>`.

### <span style="color: rgb(35, 111, 161);">Gabarits livrés par défaut</span>

<table id="bkmrk-codedescriptionpage-" style="width: 100%; border-collapse: collapse; margin: 1rem 0px; font-size: 0.95em;"><colgroup><col></col><col></col></colgroup><tbody><tr style="background: rgb(25, 5, 45); color: rgb(254, 252, 232);"><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Code

</th><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Description

</th></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">page-free</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Page libre avec titre et un grand champ texte riche. Pour les pages ponctuelles.

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">blog-standard</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Article de blog générique avec hero, introduction, corps et appel à l'action.

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">example-blog</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Article de blog au design moderne (hero CSS, accroche en italique, image secondaire, articles liés). Adaptable à votre charte.

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">example-landing</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Page de destination produit complète (environ 70 slots). Hero, problème, solution, fonctionnalités, contact, FAQ.

</td></tr></tbody></table>

<p class="callout info">**Conseil —**<span style="white-space: pre-wrap;"> Inspirez-vous de </span>`<span class="editor-theme-code">example-landing</span>`<span style="white-space: pre-wrap;"> pour comprendre comment structurer un gabarit complexe avec environ 70 slots organisés en sections.</span></p>

### <span style="color: rgb(35, 111, 161);">Bonnes pratiques pour vos gabarits</span>

- <span style="white-space: pre-wrap;">Préfixez tous les slots du gabarit par un identifiant commun (par exemple </span>`<span class="editor-theme-code">landing_</span>`) pour éviter les collisions entre gabarits.
- <span style="white-space: pre-wrap;">Regroupez les slots avec </span>`<span class="editor-theme-code">group=</span>`<span style="white-space: pre-wrap;"> par section logique (hero, fonctionnalités, contact, etc.).</span>
- Donnez des valeurs par défaut représentatives. Le rédacteur dispose ainsi d'un exemple à modifier plutôt que d'une page vide intimidante.
- Incluez les slots SEO (`<span class="editor-theme-code">page_title</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">page_meta_description</span>`) dans tout gabarit de type page.
- Incluez les balises Open Graph dans l'en-tête HTML pour le partage social.
- Incluez le helper hreflang si le site est multilingue.
- Testez le gabarit en créant une page réelle depuis l'assistant et vérifiez le rendu public.

### <span style="color: rgb(35, 111, 161);">Récapitulatif</span>

**Vous savez désormais :**

- Comprendre l'utilité d'un gabarit (squelette, slots, valeurs par défaut).
- <span style="white-space: pre-wrap;">Créer un dossier </span>`<span class="editor-theme-code">templates//</span>`<span style="white-space: pre-wrap;"> avec </span>`<span class="editor-theme-code">meta.php</span>`<span style="white-space: pre-wrap;"> et </span>`<span class="editor-theme-code">skeleton.tpl.php</span>`.
- Renseigner les métadonnées (code, label, category, icon, type\_container, default\_slug).
- <span style="white-space: pre-wrap;">Utiliser les marqueurs </span>`<span class="editor-theme-code">@@PAGEID@@</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">@@PAGEURL@@</span>`<span style="white-space: pre-wrap;"> et </span>`<span class="editor-theme-code">@@ISO2@@</span>`.
- <span style="white-space: pre-wrap;">Localiser vos gabarits en dehors du module via </span>`<span class="editor-theme-code">INFRASSTUDIO_TEMPLATE_EXTRA_DIR</span>`.
- Suivre les bonnes pratiques (préfixe, regroupement, valeurs par défaut, SEO, multilingue).

Le dernier chapitre de la Partie IV détaille le catalogue produit dynamique en profondeur.

</body></html>

# CHAPITRE 21 — Gérer le multilingue (pages sœurs)

Dolibarr Website propose deux modèles multilingues différents. Identifier le modèle que vous utilisez est une étape préalable à toute écriture de code. Ce chapitre vous présente les deux options et la manière dont le module s'y intègre.

### <span style="color: rgb(35, 111, 161);">Les deux modèles multilingues</span>

<table id="bkmrk-mod%C3%A8lecaract%C3%A9ristiqu" style="width: 100%; border-collapse: collapse; margin: 1rem 0px; font-size: 0.95em;"><colgroup><col></col><col></col></colgroup><tbody><tr style="background: rgb(25, 5, 45); color: rgb(254, 252, 232);"><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Modèle

</th><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Caractéristique

</th></tr><tr><td style="padding: 0.6rem 1rem; border: 1px solid rgb(229, 231, 235);">**A. Slot par langue**

<span style="white-space: pre-wrap;"> (recommandé)</span>

</td><td style="padding: 0.6rem 1rem; border: 1px solid rgb(229, 231, 235);">Un seul fichier tpl.php sert toutes les langues. Les slots disposent de surcharges par langue.

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.6rem 1rem; border: 1px solid rgb(229, 231, 235);">**B. Pages sœurs**

<span style="white-space: pre-wrap;"> (legacy)</span>

</td><td style="padding: 0.6rem 1rem; border: 1px solid rgb(229, 231, 235);">Un fichier tpl.php par langue (

`<span class="editor-theme-code">about.php</span>`

<span style="white-space: pre-wrap;">, </span>

`<span class="editor-theme-code">about-en.php</span>`

<span style="white-space: pre-wrap;">, </span>

`<span class="editor-theme-code">about-de.php</span>`

). Modèle hérité des sites Dolibarr Website classiques.

</td></tr></tbody></table>

<p class="callout info">**Recommandé —**<span style="white-space: pre-wrap;"> Adoptez le modèle A pour tout nouveau site. Le modèle B reste pris en charge pour la migration progressive de sites existants. Le module fournit un outil de consolidation B vers A.</span></p>

### <span style="color: rgb(35, 111, 161);">Modèle A — Slot par langue (le standard moderne)</span>

##### **Fonctionnement**

<span style="white-space: pre-wrap;">Une seule page Dolibarr Website existe par concept (par exemple </span>`<span class="editor-theme-code">about</span>`<span style="white-space: pre-wrap;">). Le fichier tpl.php est unique. Les slots possèdent leur valeur canonique (FR par défaut) et des surcharges par langue stockées dans </span>`<span class="editor-theme-code">llx_infrasstudio_slot</span>`.

```
<!-- about.tpl.php — UN SEUL fichier pour FR/EN/DE/ES/IT/PT/NL/PL -->
<h1>{{slot:about_title|type=text|default=À propos de nous|label=Titre About}}</h1>
<p>{{slot:about_lead|type=textarea|default=Notre histoire en quelques mots.|label=Accroche}}</p>
```

##### **Récupérer la langue du visiteur dans le gabarit**

Le module résout les slots automatiquement selon la langue détectée. Pour vos propres traitements PHP (afficher la date dans la bonne langue, choisir une image différente par langue), récupérez la langue via :

```
<?php
// helpers fournis par le module
$iso2 = infrasstudio_current_lang();         // 'fr', 'en', 'de', ...
$locale = infrasstudio_current_locale();     // 'fr_FR', 'en_US', 'de_DE', ...
?>
```

##### **Cascade de détection**

<span style="white-space: pre-wrap;">La fonction </span>`<span class="editor-theme-code">infrasstudio_current_lang()</span>`<span style="white-space: pre-wrap;"> détecte la langue dans cet ordre :</span>

1. <span style="white-space: pre-wrap;">Constante </span>`<span class="editor-theme-code">INFRASSTUDIO_LANG_ISO</span>`<span style="white-space: pre-wrap;"> définie par le site (souvent dans </span>`<span class="editor-theme-code">lang.inc.php</span>`).
2. <span style="white-space: pre-wrap;">Paramètre URL </span>`<span class="editor-theme-code">?lang=xx</span>`.
3. Suffixe sur le slug (`<span class="editor-theme-code">about-en.php</span>`<span style="white-space: pre-wrap;"> donne en).</span>
4. Cookie de persistance (`<span class="editor-theme-code">INFRASSTUDIO_LANG_COOKIE</span>`).
5. <span style="white-space: pre-wrap;">Valeur de </span>`<span class="editor-theme-code">$langs->defaultlang</span>`.
6. <span style="white-space: pre-wrap;">En-tête HTTP </span>`<span class="editor-theme-code">Accept-Language</span>`<span style="white-space: pre-wrap;"> du visiteur.</span>

### <span style="color: rgb(35, 111, 161);">Modèle B — Pages sœurs (legacy)</span>

Il s'agit du modèle Dolibarr Website classique : pour chaque page, vous créez un fichier tpl.php par langue.

```
about.php           # FR (canonique)
about-en.php        # EN
about-de.php        # DE
about-es.php        # ES
...
```

Si vous reprenez un site existant qui suit ce modèle, deux options s'offrent à vous :

1. Conserver le modèle avec des stubs du module.
2. Migrer vers le modèle A grâce à l'outil de consolidation en ligne de commande.

### <span style="color: rgb(35, 111, 161);">Conserver le modèle B avec des stubs</span>

<span style="white-space: pre-wrap;">Le module fournit un helper </span>`<span class="editor-theme-code">sister_stub.tpl.php</span>`<span style="white-space: pre-wrap;"> qui réduit chaque page sœur à trois lignes et permet de partager le HTML avec la canonique.</span>

##### **Procédure**

La page canonique reste un tpl.php classique :

```
// page5.tpl.php — canonique FR
<?php // bootstrap dolibarr ... ?>
<html>...<h1>{{slot:about_title|type=text|default=À propos|...}}</h1>...</html>
```

Chaque page sœur devient un stub :

```
<?php
// page42.tpl.php — sister EN
$_infrasstudio_sister_canonical = 'page5.tpl.php';
$_infrasstudio_sister_lang      = 'en';
include dol_buildpath('/infrasstudio/core/tpl/sister_stub.tpl.php', 0);
```

<span style="white-space: pre-wrap;">Le stub force </span>`<span class="editor-theme-code">$_GET['lang'] = 'en'</span>`<span style="white-space: pre-wrap;"> puis délègue le rendu à la canonique. Les surcharges EN sont automatiquement utilisées pour résoudre les slots.</span>

### <span style="color: rgb(35, 111, 161);">Migrer du modèle B vers A en ligne de commande</span>

Un script consolide une famille de pages sœurs en une page canonique et plusieurs stubs :

```
php htdocs/custom/infrasstudio/scripts/consolidate_sister_pages.php <ref-site> \
    [--entity=N] \
    [--base-slug=about] \
    [--dry-run] \
    [--extractor=/path/to/extractor.php]
```

##### **Ce que fait le script**

1. Détecte les groupes de pages sœurs parmi les codes ISO2 pris en charge (en, de, es, it, pt, nl, pl).
2. Pour chaque groupe :
    - Conserve la page tpl FR comme canonique.
    - Extrait les valeurs traduites depuis chaque page sœur (par expression régulière ou via un extracteur personnalisé).
    - <span style="white-space: pre-wrap;">Insère les surcharges dans </span>`<span class="editor-theme-code">llx_infrasstudio_slot</span>`.
    - <span style="white-space: pre-wrap;">Réécrit la canonique avec des slots </span>`<span class="editor-theme-code">{{slot:...}}</span>`.
    - Remplace chaque page sœur par un stub de trois lignes.
3. <span style="white-space: pre-wrap;">Sauvegarde de chaque tpl original avec l'extension </span>`<span class="editor-theme-code">.bak</span>`.

<p class="callout info">**Conseil —**<span style="white-space: pre-wrap;"> Lancez d'abord avec </span>`<span class="editor-theme-code">--dry-run</span>`<span style="white-space: pre-wrap;"> pour visualiser ce que le script va faire sans rien écrire. Lancez en mode réel uniquement après vérification.</span></p>

### <span style="color: rgb(35, 111, 161);">hreflang : déclarer les alternates au navigateur</span>

<span style="white-space: pre-wrap;">Pour que Google sache que vos pages sont des traductions les unes des autres, vous devez émettre des balises </span>`<span class="editor-theme-code"><link rel="alternate" hreflang="..."></span>`<span style="white-space: pre-wrap;"> dans l'en-tête HTML.</span>

Le module fournit un helper qui les génère automatiquement :

```
<head>
    ...
    <?php echo infrasstudio_hreflang_tags($website, $WEBSITE_PAGE); ?>
    ...
</head>
```

Sortie type :

```
<link rel="alternate" hreflang="fr" href="https://exemple.com/about.php" />
<link rel="alternate" hreflang="en" href="https://exemple.com/about-en.php" />
<link rel="alternate" hreflang="de" href="https://exemple.com/about-de.php" />
<link rel="alternate" hreflang="x-default" href="https://exemple.com/about.php" />
```

<p class="callout info">**Compatibilité avec les deux modèles —**<span style="white-space: pre-wrap;"> Pour le modèle A, le helper émet une seule URL canonique avec les langues différenciées par </span>`<span class="editor-theme-code">?lang=</span>`. Pour le modèle B, il émet une URL par page sœur.</p>

### <span style="color: rgb(35, 111, 161);">Sélecteur de langue côté gabarit</span>

<span style="white-space: pre-wrap;">Pour permettre au visiteur de changer de langue, vous devez exposer un sélecteur. Le helper </span>`<span class="editor-theme-code">infrasstudio_translated_url($iso2)</span>`<span style="white-space: pre-wrap;"> génère l'URL équivalente de la page courante dans une autre langue :</span>

```
<nav class="lang-switcher">
    <?php foreach (array('fr', 'en', 'de', 'es') as $iso): ?>
        <a href="<?php echo infrasstudio_translated_url($iso); ?>">
            <?php echo strtoupper($iso); ?>
        </a>
    <?php endforeach; ?>
</nav>
```

### <span style="color: rgb(35, 111, 161);">Charger les fichiers de langue du site</span>

<span style="white-space: pre-wrap;">Si votre site utilise des fichiers </span>`<span class="editor-theme-code">.lang</span>`<span style="white-space: pre-wrap;"> Dolibarr (par exemple pour les libellés de menu), chargez-les en début de tpl :</span>

```
<?php
require_once __DIR__ . '/lang.inc.php';
$langs->loadLangs(array('website_main', 'website_blog'));
?>
```

<span style="white-space: pre-wrap;">Et utilisez les clés via </span>`<span class="editor-theme-code">$langs->trans('Key')</span>`<span style="white-space: pre-wrap;"> ou via le slot avec </span>`<span class="editor-theme-code">default=@lang:Key</span>`.

### <span style="color: rgb(35, 111, 161);">Récapitulatif</span>

**Vous savez désormais :**

- Distinguer le modèle A (slot par langue, recommandé) du modèle B (pages sœurs, legacy).
- <span style="white-space: pre-wrap;">Récupérer la langue du visiteur via </span>`<span class="editor-theme-code">infrasstudio_current_lang()</span>`.
- <span style="white-space: pre-wrap;">Convertir un site existant utilisant le modèle B en utilisant le helper </span>`<span class="editor-theme-code">sister_stub.tpl.php</span>`.
- <span style="white-space: pre-wrap;">Migrer en bloc B vers A grâce à l'outil </span>`<span class="editor-theme-code">consolidate_sister_pages.php</span>`.
- <span style="white-space: pre-wrap;">Émettre les balises hreflang automatiquement avec </span>`<span class="editor-theme-code">infrasstudio_hreflang_tags</span>`.
- <span style="white-space: pre-wrap;">Construire un sélecteur de langue avec </span>`<span class="editor-theme-code">infrasstudio_translated_url</span>`.

</body></html>

# CHAPITRE 20 — Insérer des données Dolibarr (shortcodes)

<span style="white-space: pre-wrap;">Les slots stockent du contenu éditable. Les </span>**shortcodes**, eux, affichent des données Dolibarr lues en direct (produits, catégories, informations société, médias). Ils sont résolus au moment du rendu, à chaque consultation de page.

### <span style="color: rgb(35, 111, 161);">Distinction entre slot et shortcode</span>

<table id="bkmrk-slotshortcodestocke-" style="width: 100%; border-collapse: collapse; margin: 1rem 0px; font-size: 0.95em;"><colgroup><col></col><col></col></colgroup><tbody><tr style="background: rgb(25, 5, 45); color: rgb(254, 252, 232);"><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Slot

</th><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Shortcode

</th></tr><tr><td style="padding: 0.6rem 1rem; border: 1px solid rgb(229, 231, 235);"><span style="white-space: pre-wrap;">Stocke du </span>

**contenu éditable**

<span style="white-space: pre-wrap;"> par le client.</span>

</td><td style="padding: 0.6rem 1rem; border: 1px solid rgb(229, 231, 235);"><span style="white-space: pre-wrap;">Affiche des </span>

**données Dolibarr**

<span style="white-space: pre-wrap;"> lues en direct.</span>

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.6rem 1rem; border: 1px solid rgb(229, 231, 235);"><span style="white-space: pre-wrap;">Persistance dans </span>

`<span class="editor-theme-code">llx_infrasstudio_slot</span>`

.

</td><td style="padding: 0.6rem 1rem; border: 1px solid rgb(229, 231, 235);">Aucune persistance — résolu à chaque requête.

</td></tr><tr><td style="padding: 0.6rem 1rem; border: 1px solid rgb(229, 231, 235);"><span style="white-space: pre-wrap;">Exemple : </span>

`<span class="editor-theme-code">{{slot:hero_title|type=text}}</span>`

</td><td style="padding: 0.6rem 1rem; border: 1px solid rgb(229, 231, 235);"><span style="white-space: pre-wrap;">Exemple : </span>

`<span class="editor-theme-code">{{product:ref=supplyflow.label}}</span>`

</td></tr></tbody></table>

### <span style="color: rgb(35, 111, 161);">Syntaxe générale</span>

```
{{<namespace>:<sélecteur>.<champ>}}
```

- **namespace**<span style="white-space: pre-wrap;"> : la source de données (</span>`<span class="editor-theme-code">product</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">category</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">dict</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">mysoc</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">extrafield</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">media</span>`).
- **sélecteur**<span style="white-space: pre-wrap;"> : la manière d'identifier l'élément (le plus souvent </span>`<span class="editor-theme-code">ref=xxx</span>`<span style="white-space: pre-wrap;"> ou </span>`<span class="editor-theme-code">id=N</span>`).
- **champ**<span style="white-space: pre-wrap;"> : la donnée à extraire de l'élément.</span>

### <span style="color: rgb(35, 111, 161);">Namespace product</span>

<span style="white-space: pre-wrap;">Lit un produit dans la table </span>`<span class="editor-theme-code">llx_product</span>`.

```
<h2>{{product:ref=supplyflow-pro.label}}</h2>
<p>{{product:ref=supplyflow-pro.description}}</p>
<span class="price">{{product:ref=supplyflow-pro.price}} €</span>
<span>{{product:ref=supplyflow-pro.ef_tagline}}</span>
<img src="{{product:ref=supplyflow-pro.ef_hero_image}}" alt="...">
```

##### **Champs disponibles**

<table id="bkmrk-champsourcelabel%2C-de" style="width: 100%; border-collapse: collapse; margin: 1rem 0px; font-size: 0.95em;"><colgroup><col></col><col></col></colgroup><tbody><tr style="background: rgb(25, 5, 45); color: rgb(254, 252, 232);"><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Champ

</th><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Source

</th></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">label</span>`

<span style="white-space: pre-wrap;">, </span>

`<span class="editor-theme-code">description</span>`

<span style="white-space: pre-wrap;">, </span>

`<span class="editor-theme-code">note</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);"><span style="white-space: pre-wrap;">Champs natifs (traduits selon la langue du visiteur via </span>

`<span class="editor-theme-code">llx_product_lang</span>`

)

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">price</span>`

<span style="white-space: pre-wrap;">, </span>

`<span class="editor-theme-code">price_ttc</span>`

<span style="white-space: pre-wrap;">, </span>

`<span class="editor-theme-code">cost_price</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Prix HT, TTC, coût (formaté selon la langue)

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">ref</span>`

<span style="white-space: pre-wrap;">, </span>

`<span class="editor-theme-code">tosell</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Référence, statut commercialisable

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">ef_<slug></span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Tout champ personnalisé (

`<span class="editor-theme-code">ef_tagline</span>`

<span style="white-space: pre-wrap;">, </span>

`<span class="editor-theme-code">ef_hero_image</span>`

, etc.)

</td></tr></tbody></table>

##### **Le marqueur $current**

<span style="white-space: pre-wrap;">Pour un même gabarit utilisé par plusieurs produits (par exemple </span>`<span class="editor-theme-code">solution-detail</span>`<span style="white-space: pre-wrap;"> servi par les wrappers </span>`<span class="editor-theme-code">solution-<ref>.php</span>`<span style="white-space: pre-wrap;">), utilisez </span>`<span class="editor-theme-code">ref=$current</span>`<span style="white-space: pre-wrap;"> :</span>

```
<h1>{{product:ref=$current.label}}</h1>
<p>{{product:ref=$current.ef_tagline}}</p>
```

<span style="white-space: pre-wrap;">Le module résout </span>`<span class="editor-theme-code">$current</span>`<span style="white-space: pre-wrap;"> via la variable globale </span>`<span class="editor-theme-code">$infrasstudio_current_product_ref</span>`, posée par le wrapper.

### <span style="color: rgb(35, 111, 161);">Namespace category</span>

<span style="white-space: pre-wrap;">Lit une catégorie dans la table </span>`<span class="editor-theme-code">llx_categorie</span>`.

```
<h3>{{category:id=5.label}}</h3>
<p>{{category:id=5.description}}</p>
```

### <span style="color: rgb(35, 111, 161);">Namespace dict</span>

Lit une entrée d'un dictionnaire Dolibarr (`<span class="editor-theme-code">c_country</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">c_currencies</span>`, etc.).

```
<span>{{dict:c_country.code=FR.label}}</span>
<span>{{dict:c_currencies.code_iso=EUR.label}}</span>
```

### <span style="color: rgb(35, 111, 161);">Namespace mysoc</span>

Lit les informations de la société courante (`<span class="editor-theme-code">$mysoc</span>`).

```
<footer>
    © {{mysoc.name}} — {{mysoc.address}}, {{mysoc.zip}} {{mysoc.town}}
    Tél : {{mysoc.phone}} — Courriel : {{mysoc.email}}
    SIRET {{mysoc.idprof2}} — TVA {{mysoc.tva_intra}}
</footer>
```

##### **Champs disponibles**

`<span class="editor-theme-code">name</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">address</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">zip</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">town</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">country_code</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">phone</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">fax</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">email</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">url</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">capital</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">tva_intra</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">idprof1</span>`<span style="white-space: pre-wrap;"> à </span>`<span class="editor-theme-code">idprof6</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">logo</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">logo_small</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">logo_squarred</span>`.

### <span style="color: rgb(35, 111, 161);">Namespace extrafield</span>

Lit un champ personnalisé d'un objet Dolibarr quelconque.

```
{{extrafield:table=product|ref=supplyflow.field=tagline}}
{{extrafield:table=societe|id=42|field=segment}}
{{extrafield:table=product|ref=$current|field=tagline}}
```

<span style="white-space: pre-wrap;">Plus générique que </span>`<span class="editor-theme-code">{{product:ref=X.ef_tagline}}</span>`<span style="white-space: pre-wrap;"> mais plus verbeux. À utiliser lorsque le namespace dédié n'existe pas (par exemple pour </span>`<span class="editor-theme-code">societe</span>`<span style="white-space: pre-wrap;"> ou </span>`<span class="editor-theme-code">contact</span>`).

### <span style="color: rgb(35, 111, 161);">Namespace media</span>

Lit un média de la bibliothèque du module (`<span class="editor-theme-code">llx_infrasstudio_media</span>`).

```
<img src="{{media:ref=hero-1.url}}" alt="{{media:ref=hero-1.alt}}">
<img src="{{media:ref=hero-1.thumb}}">
<img src="{{media:ref=hero-1.card}}">
<img src="{{media:ref=hero-1.wide}}">
```

##### **Champs disponibles**

<table id="bkmrk-champdescriptionurlu" style="width: 100%; border-collapse: collapse; margin: 1rem 0px; font-size: 0.95em;"><colgroup><col></col><col></col></colgroup><tbody><tr style="background: rgb(25, 5, 45); color: rgb(254, 252, 232);"><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Champ

</th><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Description

</th></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">url</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">URL du fichier original

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">thumb</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Variante 200 × 200

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">card</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Variante 640 × 480

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">wide</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Variante 1600 × 1200

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">alt</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Texte alternatif (résolu selon la langue)

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">label</span>`

<span style="white-space: pre-wrap;">, </span>

`<span class="editor-theme-code">width</span>`

<span style="white-space: pre-wrap;">, </span>

`<span class="editor-theme-code">height</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Métadonnées

</td></tr></tbody></table>

### <span style="color: rgb(35, 111, 161);">Combiner shortcodes et slots</span>

Vous pouvez intégrer un shortcode dans la valeur par défaut d'un slot, ou dans le contenu d'un slot de type texte riche :

```
<!-- Shortcode dans le default d'un slot -->
<p>{{slot:greeting|type=text|default=Bienvenue chez {{mysoc.name}}}}</p>

<!-- Shortcode dans un slot richtext (l'éditeur peut le saisir librement) -->
<div>{{slot:long_intro|type=richtext|default=Notre équipe à {{mysoc.town}} vous accompagne.}}</div>
```

<p class="callout info">**Cas d'usage —**<span style="white-space: pre-wrap;"> Une page de remerciements après commande qui dit « Merci, votre commande sera livrée à... ». Le HTML reste statique côté gabarit, mais le rendu est personnalisé pour chaque visiteur.</span></p>

### <span style="color: rgb(35, 111, 161);">Étendre avec un namespace personnalisé</span>

<span style="white-space: pre-wrap;">Pour ajouter un namespace propre à votre projet, déposez un fichier dans </span>`<span class="editor-theme-code">htdocs/custom/infrasstudio/shortcodes/</span>`<span style="white-space: pre-wrap;"> :</span>

```
// shortcodes/myorg.shortcode.php
function infrasstudio_shortcode_myorg_resolve($args, $context)
{
    global $db;
    $id    = isset($args['id']) ? (int) $args['id'] : 0;
    $field = isset($args['_field']) ? $args['_field'] : 'name';
    if ($id <= 0) { return ''; }

    $sql = "SELECT name, sector FROM ".MAIN_DB_PREFIX."myorg WHERE rowid = ".$id;
    $resql = $db->query($sql);
    if (!$resql) { return ''; }
    $obj = $db->fetch_object($resql);
    $db->free($resql);
    return isset($obj->$field) ? (string) $obj->$field : '';
}
```

Utilisable dans n'importe quel gabarit :

```
<h2>{{myorg:id=12.name}}</h2>
<p>Secteur : {{myorg:id=12.sector}}</p>
```

### <span style="color: rgb(35, 111, 161);">Pièges et performance</span>

**Shortcode dans une boucle —**<span style="white-space: pre-wrap;"> Si vous résolvez cent fois le même shortcode dans une page, vous effectuez cent requêtes SQL. Mettez vos données en cache dans une variable locale avant la boucle, ou utilisez les fonctions natives Dolibarr (</span>`<span class="editor-theme-code">Product::fetch</span>`) en PHP.

**Shortcode introuvable —**<span style="white-space: pre-wrap;"> Si </span>`<span class="editor-theme-code">{{product:ref=inexistant.label}}</span>`<span style="white-space: pre-wrap;"> ne résout rien, le module retourne une chaîne vide silencieusement. Aucune erreur n'est affichée. Testez en local avant la livraison.</span>

**Shortcodes dans les attributs —**<span style="white-space: pre-wrap;"> Les shortcodes fonctionnent dans tous les contextes HTML, attributs compris : </span>`<span class="editor-theme-code"><img src="{{media:ref=X.url}}"></span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code"><a href="{{slot:cta_url|...}}"></span>`, etc.

### <span style="color: rgb(35, 111, 161);">Récapitulatif</span>

**Vous savez désormais :**

- Distinguer slots (contenu éditable) et shortcodes (données Dolibarr).
- Utiliser les six namespaces livrés (product, category, dict, mysoc, extrafield, media).
- <span style="white-space: pre-wrap;">Utiliser </span>`<span class="editor-theme-code">$current</span>`<span style="white-space: pre-wrap;"> dans les gabarits partagés entre plusieurs produits.</span>
- Combiner shortcodes et slots.
- <span style="white-space: pre-wrap;">Ajouter votre propre namespace via un fichier dans </span>`<span class="editor-theme-code">shortcodes/</span>`.
- Anticiper les pièges (boucles, valeurs absentes).

# CHAPITRE 19 — La grammaire des slots en détail

Référence exhaustive de tous les types de slot et de leurs attributs. Gardez ce chapitre à portée de main lors de l'annotation d'un template.

### <span style="color: rgb(35, 111, 161);">Format général</span>

```
{{slot:<name>|type=<type>|default=<value>|label=<text>|group=<section>|help=<text>|maxlength=<int>|options=<csv>}}
```

<span style="white-space: pre-wrap;">Tous les attributs, à l'exception de </span>`<span class="editor-theme-code">name</span>`<span style="white-space: pre-wrap;"> et </span>`<span class="editor-theme-code">type</span>`, sont facultatifs. L'ordre est libre. Le séparateur est le caractère pipe.

### <span style="color: rgb(35, 111, 161);">Règles syntaxiques</span>

<table id="bkmrk-r%C3%A8gled%C3%A9tailidentifia" style="width: 100%; border-collapse: collapse; margin: 1rem 0px; font-size: 0.95em;"><colgroup><col></col><col></col></colgroup><tbody><tr style="background: rgb(25, 5, 45); color: rgb(254, 252, 232);"><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Règle

</th><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Détail

</th></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">**Identifiant**

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">[a-z0-9_]+</span>`

, 64 caractères maximum. Pas de tirets, pas de majuscules.

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">**Unicité**

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Unique par page. Deux slots avec le même nom sur la même page produisent un avertissement au scan.

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">**Pas d'espaces**

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);"><span style="white-space: pre-wrap;">Aucun espace de part et d'autre du signe </span>

`<span class="editor-theme-code">=</span>`

. Aucun espace dans les noms d'attributs.

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">**Échappement**

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);"><span style="white-space: pre-wrap;">Le pipe à l'intérieur d'une valeur n'est pas pris en charge. Évitez-le dans les valeurs </span>

`<span class="editor-theme-code">default</span>`

.

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">**Une seule ligne**

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Un token de slot tient sur une seule ligne. Aucun saut de ligne ne doit y être présent.

</td></tr></tbody></table>

### <span style="color: rgb(35, 111, 161);">Les dix types de slot</span>

##### **Type text — Texte court**

Champ d'une seule ligne, sans mise en forme.

```
<h1>{{slot:hero_title|type=text|default=Bienvenue|label=Titre principal|maxlength=80}}</h1>
```

- Interface : champ de saisie simple.
- Idéal pour : titres, libellés, boutons d'appel à l'action, copyright.
- <span style="white-space: pre-wrap;">Compteur de caractères affiché si </span>`<span class="editor-theme-code">maxlength</span>`<span style="white-space: pre-wrap;"> est défini.</span>

##### **Type textarea — Texte long brut**

Multi-lignes, sans mise en forme HTML.

```
<p class="lead">{{slot:hero_lead|type=textarea|default=Une accroche.|label=Accroche|maxlength=300}}</p>
```

- Interface : zone de texte multi-lignes.
- <span style="white-space: pre-wrap;">Les retours à la ligne sont automatiquement convertis en balises </span>`<span class="editor-theme-code"><br></span>`<span style="white-space: pre-wrap;"> au rendu.</span>
- Idéal pour : accroches, paragraphes simples.

##### **Type richtext — Texte riche WYSIWYG**

Mise en forme complète via CKEditor.

```
<div class="post-body">
    {{slot:post_body|type=richtext|default=<p>Contenu de l'article.</p>|label=Corps de l'article}}
</div>
```

- Interface : éditeur CKEditor (Format, gras, italique, listes, liens, couleurs, alignement, etc.).
- Idéal pour : corps d'articles, descriptions longues, conditions générales.
- La valeur stockée contient du HTML (`<span class="editor-theme-code"><p></span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code"><strong></span>`, etc.).

##### **Type image — Média image**

```
<img src="{{slot:hero_image|type=image|default=/medias/hero.jpg|label=Image hero}}" alt="...">
```

- Interface : champ texte, bouton de sélection dans la bibliothèque, bouton de suppression et vignette d'aperçu.
- <span style="white-space: pre-wrap;">Stocke soit une URL absolue, soit un shortcode </span>`<span class="editor-theme-code">{{media:ref=X.url}}</span>`.
- Non traduisible par défaut : la même image est servie dans toutes les langues.

##### **Type url — Lien**

```
<a href="{{slot:hero_cta_url|type=url|default=/contact|label=URL du bouton}}">...</a>
```

- Interface : champ texte avec validation URL légère.
- Idéal pour : URL de bouton, liens vers les réseaux sociaux, URL de partage.

##### **Type icon — Icône FontAwesome**

```
{{slot:feature_1_icon|type=icon|default=fa-solid fa-rocket|label=Icône feature 1}}
```

- Interface : champ classe, sélecteur de couleur, vingt icônes proposées en accès rapide, aperçu en direct.
- <span style="white-space: pre-wrap;">Stockage en JSON : </span>`<span class="editor-theme-code">{"class":"fa-solid fa-star","color":"#fbbf24"}</span>`.
- <span style="white-space: pre-wrap;">Rendu : </span>`<span class="editor-theme-code"><i class="fa-solid fa-star" style="color:#fbbf24"></i></span>`.
- Sécurisé : liste blanche sur la classe (a-zA-Z0-9\_-) et expression régulière hexadécimale sur la couleur.

##### **Type color — Couleur**

```
<section style="background-color:{{slot:section_bg|type=color|default=#19052d|label=Couleur de fond}}">
```

- Interface : sélecteur de couleur HTML5, champ hexadécimal, bouton de retour à la valeur par défaut.
- <span style="white-space: pre-wrap;">Format strict : </span>`<span class="editor-theme-code">#RRGGBB</span>`<span style="white-space: pre-wrap;"> ou </span>`<span class="editor-theme-code">#RRGGBBAA</span>`<span style="white-space: pre-wrap;"> (avec transparence).</span>
- Non traduisible.

##### **Type number — Nombre**

```
<span class="stat">{{slot:stats_clients|type=number|default=42|label=Nombre de clients}}</span>
```

##### **Type bool — Booléen**

```
<?php if ('{{slot:hero_show_badge|type=bool|default=1|label=Afficher le badge}}'): ?>
    <span class="badge">Nouveau</span>
<?php endif; ?>
```

- Interface : case à cocher.
- Valeur : 0 ou 1.

##### **Type select — Liste déroulante**

```
<section class="hero hero-{{slot:hero_layout|type=select|options=light,dark,gradient|default=light|label=Style du hero}}">
```

- Interface : menu déroulant.
- <span style="white-space: pre-wrap;">Les valeurs disponibles sont définies dans </span>`<span class="editor-theme-code">options</span>`<span style="white-space: pre-wrap;"> (CSV obligatoire).</span>

### <span style="color: rgb(35, 111, 161);">Valeurs par défaut spéciales</span>

##### **default=@lang:KEY — Résolution via les fichiers de langue**

<span style="white-space: pre-wrap;">Lorsque votre site utilise déjà des fichiers </span>`<span class="editor-theme-code">.lang</span>`<span style="white-space: pre-wrap;"> Dolibarr (cas typique pour les sites multilingues existants), vous pouvez réutiliser les clés au lieu de les dupliquer dans les slots :</span>

```
<h1>{{slot:hero_title|type=text|default=@lang:HeroTitle|label=Titre du hero}}</h1>
```

<span style="white-space: pre-wrap;">Au moment du rendu, le module résout via </span>`<span class="editor-theme-code">$langs->trans('HeroTitle')</span>`<span style="white-space: pre-wrap;"> selon la langue du visiteur. Les fichiers </span>`<span class="editor-theme-code">.lang</span>`<span style="white-space: pre-wrap;"> du site présents dans </span>`<span class="editor-theme-code">DOL_DATA_ROOT/<entity>/website/<ref>/langs/</span>`<span style="white-space: pre-wrap;"> sont chargés automatiquement.</span>

<p class="callout info">**Cas d'usage —**<span style="white-space: pre-wrap;"> Migration progressive d'un site existant : vous conservez vos fichiers </span>`<span class="editor-theme-code">.lang</span>`<span style="white-space: pre-wrap;"> en l'état, vous ajoutez les slots, et le client peut soit éditer dans le Studio (surcharge), soit conserver la traduction du </span>`<span class="editor-theme-code">.lang</span>`<span style="white-space: pre-wrap;"> (canonique).</span></p>

### <span style="color: rgb(35, 111, 161);">Récapitulatif des types et de leur traduisibilité</span>

<table id="bkmrk-typeinterfacetraduis" style="width: 100%; border-collapse: collapse; margin: 1rem 0px; font-size: 0.95em;"><colgroup><col></col><col></col><col></col></colgroup><tbody><tr style="background: rgb(25, 5, 45); color: rgb(254, 252, 232);"><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Type

</th><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Interface

</th><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Traduisible

</th></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">text</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Champ de saisie

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Oui

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">textarea</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Zone de texte

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Oui

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">richtext</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">CKEditor

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Oui

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">image</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Sélecteur de média

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Non

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">url</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Champ de saisie

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Non

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">icon</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Classe et sélecteur de couleur

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Non

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">color</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Sélecteur de couleur

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Non

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">number</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Champ numérique

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Oui (rare)

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">bool</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Case à cocher

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Non

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">select</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Menu déroulant

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Non

</td></tr></tbody></table>

### <span style="color: rgb(35, 111, 161);">Pièges fréquents</span>

**Slots dans des attributs JSON intégrés —**<span style="white-space: pre-wrap;"> Si vous avez du JSON intégré dans une balise (par exemple </span>`<span class="editor-theme-code">data-config="{...}"</span>`), n'y placez pas de slot. Les doubles accolades sont aussi un délimiteur Mustache et risquent de casser les bibliothèques JavaScript qui parseraient ce JSON.

**Identifiant trop long —**<span style="white-space: pre-wrap;"> La limite est de 64 caractères. Au-delà, le scanner rejette silencieusement, ce que met en évidence l'option </span>`<span class="editor-theme-code">--lint</span>`.

**Espaces autour des = —**<span style="white-space: pre-wrap;"> Le scanner rejette </span>`<span class="editor-theme-code">type = text</span>`<span style="white-space: pre-wrap;">. Utilisez toujours </span>`<span class="editor-theme-code">type=text</span>`.

**Caractère pipe dans une valeur —**<span style="white-space: pre-wrap;"> </span>`<span class="editor-theme-code">default=A|B</span>`<span style="white-space: pre-wrap;"> casse l'analyseur. Utilisez une autre séparation, ou laissez </span>`<span class="editor-theme-code">default</span>`<span style="white-space: pre-wrap;"> vide et saisissez la valeur via le Studio.</span>

### <span style="color: rgb(35, 111, 161);">Récapitulatif</span>

**Vous savez désormais :**

- La syntaxe complète d'un token de slot.
- Les dix types disponibles et leurs interfaces respectives.
- <span style="white-space: pre-wrap;">La résolution </span>`<span class="editor-theme-code">@lang:KEY</span>`<span style="white-space: pre-wrap;"> pour réutiliser les fichiers .lang Dolibarr.</span>
- Quels types sont traduisibles ou non.
- Les pièges syntaxiques fréquents.

Le chapitre suivant aborde les shortcodes, qui injectent des données Dolibarr dans le HTML.

# CHAPITRE 18 — Annoter un template avec des slots

Le slot est l'unité de base du module. Ce chapitre vous montre comment transformer un HTML statique en HTML annoté, prêt à être édité par le client. Il s'agit de l'opération la plus fréquente dans votre travail d'intégrateur.

### <span style="color: rgb(35, 111, 161);">Le principe en deux phrases</span>

1. Vous écrivez votre HTML normalement, comme vous le feriez sans le module.
2. <span style="white-space: pre-wrap;">Aux endroits que vous voulez rendre éditables, vous remplacez le contenu en dur par un token </span>`<span class="editor-theme-code">{{slot:...}}</span>`.

C'est tout. Le module se charge du reste : détection automatique, persistance, rendu, interface d'édition.

### <span style="color: rgb(35, 111, 161);">Premier exemple complet</span>

##### **Avant — page non éditable**

```
<section class="hero">
    <h1>Bienvenue chez Keatic</h1>
    <p>Votre partenaire numérique de confiance depuis 2010.</p>
    <a href="/contact" class="btn">Nous contacter</a>
</section>
```

##### **Après — page éditable via le Studio**

```
<section class="hero">
    <h1>{{slot:hero_title|type=text|default=Bienvenue chez Keatic|label=Titre du hero|group=hero}}</h1>
    <p>{{slot:hero_lead|type=textarea|default=Votre partenaire numérique de confiance depuis 2010.|label=Accroche|group=hero}}</p>
    <a href="{{slot:hero_cta_url|type=url|default=/contact|label=URL du bouton|group=hero}}" class="btn">
        {{slot:hero_cta_label|type=text|default=Nous contacter|label=Libellé du bouton|group=hero}}
    </a>
</section>
```

<p class="callout success">**Résultat —**<span style="white-space: pre-wrap;"> Le client voit dans le Studio un panneau « Hero » comportant quatre champs (titre, accroche, URL du bouton, libellé du bouton). Aucun HTML à modifier de son côté.</span></p>

### <span style="color: rgb(35, 111, 161);">Anatomie d'un token de slot</span>

```
{{slot:<identifiant>|type=<type>|attribut1=valeur1|attribut2=valeur2}}
```

<table id="bkmrk-%C3%89l%C3%A9mentobligatoirede" style="width: 100%; border-collapse: collapse; margin: 1rem 0px; font-size: 0.95em;"><colgroup><col></col><col></col><col></col></colgroup><tbody><tr style="background: rgb(25, 5, 45); color: rgb(254, 252, 232);"><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Élément

</th><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Obligatoire

</th><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Description

</th></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">{{slot:</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Oui

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Préfixe fixe qui déclare un slot.

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Identifiant

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Oui

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Unique par page. Caractères acceptés : minuscules, chiffres, tiret bas. 64 caractères maximum.

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">type=</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Oui

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">text, textarea, richtext, image, url, number, select, bool, icon, color.

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">default=</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Recommandé

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Valeur de repli si le slot n'est pas encore édité.

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">label=</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Recommandé

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Libellé affiché à l'éditeur dans le Studio.

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">group=</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Facultatif

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Regroupe les slots dans une section de l'interface.

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">help=</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Facultatif

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Aide affichée sous le champ.

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">maxlength=</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Facultatif

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Limite de caractères pour text et textarea.

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">options=</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Si type=select

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">Liste CSV des valeurs possibles.

</td></tr></tbody></table>

**Conseil —**<span style="white-space: pre-wrap;"> La grammaire complète est détaillée au Chapitre 19.</span>

### <span style="color: rgb(35, 111, 161);">Où placer un slot</span>

Un slot peut être placé à de nombreux endroits dans votre HTML.

##### **Dans le contenu d'une balise (cas le plus fréquent)**

```
<h1>{{slot:title|type=text|default=Bienvenue}}</h1>
```

##### **Dans un attribut HTML**

```
<img src="{{slot:hero_image|type=image|default=/medias/hero.jpg}}" alt="...">
<a href="{{slot:cta_url|type=url|default=/contact}}">...</a>
<section style="background-color:{{slot:bg_color|type=color|default=#19052d}}">
```

##### **Dans une chaîne de classes CSS**

```
<i class="fa-solid {{slot:hero_icon|type=text|default=fa-rocket}}"></i>
```

<p class="callout warning">**À ne pas faire —**<span style="white-space: pre-wrap;"> Ne placez pas un slot dans un commentaire HTML, dans un bloc </span>`<span class="editor-theme-code"><script></span>`, ou dans du JSON intégré. Le scanner détecte tous les tokens et peut générer des slots fantômes.</p>

### <span style="color: rgb(35, 111, 161);">Convention de nommage des identifiants</span>

L'identifiant est libre, mais une convention claire facilite la maintenance.

##### **Schéma recommandé**

`<span class="editor-theme-code"><section>_<champ></span>`<span style="white-space: pre-wrap;"> ou </span>`<span class="editor-theme-code"><page>_<section>_<champ></span>`

<table id="bkmrk-bon%C3%80-%C3%A9viterhero_titl" style="width: 100%; border-collapse: collapse; margin: 1rem 0px; font-size: 0.95em;"><colgroup><col></col><col></col></colgroup><tbody><tr style="background: rgb(25, 5, 45); color: rgb(254, 252, 232);"><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">Bon

</th><th class="align-left" style="padding: 0.6rem 1rem; text-align: left; border: 1px solid rgb(25, 5, 45);">À éviter

</th></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">hero_title</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">title</span>`

<span style="white-space: pre-wrap;"> (trop générique, risque de collision)</span>

</td></tr><tr style="background: rgb(250, 245, 255);"><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">features_grid_card_3_label</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">truc3</span>`

<span style="white-space: pre-wrap;"> (incompréhensible)</span>

</td></tr><tr><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">footer_copyright</span>`

</td><td style="padding: 0.5rem 1rem; border: 1px solid rgb(229, 231, 235);">`<span class="editor-theme-code">FooterCopyright</span>`

<span style="white-space: pre-wrap;"> (les majuscules sont rejetées par le scanner)</span>

</td></tr></tbody></table>

<p class="callout info">**Recommandé —**<span style="white-space: pre-wrap;"> Si votre site comporte cinq sections (hero, features, stats, testimonials, footer), préfixez tous vos slots par leur section. Le client retrouvera ainsi plus rapidement ce qu'il cherche dans le Studio.</span></p>

### <span style="color: rgb(35, 111, 161);">Utiliser le groupe pour structurer l'interface</span>

<span style="white-space: pre-wrap;">L'attribut </span>`<span class="editor-theme-code">group</span>`<span style="white-space: pre-wrap;"> détermine sous quelle section le slot apparaît dans le panneau « Contenu de la page » du Studio.</span>

```
{{slot:hero_title|type=text|group=hero|...}}
{{slot:hero_lead|type=textarea|group=hero|...}}
{{slot:hero_image|type=image|group=hero|...}}

{{slot:features_card_1_title|type=text|group=features|...}}
{{slot:features_card_2_title|type=text|group=features|...}}
{{slot:features_card_3_title|type=text|group=features|...}}

{{slot:footer_copyright|type=text|group=footer|...}}
```

Dans le Studio, ces slots seront regroupés visuellement sous trois sections : Hero, Features et Footer.

### <span style="color: rgb(35, 111, 161);">Le rescan après ajout</span>

<span style="white-space: pre-wrap;">Ajouter un slot dans un fichier </span>`<span class="editor-theme-code">tpl.php</span>`<span style="white-space: pre-wrap;"> ne suffit pas : le module doit le détecter et l'enregistrer en base. Pour cela, lancez un rescan.</span>

##### **Méthode A — Via l'interface Studio**

1. Outils → InfraSStudio → Contenu des pages.
2. Sélectionnez votre site.
3. <span style="white-space: pre-wrap;">Cliquez sur </span>**Rescanner**<span style="white-space: pre-wrap;"> en haut.</span>
4. <span style="white-space: pre-wrap;">Le module parcourt tous les fichiers </span>`<span class="editor-theme-code">tpl.php</span>`<span style="white-space: pre-wrap;"> et synchronise les slots.</span>

##### **Méthode B — En ligne de commande**

```
php htdocs/custom/infrasstudio/scripts/rescan_slots.php <ref-du-site> --entity=<N>
```

Sortie typique :

```
Rescanning website #1 — monsite (entity 2)
Pages scanned   : 22
Slots added     : 3
Slots updated   : 40
Slots orphaned  : 0
```

**Mode de validation —**<span style="white-space: pre-wrap;"> Ajoutez l'option </span>`<span class="editor-theme-code">--lint</span>`<span style="white-space: pre-wrap;"> pour détecter les erreurs de syntaxe avant de les valider :</span>  
`<span class="editor-theme-code">php rescan_slots.php <ref> --lint</span>`  
Le code de sortie est 0 en l'absence d'erreur, 1 en présence d'avertissements, 2 en cas d'erreurs. Cette commande peut être intégrée dans un crochet de pré-commit.

### <span style="color: rgb(35, 111, 161);">Slots orphelins</span>

<span style="white-space: pre-wrap;">Lorsque vous supprimez un slot d'un fichier </span>`<span class="editor-theme-code">tpl.php</span>`, son enregistrement en base passe au statut « orphelin ». Le module le conserve pendant 30 jours avant de le supprimer automatiquement (via une tâche planifiée).

Cette politique présente un avantage : si vous restaurez le slot dans le HTML pendant cette période, sa valeur précédente est récupérée.

<p class="callout info">**Note —**<span style="white-space: pre-wrap;"> Pour forcer la purge immédiate des slots orphelins :</span>  
`<span class="editor-theme-code">php rescan_slots.php <ref> --purge-orphans</span>`</p>

### <span style="color: rgb(35, 111, 161);">Liste de contrôle d'annotation</span>

**Avant de livrer une page annotée :**

- <span style="white-space: pre-wrap;">Chaque slot possède un </span>`<span class="editor-theme-code">type</span>`<span style="white-space: pre-wrap;"> explicite.</span>
- <span style="white-space: pre-wrap;">Chaque slot possède une valeur </span>`<span class="editor-theme-code">default</span>`, garantissant la lisibilité de la page si la base venait à se vider.
- <span style="white-space: pre-wrap;">Chaque slot possède un </span>`<span class="editor-theme-code">label</span>`<span style="white-space: pre-wrap;"> en français clair, destiné à l'éditeur.</span>
- <span style="white-space: pre-wrap;">Les slots sont regroupés par section avec </span>`<span class="editor-theme-code">group</span>`.
- <span style="white-space: pre-wrap;">Les identifiants suivent la convention </span>`<span class="editor-theme-code"><section>_<champ></span>`.
- Le rescan a été lancé sans avertissement ni erreur.
- Vous avez ouvert la page dans le Studio et vérifié la présence de tous les slots.

Le chapitre suivant détaille la grammaire complète des slots, avec tous les types et leurs comportements.

# CHAPITRE 17 — Préparer un site Dolibarr Website

Avant de pouvoir éditer un site via le module, ce site doit exister côté Dolibarr Website. Ce chapitre s'adresse au développeur qui démarre un nouveau projet : créer le site, le câbler proprement, et préparer le terrain pour le Studio.

### <span style="color: rgb(35, 111, 161);">Étape 1 — Créer le site dans Dolibarr Website</span>

1. Connectez-vous à Dolibarr en tant qu'administrateur.
2. Rendez-vous dans Accueil → Sites web.
3. <span style="white-space: pre-wrap;">Cliquez sur </span>**Nouveau site**.
4. Renseignez les informations suivantes :
    - **Référence**<span style="white-space: pre-wrap;"> : identifiant court sans espace, par exemple </span>`<span class="editor-theme-code">monsite</span>`<span style="white-space: pre-wrap;"> ou </span>`<span class="editor-theme-code">keaticweb</span>`. C'est l'identifiant interne, utilisé partout (URLs des wrappers, dossiers de données, constantes de configuration).
    - **Description**<span style="white-space: pre-wrap;"> : libellé facultatif.</span>
    - **Virtualhost principal**<span style="white-space: pre-wrap;"> : URL publique, par exemple </span>`<span class="editor-theme-code">https://monsite.com</span>`.
    - **Langue principale**<span style="white-space: pre-wrap;"> : code de la locale, par exemple </span>`<span class="editor-theme-code">fr_FR</span>`.
    - **Autres langues**<span style="white-space: pre-wrap;"> : valeurs séparées par des virgules, par exemple </span>`<span class="editor-theme-code">en_US,de_DE,es_ES</span>`.
5. Enregistrez.

<p class="callout info">**Convention de référence —**<span style="white-space: pre-wrap;"> Privilégiez un identifiant court et stable. Cette référence apparaîtra dans les chemins de fichiers, les URLs internes et les constantes de configuration. La modifier ultérieurement nécessite plusieurs ajustements.</span></p>

### <span style="color: rgb(35, 111, 161);">Étape 2 — Configurer le virtualhost Apache</span>

Le module Website ne configure pas Apache pour vous. Vous devez créer un VirtualHost qui pointe sur le docroot du site.

##### **Arborescence type**

```
/var/www/monsite/                           # docroot Apache du site
├── index.php                                # wrapper page d'accueil
├── <alias>.php                              # autres wrappers générés
├── master.inc.php                           # bootstrap (inclut DOL_DOCUMENT_ROOT)
├── medias  -> lien symbolique vers DOL_DATA_ROOT/<entity>/medias/
└── ...

DOL_DATA_ROOT/<entity>/website/monsite/      # dossier de données du site
├── page1.tpl.php                            # gabarits des pages
├── page2.tpl.php
├── ...
└── htmlheader.html                          # en-tête commun éventuel
```

##### **Exemple de VirtualHost minimal**

```
<VirtualHost *:443>
    ServerName monsite.com
    DocumentRoot /var/www/monsite

    SSLEngine on
    SSLCertificateFile      /etc/letsencrypt/live/monsite.com/fullchain.pem
    SSLCertificateKeyFile   /etc/letsencrypt/live/monsite.com/privkey.pem

    <Directory /var/www/monsite>
        Options FollowSymLinks
        AllowOverride None
        Require all granted
    </Directory>

    # PHP-FPM dédié recommandé
    <FilesMatch \.php$>
        SetHandler "proxy:unix:/run/php/php8.2-fpm.monsite.sock|fcgi://localhost"
    </FilesMatch>
</VirtualHost>
```

<p class="callout info">**Note —**<span style="white-space: pre-wrap;"> Un pool PHP-FPM dédié est recommandé pour isoler les performances et les variables d'environnement par site. Le pool peut tourner sous un utilisateur spécifique pour faciliter la gestion des permissions.</span></p>

### <span style="color: rgb(35, 111, 161);">Étape 3 — Le lien symbolique medias</span>

Pour que les images téléversées soient servies directement par Apache (mode média « native »), il faut un lien symbolique :

```
cd /var/www/monsite
ln -sfn /mnt/data/dolibarr/<entity>/medias medias
ls -la medias  # doit pointer sur le dossier de données Dolibarr
```

<p class="callout info">**Conseil —**<span style="white-space: pre-wrap;"> Si vous ne pouvez pas créer ce lien symbolique, basculez le site en mode média </span>**module**<span style="white-space: pre-wrap;"> dans la configuration du module. Les images seront servies via </span>`<span class="editor-theme-code">document.php</span>`, avec un coût de performance modéré.</p>

### <span style="color: rgb(35, 111, 161);">Étape 4 — Le master.inc.php multicompany</span>

<span style="white-space: pre-wrap;">Si votre installation Dolibarr fonctionne en multicompany, le fichier </span>`<span class="editor-theme-code">master.inc.php</span>`<span style="white-space: pre-wrap;"> du site doit définir </span>`<span class="editor-theme-code">DOLENTITY</span>`<span style="white-space: pre-wrap;"> avant le chargement :</span>

```
<?php
// /var/www/monsite/master.inc.php
define('DOLENTITY', 2);   // l'entity correspondant au client
require_once '/var/www/dolibarr/htdocs/master.inc.php';
```

<p class="callout warning">**Avertissement —**<span style="white-space: pre-wrap;"> Sur une instance multicompany sans </span>`<span class="editor-theme-code">DOLENTITY</span>`, le site renverra une erreur 503 ou affichera les données de la mauvaise entity. C'est l'erreur la plus fréquemment rencontrée lors de la mise en service.</p>

### <span style="color: rgb(35, 111, 161);">Étape 5 — Créer la page d'accueil</span>

Toujours dans le module Website Dolibarr :

1. Sélectionnez votre site.
2. <span style="white-space: pre-wrap;">Cliquez sur </span>**Nouvelle page**.
3. Renseignez :
    - **Page URL**<span style="white-space: pre-wrap;"> : </span>`<span class="editor-theme-code">home</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">index</span>`<span style="white-space: pre-wrap;"> ou autre.</span>
    - **Title**<span style="white-space: pre-wrap;"> : Accueil.</span>
    - **Type container**<span style="white-space: pre-wrap;"> : </span>`<span class="editor-theme-code">page</span>`.
    - **Lang**<span style="white-space: pre-wrap;"> : </span>`<span class="editor-theme-code">fr_FR</span>`.
    - **Status**<span style="white-space: pre-wrap;"> : Brouillon (le passage à Publié interviendra plus tard).</span>
4. Enregistrez.
5. <span style="white-space: pre-wrap;">Dolibarr crée automatiquement les fichiers </span>`<span class="editor-theme-code">/var/www/monsite/home.php</span>`<span style="white-space: pre-wrap;"> et </span>`<span class="editor-theme-code">page<N>.tpl.php</span>`.

### <span style="color: rgb(35, 111, 161);">Étape 6 — Activer le site dans le module</span>

La procédure complète est détaillée au Chapitre 7. En résumé :

1. Outils → InfraSStudio → Configuration.
2. Cochez votre site dans la liste.
3. Choisissez le mode média (**native**<span style="white-space: pre-wrap;"> recommandé).</span>
4. Enregistrez.

### <span style="color: rgb(35, 111, 161);">Étape 7 — Annoter les premières pages avec des slots</span>

<span style="white-space: pre-wrap;">Cette étape fait l'objet du Chapitre 18. En quelques mots : modifiez le fichier </span>`<span class="editor-theme-code">page<N>.tpl.php</span>`<span style="white-space: pre-wrap;"> pour ajouter des tokens </span>`<span class="editor-theme-code">{{slot:nom|type=...}}</span>`<span style="white-space: pre-wrap;"> aux endroits que vous souhaitez rendre éditables.</span>

### <span style="color: rgb(35, 111, 161);">Étape 8 — Vérifier avec la page Diagnostic</span>

Avant de livrer le site au client, lancez le diagnostic (Outils → InfraSStudio → Diagnostic) :

- Tous les voyants verts dans Environnement, Schéma SQL, Stockage et Intégration Dolibarr.
- Le site géré apparaît dans la section Sites avec docroot résolu, mode média correct et dossier de données accessible en écriture.

### <span style="color: rgb(35, 111, 161);">Récapitulatif</span>

**Votre site est prêt si :**

- Le site est créé dans Dolibarr Website (référence, virtualhost, langues).
- Le VirtualHost Apache pointe sur le bon docroot.
- <span style="white-space: pre-wrap;">Le lien symbolique </span>`<span class="editor-theme-code">medias</span>`<span style="white-space: pre-wrap;"> est en place (mode native), ou la constante </span>`<span class="editor-theme-code">INFRASSTUDIO_SITE_<id>_MEDIA_MODE=module</span>`<span style="white-space: pre-wrap;"> est définie.</span>
- <span style="white-space: pre-wrap;">Le fichier </span>`<span class="editor-theme-code">master.inc.php</span>`<span style="white-space: pre-wrap;"> définit </span>`<span class="editor-theme-code">DOLENTITY</span>`<span style="white-space: pre-wrap;"> en multicompany.</span>
- Au moins une page existe (l'accueil).
- Le site est coché dans la configuration du module.
- La page Diagnostic est entièrement verte.

Le chapitre suivant aborde l'annotation des templates avec des slots.