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,163 @@
<?php
declare(strict_types=1);
namespace ROITheme\Admin\CustomCSSManager\Infrastructure\Persistence;
use ROITheme\Shared\Domain\Contracts\CSSSnippetRepositoryInterface;
/**
* Repositorio WordPress para snippets CSS
*
* Almacena snippets como JSON en wp_roi_theme_component_settings
*/
final class WordPressSnippetRepository implements CSSSnippetRepositoryInterface
{
private const COMPONENT_NAME = 'custom-css-manager';
private const GROUP_NAME = 'css_snippets';
private const ATTRIBUTE_NAME = 'snippets_json';
public function __construct(
private readonly \wpdb $wpdb
) {}
public function getAll(): array
{
$tableName = $this->wpdb->prefix . 'roi_theme_component_settings';
$sql = $this->wpdb->prepare(
"SELECT attribute_value FROM {$tableName}
WHERE component_name = %s
AND group_name = %s
AND attribute_name = %s
LIMIT 1",
self::COMPONENT_NAME,
self::GROUP_NAME,
self::ATTRIBUTE_NAME
);
$json = $this->wpdb->get_var($sql);
if (empty($json)) {
return [];
}
$snippets = json_decode($json, true);
return is_array($snippets) ? $snippets : [];
}
public function getByLoadType(string $loadType): array
{
$all = $this->getAll();
$filtered = array_filter($all, function ($snippet) use ($loadType) {
return ($snippet['type'] ?? '') === $loadType
&& ($snippet['enabled'] ?? false) === true;
});
// Reindexar para evitar keys dispersas [0,2,5] → [0,1,2]
return array_values($filtered);
}
public function getForPage(string $loadType, string $pageType): array
{
$snippets = $this->getByLoadType($loadType);
$filtered = array_filter($snippets, function ($snippet) use ($pageType) {
$pages = $snippet['pages'] ?? ['all'];
return in_array('all', $pages, true)
|| in_array($pageType, $pages, true);
});
// Reindexar para evitar keys dispersas
return array_values($filtered);
}
public function save(array $snippet): void
{
$all = $this->getAll();
// Actualizar o agregar
$found = false;
foreach ($all as &$existing) {
if ($existing['id'] === $snippet['id']) {
$existing = $snippet;
$found = true;
break;
}
}
if (!$found) {
$all[] = $snippet;
}
// Ordenar por 'order'
usort($all, fn($a, $b) => ($a['order'] ?? 100) <=> ($b['order'] ?? 100));
$this->persist($all);
}
public function delete(string $snippetId): void
{
$all = $this->getAll();
$filtered = array_filter($all, fn($s) => $s['id'] !== $snippetId);
$this->persist(array_values($filtered));
}
/**
* Persiste la lista de snippets en BD
*
* Usa update() + insert() para consistencia con patrón existente.
* NOTA: NO usa replace() porque:
* - Preserva ID autoincremental
* - Preserva campos como is_editable, created_at
*
* @param array $snippets Lista de snippets a persistir
*/
private function persist(array $snippets): void
{
$tableName = $this->wpdb->prefix . 'roi_theme_component_settings';
$json = wp_json_encode($snippets, JSON_UNESCAPED_UNICODE);
// Verificar si el registro existe
$exists = $this->wpdb->get_var($this->wpdb->prepare(
"SELECT COUNT(*) FROM {$tableName}
WHERE component_name = %s
AND group_name = %s
AND attribute_name = %s",
self::COMPONENT_NAME,
self::GROUP_NAME,
self::ATTRIBUTE_NAME
));
if ($exists > 0) {
// UPDATE existente (preserva id, created_at, is_editable)
$this->wpdb->update(
$tableName,
['attribute_value' => $json],
[
'component_name' => self::COMPONENT_NAME,
'group_name' => self::GROUP_NAME,
'attribute_name' => self::ATTRIBUTE_NAME,
],
['%s'],
['%s', '%s', '%s']
);
} else {
// INSERT nuevo
$this->wpdb->insert(
$tableName,
[
'component_name' => self::COMPONENT_NAME,
'group_name' => self::GROUP_NAME,
'attribute_name' => self::ATTRIBUTE_NAME,
'attribute_value' => $json,
'is_editable' => 1,
],
['%s', '%s', '%s', '%s', '%d']
);
}
}
}