[ 'css_files' => ['componente-top-bar.css', 'top-notification-bar.css'], 'css_classes' => ['.top-notification-bar', '.top-bar'], ], 'navbar' => [ 'css_files' => ['componente-navbar.css', 'navbar.css'], 'css_classes' => ['.navbar', '.nav-link', '.navbar-brand', '.dropdown-menu'], ], 'cta-lets-talk' => [ 'css_files' => ['componente-boton-lets-talk.css', 'cta-lets-talk.css'], 'css_classes' => ['.btn-lets-talk', '.cta-lets-talk'], ], ]; public function validate(string $componentName, string $themePath): ValidationResult { $result = new ValidationResult(); $result->addInfo("Validando conflictos CSS para: {$componentName}"); // Solo validar componentes con Renderer dinámico conocidos if (!isset(self::DYNAMIC_RENDERER_COMPONENTS[$componentName])) { $result->addInfo("Componente no tiene Renderer dinámico registrado - Omitiendo validación CSS"); return $result; } $componentConfig = self::DYNAMIC_RENDERER_COMPONENTS[$componentName]; // 1. Verificar que existe un Renderer dinámico $hasRenderer = $this->hasRendererFile($componentName, $themePath); if (!$hasRenderer) { $result->addInfo("No se encontró Renderer dinámico - Validación CSS no aplica"); return $result; } $result->addInfo("✓ Renderer dinámico detectado"); // 2. Buscar archivos CSS conflictivos en Assets/css/ $cssData = $this->validateAssetsCSSFiles($componentName, $componentConfig, $themePath, $result); // 3. Validar enqueue-scripts.php y determinar errores vs warnings $this->validateEnqueueScripts($componentName, $componentConfig, $themePath, $result, $cssData); return $result; } /** * Verifica si existe un Renderer para el componente */ private function hasRendererFile(string $componentName, string $themePath): bool { $pascalCaseName = str_replace('-', '', ucwords($componentName, '-')); $publicPath = $themePath . '/Public/' . $pascalCaseName . '/Infrastructure/Ui/' . $pascalCaseName . 'Renderer.php'; if (file_exists($publicPath)) { return true; } $adminPath = $themePath . '/Admin/' . $pascalCaseName . '/Infrastructure/Ui/' . $pascalCaseName . 'Renderer.php'; if (file_exists($adminPath)) { return true; } return false; } /** * Valida archivos CSS en Assets/css/ * * @return array{files: string[], important: array} Archivos encontrados y violaciones */ private function validateAssetsCSSFiles( string $componentName, array $componentConfig, string $themePath, ValidationResult $result ): array { $assetsPath = $themePath . '/Assets/css'; $cssFilesFound = []; $importantViolations = []; if (!is_dir($assetsPath)) { $result->addInfo("No existe carpeta Assets/css/"); return ['files' => [], 'important' => []]; } foreach ($componentConfig['css_files'] as $cssFileName) { $cssFilePath = $assetsPath . '/' . $cssFileName; if (file_exists($cssFilePath)) { $cssFilesFound[] = $cssFileName; $content = file_get_contents($cssFilePath); // Buscar reglas !important $importantCount = $this->countImportantRules($content); if ($importantCount > 0) { $importantViolations[$cssFileName] = $importantCount; } // Buscar clases CSS del componente $conflictingClasses = $this->findConflictingClasses($content, $componentConfig['css_classes']); if (!empty($conflictingClasses)) { $result->addWarning( "Archivo '{$cssFileName}' define clases del componente: " . implode(', ', $conflictingClasses) ); } } } // Reportar archivos encontrados if (!empty($cssFilesFound)) { $result->addWarning( "Archivos CSS hardcodeados encontrados en Assets/css/: " . implode(', ', $cssFilesFound) ); $result->addInfo( "⚠️ Estos archivos podrían sobrescribir estilos generados dinámicamente por el Renderer" ); } else { $result->addInfo("✓ Sin archivos CSS hardcodeados conflictivos en Assets/css/"); } // Stats $result->setStat('Archivos CSS conflictivos', count($cssFilesFound)); $result->setStat('Reglas !important', array_sum($importantViolations)); return ['files' => $cssFilesFound, 'important' => $importantViolations]; } /** * Cuenta reglas !important en contenido CSS */ private function countImportantRules(string $content): int { preg_match_all('/!important/i', $content, $matches); return count($matches[0]); } /** * Encuentra clases CSS conflictivas en el contenido */ private function findConflictingClasses(string $content, array $cssClasses): array { $found = []; foreach ($cssClasses as $className) { // Escapar el punto para regex $pattern = '/' . preg_quote($className, '/') . '\s*[{,]/'; if (preg_match($pattern, $content)) { $found[] = $className; } } return $found; } /** * Valida Inc/enqueue-scripts.php para detectar CSS encolados * * @param array{files: string[], important: array} $cssData Datos de archivos CSS encontrados */ private function validateEnqueueScripts( string $componentName, array $componentConfig, string $themePath, ValidationResult $result, array $cssData ): void { $enqueueScriptsPath = $themePath . '/Inc/enqueue-scripts.php'; if (!file_exists($enqueueScriptsPath)) { $result->addWarning("No se encontró Inc/enqueue-scripts.php"); return; } $content = file_get_contents($enqueueScriptsPath); $enqueuedFiles = []; $commentedFiles = []; foreach ($componentConfig['css_files'] as $cssFileName) { // Buscar si el archivo está siendo encolado (multiline - wp_enqueue_style puede tener saltos de línea) $pattern = '/wp_enqueue_style\s*\([^;]*' . preg_quote($cssFileName, '/') . '[^;]*\);/s'; if (preg_match($pattern, $content, $match)) { // Verificar si está comentado $matchPos = strpos($content, $match[0]); $lineStart = strrpos(substr($content, 0, $matchPos), "\n") + 1; $lineContent = substr($content, $lineStart, $matchPos - $lineStart); // Verificar si la línea está en un bloque comentado /* */ $beforeMatch = substr($content, 0, $matchPos); $lastCommentOpen = strrpos($beforeMatch, '/*'); $lastCommentClose = strrpos($beforeMatch, '*/'); $isCommented = false; if ($lastCommentOpen !== false) { if ($lastCommentClose === false || $lastCommentOpen > $lastCommentClose) { $isCommented = true; } } // También verificar comentario de línea // if (strpos($lineContent, '//') !== false) { $isCommented = true; } if ($isCommented) { $commentedFiles[] = $cssFileName; } else { $enqueuedFiles[] = $cssFileName; } } } // Reportar resultados de enqueue if (!empty($enqueuedFiles)) { $result->addError( "❌ CRÍTICO: CSS conflictivo ACTIVO en enqueue-scripts.php: " . implode(', ', $enqueuedFiles) ); $result->addInfo( "SOLUCIÓN: Comentar wp_enqueue_style() para estos archivos ya que el Renderer genera CSS dinámico" ); } if (!empty($commentedFiles)) { $result->addInfo( "✓ CSS correctamente deshabilitado en enqueue-scripts.php: " . implode(', ', $commentedFiles) ); } if (empty($enqueuedFiles) && empty($commentedFiles)) { $result->addInfo("✓ Sin conflictos de enqueue detectados"); } // Reportar !important violations basado en estado de enqueue $importantViolations = $cssData['important'] ?? []; if (!empty($importantViolations)) { foreach ($importantViolations as $file => $count) { if (in_array($file, $enqueuedFiles, true)) { // CSS activo con !important → ERROR CRÍTICO $result->addError( "❌ CRÍTICO: '{$file}' ACTIVO con {$count} regla(s) !important que SOBRESCRIBEN estilos dinámicos" ); } elseif (in_array($file, $commentedFiles, true)) { // CSS deshabilitado con !important → WARNING (considerar eliminar archivo) $result->addWarning( "'{$file}' deshabilitado pero tiene {$count} regla(s) !important - Considerar eliminar archivo" ); } else { // Archivo existe pero no está en enqueue-scripts.php → WARNING $result->addWarning( "'{$file}' no está en enqueue-scripts.php pero tiene {$count} regla(s) !important" ); } } } } public function getPhaseNumber(): int|string { return 'css'; } public function getPhaseDescription(): string { return 'CSS Conflicts (Assets vs Dynamic Renderers)'; } }