via wp_head (priority 0, antes de CriticalCSSService)
* - Cachear contenido para evitar lecturas repetidas
*
* FLUJO:
* 1. wp_head (priority 0) → render()
* 2. Lee Assets/css/critical-bootstrap.css
* 3. Output:
*
* IMPORTANTE:
* - Este servicio se ejecuta ANTES de CriticalCSSService (priority 0 vs 1)
* - Bootstrap completo se carga diferido (media="print" + onload)
*
* @package ROITheme\Shared\Infrastructure\Services
*/
final class CriticalBootstrapService
{
/**
* Instancia singleton
*/
private static ?self $instance = null;
/**
* Cache del contenido CSS
*/
private ?string $cssCache = null;
/**
* Ruta al archivo CSS crítico
*/
private string $cssFilePath;
/**
* Constructor privado (singleton)
*/
private function __construct()
{
$this->cssFilePath = get_template_directory() . '/Assets/Css/critical-bootstrap.css';
}
/**
* Obtiene la instancia singleton
*
* @return self
*/
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Renderiza Critical Bootstrap CSS en wp_head
*
* Este método se llama desde wp_head con priority 0 (muy temprano).
* Inyecta el subset de Bootstrap antes del CSS de componentes.
*
* @return void
*/
public function render(): void
{
$css = $this->getCriticalCSS();
if (empty($css)) {
return;
}
// Minificar CSS removiendo comentarios y espacios innecesarios
$minifiedCSS = $this->minifyCSS($css);
printf(
'' . "\n",
$minifiedCSS
);
}
/**
* Obtiene el contenido del CSS crítico
*
* @return string CSS crítico o string vacío si no existe
*/
private function getCriticalCSS(): string
{
// Usar cache si existe
if ($this->cssCache !== null) {
return $this->cssCache;
}
// Verificar que el archivo existe
if (!file_exists($this->cssFilePath)) {
error_log('ROI Theme: Critical Bootstrap CSS file not found: ' . $this->cssFilePath);
$this->cssCache = '';
return '';
}
// Leer contenido
$content = file_get_contents($this->cssFilePath);
if ($content === false) {
error_log('ROI Theme: Failed to read Critical Bootstrap CSS');
$this->cssCache = '';
return '';
}
$this->cssCache = $content;
return $content;
}
/**
* Minifica CSS removiendo comentarios y espacios innecesarios
*
* @param string $css CSS a minificar
* @return string CSS minificado
*/
private function minifyCSS(string $css): string
{
// Remover comentarios
$css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $css);
// Remover espacios, tabs y newlines
$css = str_replace(["\r\n", "\r", "\n", "\t"], '', $css);
// Remover espacios múltiples
$css = preg_replace('/\s+/', ' ', $css);
// Remover espacios alrededor de caracteres especiales
$css = preg_replace('/\s*([{}:;,>+~])\s*/', '$1', $css);
// Remover último punto y coma antes de }
$css = str_replace(';}', '}', $css);
return trim($css);
}
}