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:
FrankZamora
2025-12-01 15:43:25 -06:00
parent 423aae062c
commit 9cb0dd1491
24 changed files with 1553 additions and 784 deletions

View File

@@ -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';
}
}