feat(custom-css-manager): implementar TIPO 3 - CSS Crítico Personalizado
Nuevo sistema de gestión de CSS personalizado con panel admin: - Admin/CustomCSSManager: CRUD de snippets CSS (crítico/diferido) - Public/CustomCSSManager: Inyección dinámica en frontend - Schema JSON para configuración del componente Migración de CSS estático a BD: - Tablas APU (~14KB) → snippet diferido en BD - Tablas Genéricas (~10KB) → snippet diferido en BD - Comentadas funciones legacy en enqueue-scripts.php Limpieza de archivos obsoletos: - Eliminado build-bootstrap-subset.js - Eliminado migrate-legacy-options.php - Eliminado minify-css.php - Eliminado purgecss.config.js Beneficios: - CSS editable desde admin sin tocar código - Soporte crítico (head) y diferido (footer) - Filtrado por scope (all/home/single/archive) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Public\CustomCSSManager\Application\UseCases;
|
||||
|
||||
use ROITheme\Shared\Domain\Contracts\CSSSnippetRepositoryInterface;
|
||||
|
||||
/**
|
||||
* Caso de uso: Obtener snippets críticos para página actual
|
||||
*
|
||||
* Contexto: Public (renderizado)
|
||||
*/
|
||||
final class GetCriticalSnippetsUseCase
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CSSSnippetRepositoryInterface $repository
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param string $pageType Tipo de página actual
|
||||
* @return array<array> Snippets críticos aplicables
|
||||
*/
|
||||
public function execute(string $pageType): array
|
||||
{
|
||||
return $this->repository->getForPage('critical', $pageType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Public\CustomCSSManager\Application\UseCases;
|
||||
|
||||
use ROITheme\Shared\Domain\Contracts\CSSSnippetRepositoryInterface;
|
||||
|
||||
/**
|
||||
* Caso de uso: Obtener snippets diferidos para página actual
|
||||
*
|
||||
* Contexto: Public (renderizado)
|
||||
* Se ejecuta en wp_footer para cargar CSS no crítico.
|
||||
*/
|
||||
final class GetDeferredSnippetsUseCase
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CSSSnippetRepositoryInterface $repository
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Ejecuta la obtención de snippets diferidos
|
||||
*
|
||||
* @param string $pageType Tipo de página actual ('home', 'posts', 'pages', 'archives', 'all')
|
||||
* @return array<array> Snippets diferidos aplicables a la página
|
||||
*/
|
||||
public function execute(string $pageType): array
|
||||
{
|
||||
return $this->repository->getForPage('deferred', $pageType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Public\CustomCSSManager\Infrastructure\Services;
|
||||
|
||||
use ROITheme\Public\CustomCSSManager\Application\UseCases\GetCriticalSnippetsUseCase;
|
||||
use ROITheme\Public\CustomCSSManager\Application\UseCases\GetDeferredSnippetsUseCase;
|
||||
|
||||
/**
|
||||
* Servicio que inyecta CSS en wp_head y wp_footer
|
||||
*
|
||||
* NO lee archivos CSS físicos - todo viene de BD
|
||||
*/
|
||||
final class CustomCSSInjector
|
||||
{
|
||||
public function __construct(
|
||||
private readonly GetCriticalSnippetsUseCase $getCriticalUseCase,
|
||||
private readonly GetDeferredSnippetsUseCase $getDeferredUseCase
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Registra hooks de WordPress
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
// CSS crítico: priority 2 (después de componentes, antes de theme-settings)
|
||||
add_action('wp_head', [$this, 'injectCriticalCSS'], 2);
|
||||
|
||||
// CSS diferido: priority alta en footer
|
||||
add_action('wp_footer', [$this, 'injectDeferredCSS'], 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inyecta CSS crítico inline en <head>
|
||||
*/
|
||||
public function injectCriticalCSS(): void
|
||||
{
|
||||
$pageType = $this->getCurrentPageType();
|
||||
$snippets = $this->getCriticalUseCase->execute($pageType);
|
||||
|
||||
if (empty($snippets)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$css = $this->combineSnippets($snippets);
|
||||
|
||||
if (!empty($css)) {
|
||||
printf(
|
||||
'<style id="roi-custom-critical-css">%s</style>' . "\n",
|
||||
$css
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inyecta CSS diferido en footer
|
||||
*/
|
||||
public function injectDeferredCSS(): void
|
||||
{
|
||||
$pageType = $this->getCurrentPageType();
|
||||
$snippets = $this->getDeferredUseCase->execute($pageType);
|
||||
|
||||
if (empty($snippets)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$css = $this->combineSnippets($snippets);
|
||||
|
||||
if (!empty($css)) {
|
||||
printf(
|
||||
'<style id="roi-custom-deferred-css">%s</style>' . "\n",
|
||||
$css
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combina múltiples snippets en un solo string CSS
|
||||
*
|
||||
* Aplica sanitización para prevenir inyección de HTML malicioso.
|
||||
*/
|
||||
private function combineSnippets(array $snippets): string
|
||||
{
|
||||
$parts = [];
|
||||
|
||||
foreach ($snippets as $snippet) {
|
||||
if (!empty($snippet['css'])) {
|
||||
// Sanitizar CSS: eliminar tags HTML/script
|
||||
$cleanCSS = wp_strip_all_tags($snippet['css']);
|
||||
|
||||
// Eliminar caracteres potencialmente peligrosos
|
||||
$cleanCSS = preg_replace('/<[^>]*>/', '', $cleanCSS);
|
||||
|
||||
$cleanName = sanitize_text_field($snippet['name'] ?? $snippet['id']);
|
||||
|
||||
$parts[] = sprintf(
|
||||
"/* %s */\n%s",
|
||||
$cleanName,
|
||||
$cleanCSS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return implode("\n\n", $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detecta tipo de página actual
|
||||
*/
|
||||
private function getCurrentPageType(): string
|
||||
{
|
||||
if (is_front_page() || is_home()) {
|
||||
return 'home';
|
||||
}
|
||||
if (is_single()) {
|
||||
return 'posts';
|
||||
}
|
||||
if (is_page()) {
|
||||
return 'pages';
|
||||
}
|
||||
if (is_archive() || is_category() || is_tag()) {
|
||||
return 'archives';
|
||||
}
|
||||
return 'all';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user