Las Fuentes de contenido permiten que un plugin habilitado exponga datos de dominio tipados a los bloques existentes del CMS sin ser propietario del marcado público. Un plugin de catálogo, comercio, eventos o noticias proporciona registros; las páginas del CMS conservan la propiedad del diseño, la composición de bloques, la traducción, la vista previa y el renderizado.

El límite es deliberado:

  • una fuente del plugin responde qué datos están disponibles;
  • un bloque del CMS responde cómo se presentan esos datos;
  • el árbol de páginas y slots responde dónde aparecen.

Los plugins no deben recrear los renderizadores centrales de Header, Rich Text, Card, Grid, Slider o Slide solo para mostrar registros propiedad del plugin.

Contrato de fuente de entidades

Un plugin habilitado registra una fuente de entidades en su definición:

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'],
      ]),
  ]);

El resolvedor implementa ContentSourceResolver. options() proporciona al editor de bloques opciones de vista previa seguras y legibles; resolve() devuelve un registro para la clave estable solicitada y el contexto actual de sitio, página, locale y vista previa.

El plugin sigue siendo el único propietario de sus tablas y modelos de dominio. El CMS solo almacena el identificador de la fuente, la clave estable del registro, el campo seleccionado y el valor editorial alternativo ordinario del bloque.

Acceso y almacenamiento en caché

Una fuente que contiene datos restringidos adjunta con ContentSourceAccessPolicy una clase que implementa accessPolicy(...). La política recibe el sitio, la página, el locale, el estado de vista previa y el actor autenticado antes de llamar a cualquier resolvedor. Una fuente denegada se comporta como una fuente no disponible y el bloque público conserva su valor editorial alternativo.

Los plugins pueden activar el almacenamiento en caché acotado de resultados con cacheFor($seconds). Las claves de caché incluyen la fuente, el sitio, la página, el locale, los argumentos de registro/consulta y la configuración de la fuente. Las solicitudes de vista previa omiten la caché para que los editores inspeccionen siempre los datos actuales. Tras una escritura de dominio, los plugins pueden llamar a app(ContentSourceRuntime::class)->invalidate('plugin-handle::source') para invalidar de inmediato todas las variantes almacenadas de esa fuente. Una duración de cero, el valor predeterminado, desactiva la caché.

Comportamiento del editor

Los bloques existentes compatibles muestran un control de Fuente de contenido en su pestaña Configuración. El editor puede conservar el valor literal introducido en Campos del bloque o seleccionar un registro de fuente y un campo de tipo compatible. No se escribe manualmente ninguna expresión de vinculación.

Las vinculaciones de campo compatibles incluyen:

  • title de Header desde un campo de fuente text;
  • content de Plain Text desde un campo de fuente text;
  • content de Rich Text desde un campo de fuente text o rich_text.
  • Fuente de imagen, pie, texto alternativo y enlace desde campos compatibles.
  • Etiquetas y URL de Button y Button Link.
  • Título, texto secundario, descripción y URL de Link List Item.

En el renderizado público, la vinculación prevalece cuando se resuelve en un valor no vacío. Los registros ausentes, los plugins deshabilitados, las fuentes no disponibles y los valores vacíos conservan de forma segura el valor editorial del bloque. Los fallos del resolvedor se notifican sin detener la página.

Las vinculaciones residen en settings.content_bindings, por lo que el comportamiento existente de duplicación, revisión, exportación e importación de páginas las conserva sin una columna de base de datos específica del dominio.

Fuentes de colecciones

Un plugin puede registrar ContentSourceDefinition::collection(...) con un resolvedor que implemente ContentCollectionSourceResolver. Los contenedores compatibles con colecciones pueden seleccionar esa colección y un hijo directo existente como plantilla repetida. Durante el renderizado, el CMS clona ese subárbol para cada registro, proporciona el registro como elemento actual de la colección y resuelve contra él las vinculaciones de campos descendientes.

La compatibilidad con colecciones es una capacidad del contrato de bloque, no una lista del CMS con nombres de bloques especiales. Los tipos centrales Section, Container, Stack, Cluster, Grid y Slide aceptan cualquier plantilla hija directa que sea válida. Slider acepta Slide; Columns acepta Column Item; Feature Grid acepta Feature Item o Column Item; y Link List acepta Link List Item. Split y los contenedores semánticos como Card no anuncian esta capacidad porque repetir sus hijos estructurales infringiría su contrato de diseño.

Los tipos de bloque del plugin pueden habilitarla con ->contentCollectionTemplate() para cualquier tipo de hijo válido, o pasar una lista de slugs de catálogo de hijos permitidos. Los manifiestos instalados expresan el mismo contrato mediante content_collection.enabled y el valor opcional content_collection.child_types. La vista pública del contenedor del plugin renderiza los hijos resueltos mediante ContentCollectionRenderer::children($block), igual que las vistas centrales de contenedores.

Los demás hijos siguen siendo contenido editorial ordinario y mantienen su posición. La plantilla seleccionada se sustituye en su lugar por los registros resueltos, de modo que un contenedor puede combinar deliberadamente contenido manual y dinámico sin un renderizador de carousel, grid o card propiedad del plugin. Los editores pueden previsualizar hasta tres registros de fuente, limitar registros, filtrar por un campo y valor de fuente, ordenar por un campo de fuente en cualquier dirección y paginar resultados de Grid o Stack. La resolución está limitada a 50 registros. Una fuente ausente, un plugin deshabilitado, una plantilla no válida o un fallo del resolvedor restaura de forma segura el árbol de bloques almacenado ordinario.

Para fuentes pequeñas, ContentCollectionSourceResolver es suficiente y el CMS aplica filtrado, ordenación y paginación a su iterable. Las fuentes grandes deben implementar QueryableContentCollectionSourceResolver. El CMS pasa entonces al plugin una ContentCollectionQuery tipada y consume un ContentCollectionResult, lo que permite filtrado de base de datos/API, ordenación, límites, totales y ventanas de páginas sin cargar toda la colección.

Los editores eligen por separado si un resultado válido pero vacío y un error del resolvedor ocultan la plantilla repetida o muestran su valor editorial alternativo. Las fuentes denegadas o eliminadas conservan el contenido almacenado. El panel Configuración informa de las vinculaciones cuyo plugin, fuente o campo declarado haya desaparecido, para que las actualizaciones y eliminaciones de plugins no dejen contenido huérfano silenciosamente.

Las vinculaciones y la configuración de colecciones residen en la carga útil ordinaria settings del bloque. Las revisiones de página y la exportación/importación del sitio ya copian literalmente esa carga; ninguna tabla propiedad del plugin ni salida ejecutable del resolvedor entra en una revisión del CMS o paquete de transferencia.

API interna de contenido

Los clientes de la API solo descubren fuentes habilitadas y accesibles con GET /webadmin/api/content-sources. Pasar block_id añade los destinos de vinculación compatibles y si ese bloque puede alojar la colección. Las fuentes de entidades incluyen sus opciones seguras de registros; los campos siempre proceden del contrato declarado por el plugin.

POST /webadmin/api/content-sources/{source}/preview resuelve un registro de entidad o hasta cinco registros de colección sin cambiar el contenido. Las respuestas de vista previa descartan todo valor no declarado por la fuente y usan como contexto el bloque solicitado, el locale, el actor autenticado y la política de acceso a la fuente.

Un bloque estructurado existente acepta content_bindings y content_collection en el nivel superior de PATCH /webadmin/api/blocks/{block}; los mismos objetos también se aceptan bajo settings. Cada objeto sustituye su configuración correspondiente y null la elimina. Antes de escribir, la API valida destinos de bloque, tipos de campo, acceso a la fuente, opciones de registros, propiedad de hijos directos y los tipos de plantilla de colección permitidos por el contrato del bloque. Una configuración no válida devuelve 422 invalid_content_source_configuration. Tras escribir, renderice el borrador o la página de actualización propietaria para verificar el resultado compuesto.