Les Sources de contenu permettent à un plugin activé d'exposer des données de domaine typées aux blocs CMS existants sans être propriétaire du balisage public. Un plugin de catalogue, de commerce, d'événements ou d'actualités fournit les enregistrements ; les pages CMS conservent la maîtrise de la mise en page, de la composition des blocs, de la traduction, de l'aperçu et du rendu.

Cette séparation est intentionnelle :

  • une source de plugin indique quelles données sont disponibles ;
  • un bloc CMS indique comment ces données sont présentées ;
  • l'arborescence des pages et des slots indique où elles apparaissent.

Les plugins ne doivent pas recréer les moteurs de rendu principaux Header, Rich Text, Card, Grid, Slider ou Slide uniquement pour afficher des enregistrements appartenant au plugin.

Contrat de source d’entité

Un plugin activé enregistre une source d'entité dans sa définition :

PluginDefinition::make('plugin-catalog')
  ->contentSources([
    ContentSourceDefinition::entity('plugin-catalog::plugin')
      ->label('Plugin')
      ->resolver(PluginSource::class)
      ->fields([
        'name' => ['type' => 'text', 'label' => 'Name'],
        'description' => ['type' => 'rich_text', 'label' => 'Description'],
        'download_url' => ['type' => 'url', 'label' => 'Download URL'],
      ]),
  ]);

Le résolveur implémente ContentSourceResolver. options() fournit à l'éditeur de blocs des choix d'aperçu sûrs et lisibles ; resolve() renvoie un enregistrement pour la clé stable demandée et le contexte actuel du site, de la page, de la locale et de l'aperçu.

Le plugin reste l'unique propriétaire de ses tables et modèles de domaine. Le CMS ne stocke que l'identifiant de la source, la clé stable de l'enregistrement, le champ sélectionné et la valeur éditoriale de repli habituelle du bloc.

Accès et mise en cache

Une source contenant des données restreintes associe via ContentSourceAccessPolicy une classe qui implémente accessPolicy(...). La politique reçoit le site, la page, la locale, l'état de l'aperçu et l'acteur authentifié avant tout appel d'un résolveur. Une source refusée se comporte comme une source indisponible et le bloc public conserve sa valeur éditoriale de repli.

Les plugins peuvent activer une mise en cache limitée des résultats avec cacheFor($seconds). Les clés de cache comprennent la source, le site, la page, la locale, les arguments d'enregistrement/requête et les paramètres de la source. Les demandes d'aperçu contournent le cache afin que les éditeurs examinent toujours les données actuelles. Après une écriture dans le domaine, les plugins peuvent appeler app(ContentSourceRuntime::class)->invalidate('plugin-handle::source') pour rendre immédiatement obsolètes toutes les variantes en cache de cette source. Une durée nulle, valeur par défaut, désactive la mise en cache.

Comportement de l’éditeur

Les blocs existants pris en charge affichent un contrôle Source de contenu dans leur onglet Paramètres. L'éditeur peut conserver la valeur littérale saisie dans les Champs du bloc ou sélectionner un enregistrement source et un champ de type compatible. Aucune expression de liaison n'est saisie manuellement.

Les liaisons de champs prises en charge comprennent :

  • le title de Header depuis un champ source text ;
  • le content de Plain Text depuis un champ source text ;
  • le content de Rich Text depuis un champ source text ou rich_text.
  • La source de l'image, la légende, le texte alternatif et le lien depuis des champs compatibles.
  • Les libellés et URL de Button et Button Link.
  • Le titre, le texte secondaire, la description et l'URL de Link List Item.

Lors du rendu public, la liaison prévaut lorsqu'elle est résolue en une valeur non vide. Les enregistrements manquants, plugins désactivés, sources indisponibles et valeurs vides conservent de façon sûre la valeur éditoriale du bloc. Les échecs du résolveur sont signalés sans mettre la page hors service.

Les liaisons résident sous settings.content_bindings ; les mécanismes existants de duplication, révision, exportation et importation des pages les conservent donc sans colonne de base de données propre au domaine.

Sources de collections

Un plugin peut enregistrer ContentSourceDefinition::collection(...) avec un résolveur qui implémente ContentCollectionSourceResolver. Les conteneurs capables d'accueillir des collections peuvent sélectionner cette collection et un enfant direct existant comme modèle répété. Lors du rendu, le CMS clone cette sous-arborescence pour chaque enregistrement, fournit l'enregistrement comme élément courant de la collection et résout les liaisons de champs descendantes par rapport à celui-ci.

La prise en charge des collections est une capacité du contrat de bloc, et non une liste CMS de noms de blocs particuliers. Les types principaux Section, Container, Stack, Cluster, Grid et Slide acceptent tout modèle enfant direct par ailleurs valide. Slider accepte Slide ; Columns accepte Column Item ; Feature Grid accepte Feature Item ou Column Item ; Link List accepte Link List Item. Split et les conteneurs sémantiques tels que Card n'annoncent pas cette capacité, car la répétition de leurs enfants structurels enfreindrait leur contrat de mise en page.

Les types de blocs des plugins peuvent l'activer avec ->contentCollectionTemplate() pour tout type enfant valide, ou transmettre une liste de slugs de catalogue enfants autorisés. Les manifestes installés expriment le même contrat avec content_collection.enabled et, facultativement, content_collection.child_types. La vue publique du conteneur du plugin effectue le rendu des enfants résolus via ContentCollectionRenderer::children($block), comme les vues des conteneurs principaux.

Les autres enfants restent du contenu éditorial ordinaire et conservent leur position. Le modèle sélectionné est remplacé sur place par ses enregistrements résolus, ce qui permet à un conteneur de combiner volontairement contenu manuel et dynamique sans moteur de rendu carousel, grid ou card appartenant au plugin. Les éditeurs peuvent prévisualiser jusqu'à trois enregistrements source, limiter les enregistrements, filtrer selon un champ source et une valeur, trier selon un champ source dans les deux sens et paginer les résultats Grid ou Stack. La résolution est limitée à 50 enregistrements. Une source manquante, un plugin désactivé, un modèle non valide ou un échec du résolveur restaure en toute sécurité l'arborescence de blocs ordinaire stockée.

Pour les petites sources, ContentCollectionSourceResolver suffit et le CMS applique filtrage, tri et pagination à son itérable. Les grandes sources doivent implémenter QueryableContentCollectionSourceResolver. Le CMS transmet alors au plugin une ContentCollectionQuery typée et consomme un ContentCollectionResult, ce qui autorise le filtrage base de données/API, le tri, les limites, les totaux et les fenêtres de pages sans charger toute la collection.

Les éditeurs choisissent séparément si un résultat valide mais vide et une erreur du résolveur masquent le modèle répété ou affichent sa valeur éditoriale de repli. Les sources refusées ou supprimées conservent le contenu stocké. Le panneau Paramètres signale les liaisons dont le plugin, la source ou le champ déclaré a disparu, afin que les mises à niveau et suppressions de plugins ne laissent pas silencieusement de contenu orphelin.

Les liaisons et la configuration des collections résident dans la charge utile ordinaire settings du bloc. Les révisions de pages et l'exportation/importation du site copient déjà cette charge à l'identique ; aucune table appartenant au plugin ni sortie exécutable du résolveur n'entre dans une révision CMS ou un paquet de transfert.

API interne de contenu

Les clients API ne découvrent que les sources activées et accessibles avec GET /webadmin/api/content-sources. Le passage de block_id ajoute les cibles de liaison compatibles et indique si ce bloc peut accueillir la collection. Les sources d'entités incluent leurs choix sûrs d'enregistrements ; les champs proviennent toujours du contrat déclaré du plugin.

POST /webadmin/api/content-sources/{source}/preview résout un enregistrement d'entité ou jusqu'à cinq enregistrements de collection sans modifier le contenu. Les réponses d'aperçu écartent toute valeur non déclarée par la source et utilisent comme contexte le bloc demandé, la locale, l'acteur authentifié et la politique d'accès à la source.

Un bloc structuré existant accepte content_bindings et content_collection au niveau supérieur de PATCH /webadmin/api/blocks/{block} ; les mêmes objets sont aussi acceptés sous settings. Chaque objet remplace sa configuration correspondante et null l'efface. Avant l'écriture, l'API valide les cibles de bloc, les types de champs, l'accès à la source, les choix d'enregistrements, la propriété des enfants directs et les types de modèles de collection autorisés par le contrat du bloc. Une configuration non valide renvoie 422 invalid_content_source_configuration. Après l'écriture, effectuez le rendu de la page de brouillon ou de mise à jour propriétaire afin de vérifier le résultat composé.