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:
255
Shared/Infrastructure/Validators/Phase01Validator.php
Normal file
255
Shared/Infrastructure/Validators/Phase01Validator.php
Normal file
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Shared\Infrastructure\Validators;
|
||||
|
||||
/**
|
||||
* Validador de Fase 01: Schema JSON
|
||||
*
|
||||
* Valida que el archivo JSON del componente cumple con:
|
||||
* - Estructura correcta
|
||||
* - Campos obligatorios presentes
|
||||
* - Tipos de datos válidos
|
||||
* - Grupos y campos requeridos
|
||||
*/
|
||||
final class Phase01Validator implements PhaseValidatorInterface
|
||||
{
|
||||
private const ALLOWED_TYPES = ['boolean', 'text', 'textarea', 'url', 'select', 'color'];
|
||||
private const ALLOWED_PRIORITIES = [10, 20, 30, 40, 50, 60, 70, 80, 90];
|
||||
private const STANDARD_GROUPS = [
|
||||
'visibility', 'content', 'typography', 'colors', 'spacing',
|
||||
'visual_effects', 'behavior', 'layout', 'links', 'icons', 'media', 'forms'
|
||||
];
|
||||
|
||||
/**
|
||||
* Componentes especiales que NO requieren grupo visibility
|
||||
*
|
||||
* Estos son componentes de inyeccion (no visuales) que:
|
||||
* - NO renderizan HTML visual
|
||||
* - Inyectan codigo en hooks (wp_head, wp_footer)
|
||||
* - Siempre estan activos (controlados por campos vacios/llenos)
|
||||
*/
|
||||
private const INJECTION_COMPONENTS = ['theme-settings'];
|
||||
|
||||
public function validate(string $componentName, string $themePath): ValidationResult
|
||||
{
|
||||
$result = new ValidationResult();
|
||||
|
||||
// Construir ruta al schema
|
||||
$schemaPath = $themePath . '/Schemas/' . $componentName . '.json';
|
||||
|
||||
$result->addInfo("Validando Schema JSON: schemas/{$componentName}.json");
|
||||
|
||||
// 1. Verificar que el archivo existe
|
||||
if (!file_exists($schemaPath)) {
|
||||
$result->addError("Schema JSON no encontrado: {$schemaPath}");
|
||||
return $result;
|
||||
}
|
||||
|
||||
// 2. Leer y parsear JSON
|
||||
$jsonContent = file_get_contents($schemaPath);
|
||||
$schema = json_decode($jsonContent, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
$result->addError("JSON inválido: " . json_last_error_msg());
|
||||
return $result;
|
||||
}
|
||||
|
||||
// 3. Validar estructura top-level
|
||||
$this->validateTopLevelStructure($schema, $componentName, $result);
|
||||
|
||||
// 4. Validar grupos
|
||||
if (isset($schema['groups'])) {
|
||||
$this->validateGroups($schema['groups'], $result);
|
||||
}
|
||||
|
||||
// 5. Validar campos obligatorios de visibilidad (excepto componentes de inyeccion)
|
||||
$this->validateVisibilityFields($schema, $componentName, $result);
|
||||
|
||||
// Estadísticas
|
||||
$totalFields = $this->countTotalFields($schema);
|
||||
$totalGroups = isset($schema['groups']) ? count($schema['groups']) : 0;
|
||||
|
||||
$result->setStat('Archivo', "schemas/{$componentName}.json");
|
||||
$result->setStat('Grupos totales', $totalGroups);
|
||||
$result->setStat('Campos totales', $totalFields);
|
||||
$result->setStat('Tamaño JSON', strlen($jsonContent) . ' bytes');
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function validateTopLevelStructure(array $schema, string $componentName, ValidationResult $result): void
|
||||
{
|
||||
// Campos obligatorios
|
||||
$requiredFields = ['component_name', 'version', 'description', 'groups'];
|
||||
|
||||
foreach ($requiredFields as $field) {
|
||||
if (!isset($schema[$field])) {
|
||||
$result->addError("Campo obligatorio faltante: '{$field}'");
|
||||
}
|
||||
}
|
||||
|
||||
// Validar component_name coincide con archivo
|
||||
if (isset($schema['component_name']) && $schema['component_name'] !== $componentName) {
|
||||
$result->addError(
|
||||
"component_name '{$schema['component_name']}' no coincide con nombre de archivo '{$componentName}'"
|
||||
);
|
||||
}
|
||||
|
||||
// Validar versión semver
|
||||
if (isset($schema['version'])) {
|
||||
if (!preg_match('/^\d+\.\d+\.\d+$/', $schema['version'])) {
|
||||
$result->addError("Versión '{$schema['version']}' no es semver válido (debe ser X.Y.Z)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function validateGroups(array $groups, ValidationResult $result): void
|
||||
{
|
||||
if (empty($groups)) {
|
||||
$result->addError("Schema debe tener al menos un grupo");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($groups as $groupName => $group) {
|
||||
// Validar nombre de grupo es snake_case
|
||||
if (!preg_match('/^[a-z_]+$/', $groupName)) {
|
||||
$result->addError("Nombre de grupo '{$groupName}' debe estar en snake_case (solo minúsculas y _)");
|
||||
}
|
||||
|
||||
// Advertencia si grupo no es estándar
|
||||
if (!in_array($groupName, self::STANDARD_GROUPS, true)) {
|
||||
$result->addWarning("Grupo '{$groupName}' no es estándar (considerar usar: " . implode(', ', self::STANDARD_GROUPS) . ")");
|
||||
}
|
||||
|
||||
// Validar estructura del grupo
|
||||
if (!isset($group['label'])) {
|
||||
$result->addError("Grupo '{$groupName}' no tiene 'label'");
|
||||
}
|
||||
|
||||
if (!isset($group['priority'])) {
|
||||
$result->addError("Grupo '{$groupName}' no tiene 'priority'");
|
||||
} elseif (!in_array($group['priority'], self::ALLOWED_PRIORITIES, true)) {
|
||||
$result->addError(
|
||||
"Grupo '{$groupName}' tiene priority inválido ({$group['priority']}). " .
|
||||
"Debe ser uno de: " . implode(', ', self::ALLOWED_PRIORITIES)
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($group['fields'])) {
|
||||
$result->addError("Grupo '{$groupName}' no tiene 'fields'");
|
||||
} elseif (!is_array($group['fields']) || empty($group['fields'])) {
|
||||
$result->addError("Grupo '{$groupName}' debe tener al menos un campo");
|
||||
} else {
|
||||
$this->validateFields($groupName, $group['fields'], $result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function validateFields(string $groupName, array $fields, ValidationResult $result): void
|
||||
{
|
||||
foreach ($fields as $fieldName => $field) {
|
||||
$fullFieldName = "{$groupName}.{$fieldName}";
|
||||
|
||||
// Validar nombre de campo es snake_case
|
||||
if (!preg_match('/^[a-z_]+$/', $fieldName)) {
|
||||
$result->addError("Campo '{$fullFieldName}' debe estar en snake_case (solo minúsculas y _)");
|
||||
}
|
||||
|
||||
// Campos obligatorios
|
||||
if (!isset($field['type'])) {
|
||||
$result->addError("Campo '{$fullFieldName}' no tiene 'type'");
|
||||
} elseif (!in_array($field['type'], self::ALLOWED_TYPES, true)) {
|
||||
$result->addError(
|
||||
"Campo '{$fullFieldName}' tiene type inválido '{$field['type']}'. " .
|
||||
"Debe ser uno de: " . implode(', ', self::ALLOWED_TYPES)
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($field['label'])) {
|
||||
$result->addError("Campo '{$fullFieldName}' no tiene 'label'");
|
||||
}
|
||||
|
||||
if (!array_key_exists('default', $field)) {
|
||||
$result->addError("Campo '{$fullFieldName}' no tiene 'default'");
|
||||
}
|
||||
|
||||
if (!isset($field['editable'])) {
|
||||
$result->addError("Campo '{$fullFieldName}' no tiene 'editable'");
|
||||
} elseif (!is_bool($field['editable'])) {
|
||||
$result->addError("Campo '{$fullFieldName}' tiene 'editable' que no es boolean");
|
||||
}
|
||||
|
||||
// Si type es select, debe tener options
|
||||
if (isset($field['type']) && $field['type'] === 'select') {
|
||||
if (!isset($field['options']) || !is_array($field['options']) || empty($field['options'])) {
|
||||
$result->addError("Campo '{$fullFieldName}' es type 'select' pero no tiene array 'options' válido");
|
||||
}
|
||||
}
|
||||
|
||||
// Si tiene required, debe ser boolean
|
||||
if (isset($field['required']) && !is_bool($field['required'])) {
|
||||
$result->addError("Campo '{$fullFieldName}' tiene 'required' que no es boolean");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function validateVisibilityFields(array $schema, string $componentName, ValidationResult $result): void
|
||||
{
|
||||
// Componentes de inyeccion no requieren grupo visibility
|
||||
if (in_array($componentName, self::INJECTION_COMPONENTS, true)) {
|
||||
$result->addInfo("✓ Componente de inyección '{$componentName}' - grupo visibility no requerido");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($schema['groups']['visibility'])) {
|
||||
$result->addError("Grupo 'visibility' es obligatorio y no está presente");
|
||||
return;
|
||||
}
|
||||
|
||||
$visibilityFields = $schema['groups']['visibility']['fields'] ?? [];
|
||||
|
||||
// Campos obligatorios de visibilidad
|
||||
$requiredVisibilityFields = [
|
||||
'is_enabled' => 'boolean',
|
||||
'show_on_desktop' => 'boolean',
|
||||
'show_on_mobile' => 'boolean',
|
||||
];
|
||||
|
||||
foreach ($requiredVisibilityFields as $fieldName => $expectedType) {
|
||||
if (!isset($visibilityFields[$fieldName])) {
|
||||
$result->addError("Campo obligatorio de visibilidad faltante: 'visibility.{$fieldName}'");
|
||||
} elseif (isset($visibilityFields[$fieldName]['type']) && $visibilityFields[$fieldName]['type'] !== $expectedType) {
|
||||
$result->addError(
|
||||
"Campo 'visibility.{$fieldName}' debe ser type '{$expectedType}' " .
|
||||
"(encontrado: '{$visibilityFields[$fieldName]['type']}')"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function countTotalFields(array $schema): int
|
||||
{
|
||||
$count = 0;
|
||||
|
||||
if (isset($schema['groups'])) {
|
||||
foreach ($schema['groups'] as $group) {
|
||||
if (isset($group['fields'])) {
|
||||
$count += count($group['fields']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
public function getPhaseNumber(): int|string
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public function getPhaseDescription(): string
|
||||
{
|
||||
return 'Schema JSON';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user