fix(structure): Correct case-sensitivity for Linux compatibility

Rename folders to match PHP PSR-4 autoloading conventions:
- schemas → Schemas
- shared → Shared
- Wordpress → WordPress (in all locations)

Fixes deployment issues on Linux servers where filesystem is case-sensitive.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
FrankZamora
2025-11-26 22:53:34 -06:00
parent a2548ab5c2
commit 90863cd8f5
92 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,272 @@
<?php
declare(strict_types=1);
namespace ROITheme\Shared\Infrastructure\Validators;
/**
* Validador de Fase 03: Renderers (DB→HTML/CSS)
*
* Valida que:
* - Renderer existe en ubicación correcta
* - Namespace y clase correctos
* - Inyecta CSSGeneratorInterface
* - CERO CSS hardcodeado (CRÍTICO)
* - Tiene métodos obligatorios (render, getVisibilityClasses)
* - Usa escaping correcto
* - NO usa WordPress de BD
*/
final class Phase03Validator implements PhaseValidatorInterface
{
/**
* Componentes especiales que NO requieren CSSGeneratorInterface ni getVisibilityClasses
*
* Estos son componentes de inyeccion (no visuales) que:
* - NO renderizan HTML visual
* - NO generan CSS dinamico (inyectan CSS del usuario tal cual)
* - NO necesitan clases de visibilidad responsive
* - Inyectan codigo en hooks (wp_head, wp_footer)
*/
private const INJECTION_COMPONENTS = ['theme-settings'];
public function validate(string $componentName, string $themePath): ValidationResult
{
$result = new ValidationResult();
$result->addInfo("Validando Renderer para: {$componentName}");
// Determinar contexto (admin o public) - intentar ambos
$rendererPath = $this->findRendererPath($componentName, $themePath);
if ($rendererPath === null) {
$result->addError("Renderer no encontrado en Public/ ni Admin/");
$pascalCaseName = str_replace('-', '', ucwords($componentName, '-'));
$result->addInfo("Ubicación esperada: Public/{$pascalCaseName}/Infrastructure/Ui/*Renderer.php");
return $result;
}
$result->addInfo("Archivo encontrado: {$rendererPath}");
// Leer contenido del archivo
$content = file_get_contents($rendererPath);
// Validaciones
$this->validateNamespaceAndClass($content, $componentName, $result);
// Validaciones especiales para componentes de inyeccion
$isInjectionComponent = in_array($componentName, self::INJECTION_COMPONENTS, true);
if ($isInjectionComponent) {
$result->addInfo("✓ Componente de inyección - validaciones CSS/visibility omitidas");
} else {
$this->validateCSSGeneratorInjection($content, $result);
$this->validateNoCSSHardcoded($content, $result); // CRÍTICO
$this->validateGetVisibilityClassesMethod($content, $result);
}
$this->validateRenderMethod($content, $componentName, $result);
$this->validateEscaping($content, $result);
$this->validateNoDirectDatabaseAccess($content, $result);
// Estadísticas
$fileSize = filesize($rendererPath);
$lineCount = substr_count($content, "\n") + 1;
$result->setStat('Archivo', basename($rendererPath));
$result->setStat('Líneas', $lineCount);
$result->setStat('Tamaño', $fileSize . ' bytes');
if ($lineCount > 500) {
$result->addWarning("Archivo excede 500 líneas ({$lineCount}) - considerar refactorizar.");
} elseif ($lineCount > 300) {
$result->addInfo(" Archivo tiene {$lineCount} líneas (recomendado: <300, aceptable: <500)");
}
return $result;
}
private function findRendererPath(string $componentName, string $themePath): ?string
{
// Convertir kebab-case a PascalCase (para carpetas y archivos)
$pascalCaseName = str_replace('-', '', ucwords($componentName, '-'));
// Intentar en Public/ con PascalCase (PSR-4 standard)
$publicPath = $themePath . '/Public/' . $pascalCaseName . '/Infrastructure/Ui/' . $pascalCaseName . 'Renderer.php';
if (file_exists($publicPath)) {
return $publicPath;
}
// Intentar en Admin/ con PascalCase (PSR-4 standard)
$adminPath = $themePath . '/Admin/' . $pascalCaseName . '/Infrastructure/Ui/' . $pascalCaseName . 'Renderer.php';
if (file_exists($adminPath)) {
return $adminPath;
}
return null;
}
private function validateNamespaceAndClass(string $content, string $componentName, ValidationResult $result): void
{
$pascalCaseName = str_replace('-', '', ucwords($componentName, '-'));
// Validar namespace (Ui en PascalCase, primera letra mayúscula)
if (!preg_match('/namespace\s+ROITheme\\\\(Public|Admin)\\\\' . preg_quote($pascalCaseName, '/') . '\\\\Infrastructure\\\\Ui;/', $content)) {
$result->addError("Namespace incorrecto. Debe ser: ROITheme\\Public\\{$pascalCaseName}\\Infrastructure\\Ui");
}
// Validar clase final
if (!preg_match('/final\s+class\s+' . preg_quote($pascalCaseName, '/') . 'Renderer/', $content)) {
$result->addError("Clase debe ser: final class {$pascalCaseName}Renderer");
}
}
private function validateCSSGeneratorInjection(string $content, ValidationResult $result): void
{
// Verificar que constructor recibe CSSGeneratorInterface
if (!preg_match('/public\s+function\s+__construct\([^)]*CSSGeneratorInterface\s+\$cssGenerator/s', $content)) {
$result->addError("Constructor NO inyecta CSSGeneratorInterface (debe recibir interfaz, no clase concreta)");
}
// Verificar propiedad privada
if (!preg_match('/private\s+CSSGeneratorInterface\s+\$cssGenerator/', $content)) {
$result->addError("Falta propiedad: private CSSGeneratorInterface \$cssGenerator");
}
}
private function validateRenderMethod(string $content, string $componentName, ValidationResult $result): void
{
// Verificar método render existe (aceptar Component object o array - Component es preferido)
$hasComponentSignature = preg_match('/public\s+function\s+render\s*\(\s*Component\s+\$component\s*\)\s*:\s*string/', $content);
$hasArraySignature = preg_match('/public\s+function\s+render\s*\(\s*array\s+\$data\s*\)\s*:\s*string/', $content);
if (!$hasComponentSignature && !$hasArraySignature) {
$result->addError("Falta método: public function render(Component \$component): string o render(array \$data): string");
} elseif ($hasArraySignature) {
$result->addWarning("Método usa render(array \$data) - Considerar migrar a render(Component \$component) para type safety");
}
// Componentes de inyeccion no requieren validacion de visibility
$isInjectionComponent = in_array($componentName, self::INJECTION_COMPONENTS, true);
if (!$isInjectionComponent) {
// Verificar que valida is_enabled
$hasArrayValidation = preg_match('/\$data\s*\[\s*[\'"]visibility[\'"]\s*\]\s*\[\s*[\'"]is_enabled[\'"]\s*\]/', $content);
$hasComponentValidation = preg_match('/\$component->getVisibility\(\)->isEnabled\(\)/', $content);
if (!$hasArrayValidation && !$hasComponentValidation) {
$result->addWarning("Método render() debería validar visibilidad (is_enabled)");
}
}
}
private function validateNoCSSHardcoded(string $content, ValidationResult $result): void
{
$violations = [];
// Detectar style="..." (pero permitir algunos casos específicos del design system)
if (preg_match_all('/style\s*=\s*["\']/', $content, $matches, PREG_OFFSET_CAPTURE)) {
// Contar ocurrencias
$count = count($matches[0]);
// Si hay más de 2-3 (casos permitidos del design system), es violación
if ($count > 3) {
$violations[] = "Encontrado style=\"...\" inline ({$count} ocurrencias) - PROHIBIDO";
} elseif ($count > 0) {
$result->addWarning("Encontrado {$count} uso(s) de style=\"...\" - Verificar que sea del design system aprobado");
}
}
// Detectar heredoc <<<STYLE o <<<CSS
if (preg_match('/<<<(STYLE|CSS)/', $content)) {
$violations[] = "Encontrado heredoc <<<STYLE o <<<CSS - PROHIBIDO";
}
// Detectar eventos inline (onclick, onmouseover, etc.)
$inlineEvents = ['onclick', 'onmouseover', 'onmouseout', 'onload', 'onchange', 'onsubmit'];
foreach ($inlineEvents as $event) {
if (preg_match('/' . $event . '\s*=/', $content)) {
$violations[] = "Encontrado evento inline '{$event}=\"...\"' - PROHIBIDO";
}
}
// Verificar que usa $this->cssGenerator->generate()
if (!preg_match('/\$this->cssGenerator->generate\s*\(/', $content)) {
$violations[] = "NO usa \$this->cssGenerator->generate() - CSS debe generarse vía servicio";
}
if (!empty($violations)) {
foreach ($violations as $violation) {
$result->addError("❌ CRÍTICO - CSS HARDCODEADO: {$violation}");
}
} else {
$result->addInfo("✓ CERO CSS hardcodeado detectado");
}
}
private function validateGetVisibilityClassesMethod(string $content, ValidationResult $result): void
{
// Verificar firma del método
if (!preg_match('/private\s+function\s+getVisibilityClasses\s*\(\s*bool\s+\$desktop\s*,\s*bool\s+\$mobile\s*\)\s*:\s*\?string/', $content)) {
$result->addError("Falta método: private function getVisibilityClasses(bool \$desktop, bool \$mobile): ?string");
return;
}
// Verificar implementación de tabla Bootstrap
$requiredPatterns = [
'/d-none d-lg-block/' => "Patrón 'd-none d-lg-block' (desktop only)",
'/d-lg-none/' => "Patrón 'd-lg-none' (mobile only)",
];
foreach ($requiredPatterns as $pattern => $description) {
if (!preg_match($pattern, $content)) {
$result->addWarning("getVisibilityClasses() puede no implementar correctamente: {$description}");
}
}
}
private function validateEscaping(string $content, ValidationResult $result): void
{
$escapingUsed = false;
if (preg_match('/esc_html\s*\(/', $content)) {
$escapingUsed = true;
}
if (preg_match('/esc_attr\s*\(/', $content)) {
$escapingUsed = true;
}
if (preg_match('/esc_url\s*\(/', $content)) {
$escapingUsed = true;
}
if (!$escapingUsed) {
$result->addWarning("No se detectó uso de esc_html(), esc_attr() o esc_url() - Verificar escaping");
}
}
private function validateNoDirectDatabaseAccess(string $content, ValidationResult $result): void
{
// Verificar que NO usa global $wpdb
if (preg_match('/global\s+\$wpdb/', $content)) {
$result->addError("Renderer usa 'global \$wpdb' - Acceso a BD debe estar en Repository, NO en Renderer");
}
// Verificar que NO usa funciones directas de BD
$dbFunctions = ['get_option', 'update_option', 'get_post_meta', 'update_post_meta'];
foreach ($dbFunctions as $func) {
if (preg_match('/\b' . $func . '\s*\(/', $content)) {
$result->addWarning("Renderer usa '{$func}()' - Considerar mover a Repository");
}
}
}
public function getPhaseNumber(): int|string
{
return 3;
}
public function getPhaseDescription(): string
{
return 'Renderers (DB→HTML/CSS)';
}
}