## TIPO 4: CSS Below-the-fold (Critical Variables + Responsive) - Inyecta variables CSS críticas inline en wp_head P:-1 - Inyecta media queries críticas inline en wp_head P:2 (corregido de P:1) - Auto-regeneración cuando archivos fuente cambian (filemtime check) - Cache en Assets/CriticalCSS/ para evitar lecturas repetidas - Comando WP-CLI: wp roi-theme generate-critical-css Archivos TIPO 4: - Public/CriticalCSS/Domain/Contracts/ - Interfaces (DIP) - Public/CriticalCSS/Application/UseCases/GetCriticalCSSUseCase.php - Public/CriticalCSS/Infrastructure/Cache/CriticalCSSFileCache.php - Public/CriticalCSS/Infrastructure/Services/CriticalCSSExtractor.php - Public/CriticalCSS/Infrastructure/Services/CriticalCSSInjector.php - bin/generate-critical-css.php ## TIPO 5: CSS No Crítico (Lazy Loading) - Animaciones CSS: carga 2s después de page load via requestIdleCallback - Print CSS: carga solo al imprimir via beforeprint event - Fallback <noscript> para usuarios sin JavaScript - Safari fallback: setTimeout cuando requestIdleCallback no disponible Archivos TIPO 5: - Assets/Js/lazy-css-loader.js - Public/LazyCSSLoader/Infrastructure/Contracts/LazyCSSRegistrarInterface.php - Public/LazyCSSLoader/Infrastructure/Services/LazyCSSRegistrar.php ## Fix: Colisión de prioridades wp_head Antes: TIPO 1 (P:1), TIPO 4 responsive (P:1), TIPO 3 (P:2) - CONFLICTO Después: TIPO 1 (P:1), TIPO 4 responsive (P:2), TIPO 3 (P:3) - OK Nuevo orden de prioridades: P:-1 roi-critical-variables (TIPO 4) P:0 roi-critical-bootstrap (TIPO 2) P:1 roi-critical-css (TIPO 1) P:2 roi-critical-responsive (TIPO 4) P:3 roi-custom-critical-css (TIPO 3) P:5 roi-theme-layout-css (ThemeSettings) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
128 lines
3.4 KiB
PHP
128 lines
3.4 KiB
PHP
<?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 3 (después de TIPO 4 responsive P:2, antes de theme-settings P:5)
|
|
// NOTA: Cambiado de P:2 a P:3 para evitar colision con roi-critical-responsive
|
|
add_action('wp_head', [$this, 'injectCriticalCSS'], 3);
|
|
|
|
// 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';
|
|
}
|
|
}
|