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,74 @@
<?php
declare(strict_types=1);
namespace ROITheme\Admin\CustomCSSManager\Domain\ValueObjects;
use ROITheme\Shared\Domain\Exceptions\ValidationException;
/**
* Value Object para código CSS validado
*/
final class CSSCode
{
private const MAX_SIZE_CRITICAL = 14336; // 14KB para CSS crítico
private const MAX_SIZE_DEFERRED = 102400; // 100KB para CSS diferido
private function __construct(
private readonly string $value
) {}
public static function fromString(string $css): self
{
$sanitized = self::sanitize($css);
self::validate($sanitized);
return new self($sanitized);
}
private static function sanitize(string $css): string
{
// Eliminar etiquetas <style>
$css = preg_replace('/<\/?style[^>]*>/i', '', $css);
// Eliminar comentarios HTML
$css = preg_replace('/<!--.*?-->/s', '', $css);
return trim($css);
}
private static function validate(string $css): void
{
// Detectar código potencialmente peligroso
$dangerous = ['javascript:', 'expression(', '@import', 'behavior:'];
foreach ($dangerous as $pattern) {
if (stripos($css, $pattern) !== false) {
throw new ValidationException("CSS contiene patrón no permitido: {$pattern}");
}
}
}
public function validateForLoadType(LoadType $loadType): void
{
$maxSize = $loadType->isCritical()
? self::MAX_SIZE_CRITICAL
: self::MAX_SIZE_DEFERRED;
if (strlen($this->value) > $maxSize) {
throw new ValidationException(
sprintf('CSS excede el tamaño máximo de %d bytes para tipo %s',
$maxSize,
$loadType->value()
)
);
}
}
public function value(): string
{
return $this->value;
}
public function isEmpty(): bool
{
return empty($this->value);
}
}