Files
roi-theme/functions.php
FrankZamora 5971f2c971 feat(js): switch to eager loading to eliminate layout shift
Instead of lazy loading slots when they enter the viewport (which
causes layout shift when unfilled slots collapse), now all slots
are activated immediately on page load.

- Slots start collapsed (max-height:0, opacity:0) via CSS
- All slots are activated with 100ms stagger to avoid rate limiting
- Only slots confirmed as 'filled' expand and become visible
- Unfilled slots remain collapsed - zero layout shift

This completely eliminates the CLS issue where content would jump
when ad slots were hidden after entering the viewport.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-10 17:11:08 -06:00

470 lines
18 KiB
PHP

<?php
declare(strict_types=1);
/**
* ROI Theme - Clean Architecture Bootstrap
*
* Este archivo es el punto de entrada del tema.
* Solo contiene bootstrap (carga del autoloader e inicialización del DI Container).
* TODO el código de lógica de negocio está en admin/, public/, shared/
*
* @package ROITheme
* @version 1.0.0
*/
// Prevenir acceso directo
if (!defined('ABSPATH')) {
exit;
}
// Definir constante de versión del tema
define('ROI_VERSION', '1.0.26');
// =============================================================================
// 1. CARGAR AUTOLOADER MANUAL
// =============================================================================
// IMPORTANTE: Cargar functions-addon.php PRIMERO para registrar el autoloader
// que permite cargar clases de ROITheme automáticamente
require_once __DIR__ . '/functions-addon.php';
// =============================================================================
// 2. CARGAR ARCHIVOS DE INC/ (ANTES DE LA CONFIGURACIÓN)
// =============================================================================
// IMPORTANTE: Cargar archivos inc/ ANTES del DI Container
// para que los estilos y scripts se registren correctamente
require_once get_template_directory() . '/Inc/sanitize-functions.php';
// ELIMINADO: Inc/theme-options-helpers.php (FASE 6 - Clean Architecture)
require_once get_template_directory() . '/Inc/nav-walker.php';
require_once get_template_directory() . '/Inc/enqueue-scripts.php';
// ELIMINADO: Inc/customizer-fonts.php (FASE 6 - Clean Architecture)
require_once get_template_directory() . '/Inc/seo.php';
require_once get_template_directory() . '/Inc/performance.php';
// ELIMINADO: Inc/critical-css.php (CSS crítico ahora viene de CriticalCSSService.php)
require_once get_template_directory() . '/Inc/image-optimization.php';
require_once get_template_directory() . '/Inc/template-functions.php';
require_once get_template_directory() . '/Inc/template-tags.php';
require_once get_template_directory() . '/Inc/featured-image.php';
require_once get_template_directory() . '/Inc/category-badge.php';
require_once get_template_directory() . '/Inc/adsense-delay.php';
require_once get_template_directory() . '/Inc/adsense-placement.php';
// ELIMINADO: Inc/related-posts.php (Plan 101 - usa RelatedPostRenderer)
// ELIMINADO: Inc/toc.php (FASE 6 - Clean Architecture: usa TableOfContentsRenderer)
require_once get_template_directory() . '/Inc/apu-tables.php';
require_once get_template_directory() . '/Inc/search-disable.php';
require_once get_template_directory() . '/Inc/comments-disable.php';
require_once get_template_directory() . '/Inc/social-share.php';
// =============================================================================
// 3. INICIALIZAR DI CONTAINER (Clean Architecture)
// =============================================================================
use ROITheme\Shared\Infrastructure\Di\DIContainer;
// Declarar $container como global para que esté disponible en Inc/*.php
global $container;
try {
global $wpdb;
// Path a los schemas
$schemas_path = get_template_directory() . '/schemas';
// Crear instancia del DI Container con dependencias
$container = new DIContainer($wpdb, $schemas_path);
// TODO: Inicializar controladores cuando estén implementados (Fase 5+)
// $ajaxController = $container->getAjaxController();
} catch (\Throwable $e) {
// Manejar errores de inicialización (no crítico, solo log)
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('ROI Theme: Failed to initialize DI Container: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
}
// NO hacer return - permitir que el tema continúe funcionando
$container = null;
}
// =============================================================================
// 3.1. INICIALIZAR PANEL DE ADMINISTRACIÓN (Clean Architecture)
// =============================================================================
use ROITheme\Admin\Domain\ValueObjects\MenuItem;
use ROITheme\Admin\Application\UseCases\RenderDashboardUseCase;
use ROITheme\Admin\Infrastructure\Ui\AdminDashboardRenderer;
use ROITheme\Admin\Infrastructure\Api\WordPress\AdminMenuRegistrar;
use ROITheme\Admin\Infrastructure\Services\AdminAssetEnqueuer;
try {
// Obtener Use Case para cargar configuraciones
$getComponentSettingsUseCase = $container?->getGetComponentSettingsUseCase();
// Crear MenuItem con configuración del panel
$menuItem = new MenuItem(
pageTitle: 'ROI Theme - Panel de Administración',
menuTitle: 'ROI Theme',
capability: 'manage_options',
menuSlug: 'roi-theme-admin',
icon: 'dashicons-admin-settings',
position: 60
);
// Crear GroupRegistry para la nueva UI de Cards/Grupos
$groupRegistry = new \ROITheme\Admin\Infrastructure\Ui\ComponentGroupRegistry();
// Crear renderer del dashboard con inyección del Use Case y GroupRegistry
$dashboardRenderer = new AdminDashboardRenderer($getComponentSettingsUseCase, $groupRegistry);
// Crear caso de uso para renderizar
$renderDashboardUseCase = new RenderDashboardUseCase($dashboardRenderer);
// Crear y registrar el menú de administración
$adminMenuRegistrar = new AdminMenuRegistrar($menuItem, $renderDashboardUseCase);
$adminMenuRegistrar->register();
// Crear y registrar el enqueuer de assets
$adminAssetEnqueuer = new AdminAssetEnqueuer(get_template_directory_uri());
$adminAssetEnqueuer->register();
// Obtener Use Case para guardar configuraciones
$saveComponentSettingsUseCase = $container?->getSaveComponentSettingsUseCase();
// === FIELD MAPPER REGISTRY (Auto-registro escalable) ===
$fieldMapperRegistry = new \ROITheme\Admin\Shared\Infrastructure\FieldMapping\FieldMapperRegistry();
$fieldMapperProvider = new \ROITheme\Admin\Shared\Infrastructure\FieldMapping\FieldMapperProvider($fieldMapperRegistry);
$fieldMapperProvider->registerAll();
// === ADMIN AJAX HANDLER ===
$adminAjaxHandler = new \ROITheme\Admin\Shared\Infrastructure\Api\WordPress\AdminAjaxHandler(
$saveComponentSettingsUseCase,
$fieldMapperRegistry
);
$adminAjaxHandler->register();
// Crear y registrar el handler AJAX para el Contact Form (público)
$contactFormAjaxHandler = new \ROITheme\Public\ContactForm\Infrastructure\Api\WordPress\ContactFormAjaxHandler(
$container->getComponentSettingsRepository()
);
$contactFormAjaxHandler->register();
// Crear y registrar el handler AJAX para Newsletter (público)
$newsletterAjaxHandler = new \ROITheme\Public\Footer\Infrastructure\Api\WordPress\NewsletterAjaxHandler(
$container->getComponentSettingsRepository()
);
$newsletterAjaxHandler->register();
// Crear y registrar el inyector de Theme Settings (GA, Custom CSS/JS)
$themeSettingsRenderer = new \ROITheme\Public\ThemeSettings\Infrastructure\Ui\ThemeSettingsRenderer();
$themeSettingsInjector = new \ROITheme\Public\ThemeSettings\Infrastructure\Services\ThemeSettingsInjector(
$container->getComponentSettingsRepository(),
$themeSettingsRenderer
);
$themeSettingsInjector->register();
// === YOUTUBE FACADE (PageSpeed Optimization Phase 2.4) ===
// Lazy-load YouTube iframes to reduce TBT by ~2000ms
$youtubeFacadeRenderer = new \ROITheme\Public\YoutubeFacade\Infrastructure\Ui\YoutubeFacadeRenderer();
$youtubeFacadeFilter = new \ROITheme\Public\YoutubeFacade\Infrastructure\Services\YoutubeFacadeContentFilter(
$youtubeFacadeRenderer
);
$youtubeFacadeHooksRegistrar = new \ROITheme\Public\YoutubeFacade\Infrastructure\Wordpress\YoutubeFacadeHooksRegistrar(
$youtubeFacadeFilter
);
$youtubeFacadeHooksRegistrar->register();
// === CACHE-FIRST ARCHITECTURE (Plan 1000.01) ===
// Hook para plugins externos que necesitan evaluar acceso antes de servir página
// @see openspec/specs/cache-first-architecture/spec.md
$cacheFirstHooksRegistrar = new \ROITheme\Shared\Infrastructure\Hooks\CacheFirstHooksRegistrar();
$cacheFirstHooksRegistrar->register();
// Log en modo debug
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('ROI Theme: Admin Panel initialized successfully');
}
} catch (\Throwable $e) {
// Manejar errores de inicialización del panel
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('ROI Theme: Failed to initialize Admin Panel: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
}
}
// =============================================================================
// 4. CONFIGURACIÓN DEL TEMA
// =============================================================================
/**
* Setup del tema
*
* Configuraciones básicas de WordPress theme support
*/
add_action('after_setup_theme', function() {
// Soporte para título del documento
add_theme_support('title-tag');
// Soporte para imágenes destacadas
add_theme_support('post-thumbnails');
// Soporte para HTML5
add_theme_support('html5', [
'search-form',
'comment-form',
'comment-list',
'gallery',
'caption',
'style',
'script'
]);
// Soporte para feeds automáticos
add_theme_support('automatic-feed-links');
// Registro de ubicaciones de menús
register_nav_menus([
'primary' => __('Primary Menu', 'roi-theme'),
'footer' => __('Footer Menu', 'roi-theme'),
'footer_menu_1' => __('Footer Menu 1 (Widget 1)', 'roi-theme'),
'footer_menu_2' => __('Footer Menu 2 (Widget 2)', 'roi-theme'),
'footer_menu_3' => __('Footer Menu 3 (Widget 3)', 'roi-theme'),
'footer_menu_4' => __('Footer Menu 4 (Widget 1B - Bases de datos)', 'roi-theme'),
]);
// TODO: Agregar más configuraciones según sea necesario
});
// =============================================================================
// 5. HOOKS DE INICIALIZACIÓN (Para fases posteriores)
// =============================================================================
/**
* Hook para sincronización de schemas
* TODO: Implementar en Fase 6
*/
// add_action('admin_init', function() use ($container) {
// $syncService = $container->getSchemaSyncService();
// // Verificar si hay schemas desactualizados
// });
/**
* Hook para detección de schemas desactualizados
* TODO: Implementar en Fase 6
*/
// add_action('admin_notices', function() use ($container) {
// // Mostrar aviso si hay schemas desactualizados
// });
// =============================================================================
// 5. RENDERIZAR MODAL DE CONTACTO (Let's Talk)
// =============================================================================
/**
* Renderizar modal de contacto en el footer
* Usa la misma configuracion que contact-form (mismo webhook)
*/
add_action('wp_footer', function() use ($container) {
if ($container === null) {
return;
}
try {
// Obtener configuracion del contact-form
$repository = $container->getComponentSettingsRepository();
$settings = $repository->getComponentSettings('contact-form');
if (empty($settings)) {
return;
}
// Crear Component entity
$componentName = new \ROITheme\Shared\Domain\ValueObjects\ComponentName('contact-form');
$component = new \ROITheme\Shared\Domain\Entities\Component(
name: $componentName,
configuration: \ROITheme\Shared\Domain\ValueObjects\ComponentConfiguration::fromArray($settings),
visibility: \ROITheme\Shared\Domain\ValueObjects\ComponentVisibility::allDevices()
);
// Crear renderer y renderizar modal
$cssGenerator = new \ROITheme\Shared\Infrastructure\Services\CSSGeneratorService();
$renderer = new \ROITheme\Public\ContactForm\Infrastructure\Ui\ContactFormRenderer($cssGenerator);
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $renderer->renderModal($component);
} catch (\Throwable $e) {
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('ROI Theme: Failed to render contact modal: ' . $e->getMessage());
}
}
}, 99); // Prioridad alta para que se renderice al final del footer
// =============================================================================
// 5.2. CUSTOM CSS MANAGER (TIPO 3: CSS Crítico Personalizado)
// =============================================================================
/**
* Bootstrap CustomCSSManager
*
* Inicializa el sistema de CSS personalizado configurable.
* - Frontend: Inyecta CSS crítico (head) y diferido (footer)
* - Admin: El FormBuilder se auto-registra cuando es instanciado por el dashboard
*/
// Registrar hooks de inyección CSS directamente (sin wrapper)
if (!is_admin()) {
try {
global $wpdb;
$repository = new \ROITheme\Admin\CustomCSSManager\Infrastructure\Persistence\WordPressSnippetRepository($wpdb);
$getCriticalUseCase = new \ROITheme\Public\CustomCSSManager\Application\UseCases\GetCriticalSnippetsUseCase($repository);
$getDeferredUseCase = new \ROITheme\Public\CustomCSSManager\Application\UseCases\GetDeferredSnippetsUseCase($repository);
$injector = new \ROITheme\Public\CustomCSSManager\Infrastructure\Services\CustomCSSInjector(
$getCriticalUseCase,
$getDeferredUseCase
);
$injector->register();
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('ROI Theme: CustomCSSManager hooks registered successfully');
}
} catch (\Throwable $e) {
error_log('ROI Theme: CustomCSSManager FAILED: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
}
}
// =============================================================================
// 5.2.1. CUSTOM CSS MANAGER BOOTSTRAP (Handler de formulario POST)
// =============================================================================
/**
* Inicializar Bootstrap de CustomCSSManager para admin
*
* IMPORTANTE: Este Bootstrap registra el handler de formulario POST en admin_init,
* ANTES de que WordPress envíe headers HTTP. Esto permite que wp_redirect()
* funcione correctamente después de guardar/eliminar snippets.
*/
if (is_admin()) {
\ROITheme\Admin\CustomCSSManager\Infrastructure\Bootstrap\CustomCSSManagerBootstrap::init();
}
// =============================================================================
// 5.3. INFORMACIÓN DE DEBUG (Solo en desarrollo)
// =============================================================================
if (defined('WP_DEBUG') && WP_DEBUG) {
// Registrar que el tema se inicializó correctamente
error_log('ROI Theme: Bootstrap completed successfully');
}
// =============================================================================
// 5.4. CRITICAL CSS TIPO 4 (CSS Below-the-fold)
// =============================================================================
/**
* Bootstrap CriticalCSS TIPO 4
*
* Inyecta CSS critico (variables, responsive) inline en wp_head
* antes de cualquier otro CSS para evitar FOUC.
*
* Incluye auto-regeneracion: si los archivos fuente cambian,
* el cache se regenera automaticamente sin intervencion manual.
* Costo: ~0.1ms (2 llamadas a filemtime())
*/
if (!is_admin()) {
$criticalCSSCache = new \ROITheme\Public\CriticalCSS\Infrastructure\Cache\CriticalCSSFileCache();
$criticalCSSExtractor = new \ROITheme\Public\CriticalCSS\Infrastructure\Services\CriticalCSSExtractor(
$criticalCSSCache
);
// Auto-regenerar si archivos fuente cambiaron
if ($criticalCSSExtractor->needsRegeneration()) {
$criticalCSSExtractor->generateAll();
}
$criticalCSSInjector = new \ROITheme\Public\CriticalCSS\Infrastructure\Services\CriticalCSSInjector(
$criticalCSSCache
);
$criticalCSSInjector->register();
}
// Registrar comando WP-CLI (util para forzar regeneracion o limpiar cache)
if (defined('WP_CLI') && WP_CLI) {
require_once get_template_directory() . '/bin/generate-critical-css.php';
}
// =============================================================================
// 5.5. LAZY CSS LOADER TIPO 5 (CSS No Critico)
// =============================================================================
/**
* Bootstrap LazyCSSLoader TIPO 5
*
* Carga CSS no critico de forma lazy:
* - Animaciones: requestIdleCallback o timeout 2s
* - Print: solo cuando usuario va a imprimir (beforeprint event)
*
* @since 1.0.20
*/
if (!is_admin()) {
$lazyCSSRegistrar = new \ROITheme\Public\LazyCSSLoader\Infrastructure\Services\LazyCSSRegistrar();
$lazyCSSRegistrar->register();
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('ROI Theme: TIPO 5 LazyCSSLoader registered');
}
}
// =============================================================================
// 6. INSTALACIÓN DE TABLAS DEL TEMA
// =============================================================================
/**
* Crear tablas del tema en la activación
*
* Este hook se ejecuta cuando el tema se activa en WordPress.
* Crea las tablas necesarias si no existen.
*/
add_action('after_switch_theme', function() {
global $wpdb;
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
$charset_collate = $wpdb->get_charset_collate();
// Tabla de configuración de componentes (normalizada)
$table_settings = $wpdb->prefix . 'roi_theme_component_settings';
$sql_settings = "CREATE TABLE {$table_settings} (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
component_name VARCHAR(100) NOT NULL,
group_name VARCHAR(100) NOT NULL,
attribute_name VARCHAR(100) NOT NULL,
attribute_value LONGTEXT NOT NULL,
is_editable TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY unique_setting (component_name, group_name, attribute_name),
INDEX idx_component (component_name),
INDEX idx_editable (is_editable)
) {$charset_collate};";
// Crear/actualizar tabla
dbDelta($sql_settings);
// Log en modo debug
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('ROI Theme: Database tables created/updated');
}
});
// =============================================================================
// REGISTRO DE COMANDOS WP-CLI
// =============================================================================
if (defined('WP_CLI') && WP_CLI) {
require_once get_template_directory() . '/Shared/Infrastructure/Api/WordPress/MigrationCommand.php';
}