Files
roi-theme/openspec/specs/post-grid-shortcode/spec.md
FrankZamora 79e91f59ee feat(theme): add [roi_post_grid] shortcode for static pages
- Create PostGridShortcodeRegistrar for WordPress shortcode registration
- Implement RenderPostGridUseCase following Clean Architecture
- Add PostGridQueryBuilder for custom WP_Query construction
- Add PostGridShortcodeRenderer for HTML/CSS generation
- Register shortcode in DIContainer with proper DI
- Add shortcode usage guide in post-grid admin panel
- Fix sidebar layout: add hide_for_logged_in check to wrapper visibility

Shortcode attributes: category, tag, author, posts_per_page, columns,
show_pagination, show_thumbnail, show_excerpt, show_meta, etc.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 21:33:20 -06:00

14 KiB

Especificacion de Shortcode Post Grid

Purpose

Crear un shortcode [roi_post_grid] que permita insertar grids de posts en cualquier pagina o entrada de WordPress, con filtros configurables por categoria, tag, autor y otras opciones. Sigue Clean Architecture delegando logica a Use Cases.

Context

Problema Actual

El componente post-grid implementado en templates-unificados solo funciona en templates de archivo (home.php, archive.php, etc.) porque depende del loop principal de WordPress (global $wp_query).

Solucion

Crear un shortcode que:

  1. Siga Clean Architecture (ShortcodeRegistrar → UseCase → Repository)
  2. Reutilice estilos del componente post-grid existente
  3. Permita filtrar posts por categoria, tag, autor
  4. No dependa del loop principal de WordPress

Relacion con templates-unificados

  • Este shortcode complementa (no reemplaza) el componente post-grid
  • El componente post-grid sigue funcionando en templates de archivo
  • El shortcode permite insertar grids en contenido arbitrario

Nota sobre shortcodes legacy

Los shortcodes existentes (apu_table, apu_row) estan en Inc/apu-tables.php (patron legacy). Este nuevo shortcode sigue el patron moderno de Clean Architecture.


Requirements

Requirement: Arquitectura del Shortcode

The shortcode MUST follow Clean Architecture patterns.

Scenario: Ubicacion del ShortcodeRegistrar

  • WHEN se implementa el shortcode
  • THEN DEBE estar en Shared/Infrastructure/Wordpress/PostGridShortcodeRegistrar.php
  • AND DEBE usar namespace ROITheme\Shared\Infrastructure\Wordpress
  • AND DEBE registrar el shortcode via add_shortcode

Scenario: Delegacion a Use Case

  • WHEN el shortcode se ejecuta
  • THEN PostGridShortcodeRegistrar DEBE delegar a RenderPostGridUseCase
  • AND NO DEBE contener logica de negocio
  • AND solo sanitiza atributos y pasa al Use Case

Scenario: Ubicacion del Use Case

  • WHEN se implementa la logica del shortcode
  • THEN DEBE estar en Shared/Application/UseCases/RenderPostGrid/RenderPostGridUseCase.php
  • AND DEBE usar namespace ROITheme\Shared\Application\UseCases\RenderPostGrid
  • AND DEBE orquestar Query, Renderer y Settings

Scenario: Ubicacion del QueryBuilder

  • WHEN se construye el WP_Query
  • THEN DEBE estar en Shared/Infrastructure/Query/PostGridQueryBuilder.php
  • AND DEBE usar namespace ROITheme\Shared\Infrastructure\Query
  • AND DEBE recibir parametros y retornar WP_Query

Scenario: Ubicacion del ShortcodeRenderer

  • WHEN se genera el HTML del grid
  • THEN DEBE estar en Shared/Infrastructure/Ui/PostGridShortcodeRenderer.php
  • AND DEBE usar namespace ROITheme\Shared\Infrastructure\Ui
  • AND DEBE inyectar CSSGeneratorInterface

Requirement: Estructura de Clases

Each class MUST have single responsibility following SRP.

Scenario: Responsabilidad de PostGridShortcodeRegistrar

  • WHEN se define PostGridShortcodeRegistrar
  • THEN DEBE tener metodo estatico register() para add_shortcode
  • AND DEBE tener metodo handleShortcode(array $atts): string
  • AND handleShortcode DEBE sanitizar atributos
  • AND handleShortcode DEBE obtener UseCase del DIContainer
  • AND handleShortcode DEBE retornar resultado del UseCase
  • AND NO DEBE tener mas de 50 lineas

Scenario: Responsabilidad de RenderPostGridUseCase

  • WHEN se define RenderPostGridUseCase
  • THEN DEBE recibir RenderPostGridRequest como input
  • AND DEBE inyectar PostGridQueryBuilderInterface via constructor
  • AND DEBE inyectar PostGridShortcodeRendererInterface via constructor
  • AND DEBE inyectar ComponentSettingsRepositoryInterface via constructor
  • AND DEBE orquestar: obtener settings, construir query, renderizar
  • AND DEBE retornar string HTML
  • AND NO DEBE tener mas de 80 lineas

Scenario: Responsabilidad de PostGridQueryBuilder

  • WHEN se define PostGridQueryBuilder
  • THEN DEBE recibir parametros de filtro (category, tag, etc.)
  • AND DEBE construir array de argumentos WP_Query
  • AND DEBE retornar WP_Query configurado
  • AND NO DEBE generar HTML
  • AND NO DEBE tener mas de 100 lineas

Scenario: Responsabilidad de PostGridShortcodeRenderer

  • WHEN se define PostGridShortcodeRenderer
  • THEN DEBE recibir WP_Query y configuracion
  • AND DEBE inyectar CSSGeneratorInterface
  • AND DEBE generar HTML del grid
  • AND DEBE generar CSS inline
  • AND NO DEBE construir queries
  • AND NO DEBE tener mas de 150 lineas

Requirement: Interfaces en Domain/Application

Interfaces MUST be defined for dependency injection.

Scenario: Interface del QueryBuilder

  • WHEN se define la interface
  • THEN DEBE estar en Shared/Domain/Contracts/PostGridQueryBuilderInterface.php
  • AND DEBE tener metodo build(array $params): \WP_Query

Scenario: Interface del Renderer

  • WHEN se define la interface
  • THEN DEBE estar en Shared/Domain/Contracts/PostGridShortcodeRendererInterface.php
  • AND DEBE tener metodo render(\WP_Query $query, array $settings, array $options): string

Scenario: Request DTO

  • WHEN se define el DTO de entrada
  • THEN DEBE estar en Shared/Application/UseCases/RenderPostGrid/RenderPostGridRequest.php
  • AND DEBE ser readonly class con propiedades tipadas
  • AND DEBE contener todos los atributos del shortcode

Requirement: Atributos del Shortcode

The shortcode MUST accept configurable attributes.

Scenario: Atributo category

  • WHEN se usa [roi_post_grid category="precios-unitarios"]
  • THEN DEBE filtrar posts por slug de categoria
  • AND DEBE aceptar multiples categorias separadas por coma
  • AND ejemplo: category="cat1,cat2,cat3"

Scenario: Atributo exclude_category

  • WHEN se usa [roi_post_grid exclude_category="noticias"]
  • THEN DEBE excluir posts de esas categorias
  • AND DEBE aceptar multiples categorias separadas por coma

Scenario: Atributo tag

  • WHEN se usa [roi_post_grid tag="concreto"]
  • THEN DEBE filtrar posts por slug de tag
  • AND DEBE aceptar multiples tags separados por coma

Scenario: Atributo author

  • WHEN se usa [roi_post_grid author="admin"]
  • THEN DEBE filtrar posts por login de autor
  • AND DEBE aceptar ID numerico o login string

Scenario: Atributo posts_per_page

  • WHEN se usa [roi_post_grid posts_per_page="6"]
  • THEN DEBE limitar cantidad de posts mostrados
  • AND default DEBE ser 9
  • AND DEBE aceptar valores entre 1 y 50

Scenario: Atributo columns

  • WHEN se usa [roi_post_grid columns="4"]
  • THEN DEBE definir columnas en desktop
  • AND default DEBE ser 3
  • AND DEBE aceptar valores 1, 2, 3 o 4

Scenario: Atributo orderby

  • WHEN se usa [roi_post_grid orderby="title"]
  • THEN DEBE ordenar posts por ese campo
  • AND default DEBE ser "date"
  • AND opciones validas: date, title, modified, rand, comment_count

Scenario: Atributo order

  • WHEN se usa [roi_post_grid order="ASC"]
  • THEN DEBE definir direccion del orden
  • AND default DEBE ser "DESC"
  • AND opciones validas: ASC, DESC

Scenario: Atributo show_pagination

  • WHEN se usa [roi_post_grid show_pagination="true"]
  • THEN DEBE mostrar paginacion si hay mas posts
  • AND default DEBE ser false

Scenario: Atributo offset

  • WHEN se usa [roi_post_grid offset="3"]
  • THEN DEBE saltar los primeros N posts
  • AND default DEBE ser 0

Scenario: Atributo exclude_posts

  • WHEN se usa [roi_post_grid exclude_posts="123,456"]
  • THEN DEBE excluir posts por ID
  • AND DEBE aceptar IDs separados por coma

Scenario: Atributos de visualizacion

  • WHEN se usan atributos de visualizacion
  • THEN show_thumbnail default true
  • AND show_excerpt default true
  • AND show_meta default true
  • AND show_categories default true
  • AND excerpt_length default 20

Scenario: Atributo class

  • WHEN se usa [roi_post_grid class="my-custom-grid"]
  • THEN DEBE agregar clase CSS adicional al contenedor

Scenario: Atributo id para paginacion multiple

  • WHEN se usa [roi_post_grid id="grid-1" show_pagination="true"]
  • THEN DEBE usar query var unico paged_grid-1
  • AND permite multiples shortcodes paginados en misma pagina

Requirement: Obtencion de Settings

The shortcode MUST obtain styles from post-grid component settings.

Scenario: Lectura de configuracion

  • WHEN RenderPostGridUseCase se ejecuta
  • THEN DEBE usar ComponentSettingsRepositoryInterface
  • AND DEBE obtener settings de componente 'post-grid'
  • AND DEBE aplicar colores, spacing, visual_effects del componente

Scenario: Settings no encontrados

  • WHEN no existen settings de post-grid en BD
  • THEN DEBE usar valores default definidos en VisibilityDefaults
  • AND NO DEBE fallar con error

Requirement: Renderizado HTML

The shortcode MUST generate valid HTML with proper escaping.

Scenario: Estructura HTML del grid

  • WHEN el shortcode renderiza
  • THEN DEBE generar contenedor con clase roi-post-grid-shortcode
  • AND DEBE generar row con clase Bootstrap row
  • AND cada card DEBE tener columna responsive

Scenario: Clases responsive de columnas

  • WHEN columns es 3
  • THEN cada card DEBE tener col-12 col-md-6 col-lg-4
  • AND para columns=4: col-12 col-md-6 col-lg-3
  • AND para columns=2: col-12 col-md-6
  • AND para columns=1: col-12

Scenario: Sin resultados

  • WHEN el query no encuentra posts
  • THEN DEBE mostrar mensaje "No se encontraron publicaciones"
  • AND NO DEBE romper layout

Scenario: Escaping obligatorio

  • WHEN se genera HTML
  • THEN DEBE usar esc_html() para textos
  • AND DEBE usar esc_attr() para atributos
  • AND DEBE usar esc_url() para URLs
  • AND DEBE usar wp_kses_post() para excerpts

Requirement: CSS via CSSGenerator

The shortcode MUST use CSSGeneratorInterface for styles.

Scenario: Generacion de CSS

  • WHEN PostGridShortcodeRenderer genera CSS
  • THEN DEBE inyectar CSSGeneratorInterface
  • AND DEBE usar settings de post-grid desde BD
  • AND DEBE generar CSS inline en el shortcode

Scenario: Selector unico

  • WHEN se genera CSS
  • THEN DEBE usar selector .roi-post-grid-shortcode
  • AND si se especifica id, usar .roi-post-grid-shortcode-{id}
  • AND NO DEBE conflictuar con post-grid del template

Requirement: Registro del Shortcode

The shortcode MUST be registered in WordPress.

Scenario: Registro en bootstrap

  • WHEN WordPress carga el tema
  • THEN functions-addon.php DEBE llamar PostGridShortcodeRegistrar::register()
  • AND DEBE estar disponible en editor clasico y Gutenberg

Scenario: Metodo register estatico

  • WHEN se llama PostGridShortcodeRegistrar::register()
  • THEN DEBE ejecutar add_shortcode('roi_post_grid', ...)
  • AND DEBE usar DIContainer para obtener dependencias

Implementation Order

Fase 1: Interfaces y DTOs

  1. Crear Shared/Domain/Contracts/PostGridQueryBuilderInterface.php
  2. Crear Shared/Domain/Contracts/PostGridShortcodeRendererInterface.php
  3. Crear Shared/Application/UseCases/RenderPostGrid/RenderPostGridRequest.php

Fase 2: Use Case

  1. Crear Shared/Application/UseCases/RenderPostGrid/RenderPostGridUseCase.php
  2. Implementar orquestacion de query, settings, render

Fase 3: Infrastructure - Query

  1. Crear Shared/Infrastructure/Query/PostGridQueryBuilder.php
  2. Implementar construccion de WP_Query con todos los filtros

Fase 4: Infrastructure - Renderer

  1. Crear Shared/Infrastructure/Ui/PostGridShortcodeRenderer.php
  2. Implementar generacion HTML y CSS

Fase 5: Infrastructure - Registrar

  1. Crear Shared/Infrastructure/Wordpress/PostGridShortcodeRegistrar.php
  2. Implementar registro y sanitizacion de atributos

Fase 6: Registro y DI

  1. Registrar en DIContainer las implementaciones
  2. Llamar register() en functions-addon.php

Fase 7: Testing

  1. Probar en pagina estatica
  2. Verificar filtros por categoria, tag
  3. Verificar paginacion con id unico

File Structure

Shared/
├── Domain/
│   └── Contracts/
│       ├── PostGridQueryBuilderInterface.php
│       └── PostGridShortcodeRendererInterface.php
├── Application/
│   └── UseCases/
│       └── RenderPostGrid/
│           ├── RenderPostGridRequest.php
│           └── RenderPostGridUseCase.php
└── Infrastructure/
    ├── Query/
    │   └── PostGridQueryBuilder.php
    ├── Ui/
    │   └── PostGridShortcodeRenderer.php
    └── Wordpress/
        └── PostGridShortcodeRegistrar.php

Examples

Ejemplo: Grid basico

[roi_post_grid]

Ejemplo: Posts de una categoria

[roi_post_grid category="precios-unitarios" posts_per_page="6"]

Ejemplo: Multiples shortcodes con paginacion

[roi_post_grid id="grid-cursos" category="cursos" show_pagination="true"]

[roi_post_grid id="grid-tutoriales" category="tutoriales" show_pagination="true"]

Dependencies

Existentes (reutilizar)

  • CSSGeneratorInterface - Para generar CSS
  • ComponentSettingsRepositoryInterface - Para leer config de post-grid
  • DIContainer - Para inyeccion de dependencias
  • Bootstrap 5 grid system - Para layout responsive

Nuevas (crear)

  • PostGridQueryBuilderInterface - Contrato para query builder
  • PostGridShortcodeRendererInterface - Contrato para renderer
  • RenderPostGridRequest - DTO de entrada
  • RenderPostGridUseCase - Orquestador
  • PostGridQueryBuilder - Implementacion query
  • PostGridShortcodeRenderer - Implementacion render
  • PostGridShortcodeRegistrar - Registro WordPress

Testing Checklist

  • Shortcode renderiza sin atributos
  • Filtro por categoria funciona
  • Filtro por multiples categorias funciona
  • Exclusion de categoria funciona
  • Filtro por tag funciona
  • Columnas 1, 2, 3, 4 funcionan
  • Paginacion con id unico funciona
  • Multiples shortcodes paginados funcionan
  • CSS se aplica desde settings de post-grid
  • Mensaje "sin posts" aparece cuando corresponde
  • Escaping correcto en todo el HTML
  • Funciona en editor clasico
  • Funciona en Gutenberg
  • No hay errores PHP
  • Clases tienen menos de 150 lineas