Files
roi-theme/functions-addon.php
FrankZamora 6004420620 fix: eliminate forced reflows in TOC ScrollSpy + revert Bootstrap defer
- Replace scroll event listener with Intersection Observer in TableOfContentsRenderer
- Eliminates ~100ms forced reflows from offsetTop reads during scroll
- Revert Bootstrap CSS to blocking (media='all') - deferring caused CLS 0.954
- Keep CriticalBootstrapService available for future optimization
- Simplify CriticalCSSHooksRegistrar to only use CriticalCSSService

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 10:52:25 -06:00

307 lines
12 KiB
PHP

<?php
// =============================================================================
// AUTOLOADER PARA COMPONENTES
// =============================================================================
spl_autoload_register(function ($class) {
// Mapeo de namespaces a directorios
$prefixes = [
'ROITheme\\Shared\\' => get_template_directory() . '/Shared/',
'ROITheme\\Public\\' => get_template_directory() . '/Public/',
'ROITheme\\Admin\\' => get_template_directory() . '/Admin/',
];
foreach ($prefixes as $prefix => $base_dir) {
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) === 0) {
$relative_class = substr($class, $len);
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
if (file_exists($file)) {
require $file;
return;
}
}
}
});
// =============================================================================
// HELPER FUNCTION: roi_get_component_setting() - GENÉRICA
// =============================================================================
/**
* Obtiene un valor de configuración de cualquier componente desde la BD
*
* Reemplaza a roi_get_option() legacy - lee de wp_roi_theme_component_settings
*
* @param string $component Nombre del componente (ej: 'featured-image', 'navbar')
* @param string $group Nombre del grupo (ej: 'visibility', 'content')
* @param string $attribute Nombre del atributo (ej: 'is_enabled', 'show_on_pages')
* @param mixed $default Valor por defecto si no existe
* @return mixed Valor del atributo
*/
function roi_get_component_setting(string $component, string $group, string $attribute, $default = null) {
global $wpdb;
$table = $wpdb->prefix . 'roi_theme_component_settings';
$value = $wpdb->get_var($wpdb->prepare(
"SELECT attribute_value FROM {$table}
WHERE component_name = %s
AND group_name = %s
AND attribute_name = %s",
$component,
$group,
$attribute
));
if ($value === null) {
return $default;
}
// Convertir booleanos
if ($value === '1') return true;
if ($value === '0') return false;
// Intentar decodificar JSON
$decoded = json_decode($value, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
return $decoded;
}
return $value;
}
// =============================================================================
// HELPER FUNCTION: roi_get_navbar_setting()
// =============================================================================
/**
* Obtiene un valor de configuración del navbar desde la BD
*
* @param string $group Nombre del grupo (ej: 'media', 'visibility')
* @param string $attribute Nombre del atributo (ej: 'show_brand', 'logo_url')
* @param mixed $default Valor por defecto si no existe
* @return mixed Valor del atributo
*/
function roi_get_navbar_setting(string $group, string $attribute, $default = null) {
global $wpdb;
$table = $wpdb->prefix . 'roi_theme_component_settings';
$value = $wpdb->get_var($wpdb->prepare(
"SELECT attribute_value FROM {$table}
WHERE component_name = 'navbar'
AND group_name = %s
AND attribute_name = %s",
$group,
$attribute
));
if ($value === null) {
return $default;
}
// Convertir booleanos
if ($value === '1') return true;
if ($value === '0') return false;
// Intentar decodificar JSON
$decoded = json_decode($value, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
return $decoded;
}
return $value;
}
// =============================================================================
// CRITICAL CSS SERVICE SINGLETON
// =============================================================================
/**
* Obtiene la instancia singleton del CriticalCSSService
*
* Este servicio consulta la BD para componentes con is_critical=true
* y genera su CSS en wp_head ANTES de que los componentes rendericen.
*
* @return \ROITheme\Shared\Infrastructure\Services\CriticalCSSService
*/
function roi_get_critical_css_service(): \ROITheme\Shared\Infrastructure\Services\CriticalCSSService {
global $wpdb;
static $cssGenerator = null;
if ($cssGenerator === null) {
$cssGenerator = new \ROITheme\Shared\Infrastructure\Services\CSSGeneratorService();
}
return \ROITheme\Shared\Infrastructure\Services\CriticalCSSService::getInstance($wpdb, $cssGenerator);
}
// =============================================================================
// HELPER FUNCTION: roi_render_component()
// =============================================================================
/**
* Renderiza un componente por su nombre
*
* @param string $componentName Nombre del componente
* @return string HTML del componente renderizado
*/
function roi_render_component(string $componentName): string {
global $wpdb;
// DEBUG: Trace component rendering
error_log("ROI Theme DEBUG: roi_render_component called with: {$componentName}");
try {
// Obtener datos del componente desde BD normalizada
$table = $wpdb->prefix . 'roi_theme_component_settings';
$rows = $wpdb->get_results($wpdb->prepare(
"SELECT group_name, attribute_name, attribute_value
FROM {$table}
WHERE component_name = %s
ORDER BY group_name, attribute_name",
$componentName
));
if (empty($rows)) {
return '';
}
// Reconstruir estructura de datos agrupada
$data = [];
foreach ($rows as $row) {
if (!isset($data[$row->group_name])) {
$data[$row->group_name] = [];
}
// Decodificar valor
$value = $row->attribute_value;
// Convertir booleanos almacenados como '1' o '0'
if ($value === '1' || $value === '0') {
$value = ($value === '1');
} else {
// Intentar decodificar JSON
$decoded = json_decode($value, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
$value = $decoded;
}
}
$data[$row->group_name][$row->attribute_name] = $value;
}
// Crear Value Objects requeridos
$name = new \ROITheme\Shared\Domain\ValueObjects\ComponentName($componentName);
$configuration = new \ROITheme\Shared\Domain\ValueObjects\ComponentConfiguration($data);
$visibility = \ROITheme\Shared\Domain\ValueObjects\ComponentVisibility::allDevices(); // Default: visible en todas partes
// Crear instancia del componente
$component = new \ROITheme\Shared\Domain\Entities\Component(
$name,
$configuration,
$visibility
);
// Obtener renderer específico para el componente
$renderer = null;
// Crear instancia del CSSGeneratorService (reutilizable para todos los renderers)
$cssGenerator = new \ROITheme\Shared\Infrastructure\Services\CSSGeneratorService();
switch ($componentName) {
// Componentes con soporte de CSS Crítico (above-the-fold)
// Nota: Si is_critical=true, el CSS ya fue inyectado en <head> por CriticalCSSService
case 'top-notification-bar':
$renderer = new \ROITheme\Public\TopNotificationBar\Infrastructure\Ui\TopNotificationBarRenderer($cssGenerator);
break;
case 'navbar':
$renderer = new \ROITheme\Public\Navbar\Infrastructure\Ui\NavbarRenderer($cssGenerator);
break;
case 'hero':
error_log("ROI Theme DEBUG: Creating HeroRenderer");
$renderer = new \ROITheme\Public\Hero\Infrastructure\Ui\HeroRenderer($cssGenerator);
error_log("ROI Theme DEBUG: HeroRenderer created successfully");
break;
// Componentes sin soporte de CSS Crítico (below-the-fold)
case 'cta-lets-talk':
$renderer = new \ROITheme\Public\CtaLetsTalk\Infrastructure\Ui\CtaLetsTalkRenderer($cssGenerator);
break;
case 'hero-section':
$renderer = new \ROITheme\Public\HeroSection\Infrastructure\Ui\HeroSectionRenderer();
break;
case 'featured-image':
$renderer = new \ROITheme\Public\FeaturedImage\Infrastructure\Ui\FeaturedImageRenderer($cssGenerator);
break;
case 'table-of-contents':
$renderer = new \ROITheme\Public\TableOfContents\Infrastructure\Ui\TableOfContentsRenderer($cssGenerator);
break;
case 'cta-box-sidebar':
$renderer = new \ROITheme\Public\CtaBoxSidebar\Infrastructure\Ui\CtaBoxSidebarRenderer($cssGenerator);
break;
case 'social-share':
$renderer = new \ROITheme\Public\SocialShare\Infrastructure\Ui\SocialShareRenderer($cssGenerator);
break;
case 'cta-post':
$renderer = new \ROITheme\Public\CtaPost\Infrastructure\Ui\CtaPostRenderer($cssGenerator);
break;
case 'related-post':
$renderer = new \ROITheme\Public\RelatedPost\Infrastructure\Ui\RelatedPostRenderer($cssGenerator);
break;
case 'contact-form':
$renderer = new \ROITheme\Public\ContactForm\Infrastructure\Ui\ContactFormRenderer($cssGenerator);
break;
case 'footer':
$renderer = new \ROITheme\Public\Footer\Infrastructure\Ui\FooterRenderer($cssGenerator);
break;
}
if (!$renderer) {
error_log("ROI Theme DEBUG: No renderer for {$componentName}");
return '';
}
error_log("ROI Theme DEBUG: Calling render() for {$componentName}");
$output = $renderer->render($component);
error_log("ROI Theme DEBUG: render() returned " . strlen($output) . " chars for {$componentName}");
return $output;
} catch (\Exception $e) {
// Always log errors for debugging
error_log('ROI Theme ERROR: Exception rendering component ' . $componentName . ': ' . $e->getMessage());
error_log('ROI Theme ERROR: Stack trace: ' . $e->getTraceAsString());
return '';
}
}
// =============================================================================
// REGISTRO DE CRITICAL CSS HOOKS
// =============================================================================
/**
* Registra hooks para inyectar CSS crítico en <head>
*
* FLUJO:
* 1. wp_head (priority 1) → CriticalCSSService::render()
* - Consulta BD por componentes con is_critical=true
* - Genera CSS usando los métodos públicos generateCSS() de los Renderers
* - Output: <style id="roi-critical-css">...</style>
*
* NOTA: CriticalBootstrapService está DESHABILITADO porque diferir
* Bootstrap causa CLS alto (0.954). Bootstrap carga bloqueante.
*
* Cuando los Renderers ejecutan, detectan is_critical y omiten CSS inline.
*/
add_action('after_setup_theme', function() {
$criticalCSSService = roi_get_critical_css_service();
$hooksRegistrar = new \ROITheme\Shared\Infrastructure\Wordpress\CriticalCSSHooksRegistrar($criticalCSSService);
$hooksRegistrar->register();
});
// =============================================================================
// NOTA: Los estilos de TOC y CTA Box Sidebar se generan dinámicamente
// desde la base de datos a través de sus respectivos Renderers.
// NO hardcodear CSS aquí - viola la arquitectura Clean Architecture.
// =============================================================================