PROBLEMA: - El modal de contacto no se mostraba en producción (Linux) - Funcionaba en local (Windows) porque filesystem es case-insensitive - Carpeta: `WordPress` (con P mayúscula) - Namespaces: `Wordpress` (con p minúscula) SOLUCION: - Corregir todos los namespaces de `Wordpress` a `WordPress` - También corregir paths incorrectos `ROITheme\Component\...` a `ROITheme\Shared\...` ARCHIVOS CORREGIDOS (14): - functions.php - Admin/Infrastructure/Api/WordPress/AdminMenuRegistrar.php - Admin/Shared/Infrastructure/Api/WordPress/AdminAjaxHandler.php - Public/ContactForm/Infrastructure/Api/WordPress/ContactFormAjaxHandler.php - Public/Footer/Infrastructure/Api/WordPress/NewsletterAjaxHandler.php - Shared/Infrastructure/Api/WordPress/AjaxController.php - Shared/Infrastructure/Api/WordPress/MigrationCommand.php - Shared/Infrastructure/Di/DIContainer.php - Shared/Infrastructure/Persistence/WordPress/WordPressComponentRepository.php - Shared/Infrastructure/Persistence/WordPress/WordPressComponentSettingsRepository.php - Shared/Infrastructure/Persistence/WordPress/WordPressDefaultsRepository.php - Shared/Infrastructure/Services/CleanupService.php - Shared/Infrastructure/Services/SchemaSyncService.php - Shared/Infrastructure/Services/WordPressValidationService.php 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
248 lines
6.7 KiB
PHP
248 lines
6.7 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace ROITheme\Shared\Infrastructure\Persistence\WordPress;
|
|
|
|
use ROITheme\Shared\Domain\Entities\Component;
|
|
use ROITheme\Shared\Domain\ValueObjects\ComponentName;
|
|
use ROITheme\Shared\Domain\ValueObjects\ComponentConfiguration;
|
|
use ROITheme\Shared\Domain\ValueObjects\ComponentVisibility;
|
|
use ROITheme\Shared\Domain\Contracts\ComponentRepositoryInterface;
|
|
use ROITheme\Shared\Domain\Exceptions\ComponentNotFoundException;
|
|
|
|
/**
|
|
* WordPressComponentRepository - Implementación con WordPress/MySQL
|
|
*
|
|
* RESPONSABILIDAD: Persistir y recuperar componentes desde wp_roi_theme_components
|
|
*
|
|
* DEPENDENCIAS EXTERNAS:
|
|
* - wpdb (WordPress database abstraction)
|
|
* - Tabla wp_roi_theme_components (creada en Fase 2)
|
|
*
|
|
* CONVERSIÓN:
|
|
* - Entity → Array → MySQL (al guardar)
|
|
* - MySQL → Array → Entity (al recuperar)
|
|
*
|
|
* @package ROITheme\Infrastructure\Persistence\WordPress
|
|
*/
|
|
final class WordPressComponentRepository implements ComponentRepositoryInterface
|
|
{
|
|
private string $tableName;
|
|
|
|
/**
|
|
* @param \wpdb $wpdb WordPress database object
|
|
*/
|
|
public function __construct(
|
|
private \wpdb $wpdb
|
|
) {
|
|
$this->tableName = $this->wpdb->prefix . 'roi_theme_components';
|
|
}
|
|
|
|
/**
|
|
* Guardar componente
|
|
*
|
|
* INSERT si no existe, UPDATE si existe
|
|
*
|
|
* @param Component $component
|
|
* @return Component Componente guardado (con timestamps actualizados)
|
|
*/
|
|
public function save(Component $component): Component
|
|
{
|
|
$componentName = $component->name()->value();
|
|
|
|
// Verificar si ya existe
|
|
$existing = $this->findByName($componentName);
|
|
|
|
$data = [
|
|
'component_name' => $componentName,
|
|
'configuration' => json_encode($component->configuration()->toArray()),
|
|
'visibility' => json_encode($component->visibility()->toArray()),
|
|
'is_enabled' => $component->isEnabled() ? 1 : 0,
|
|
'schema_version' => $component->schemaVersion(),
|
|
'updated_at' => current_time('mysql')
|
|
];
|
|
|
|
if ($existing === null) {
|
|
// INSERT
|
|
$data['created_at'] = current_time('mysql');
|
|
|
|
$this->wpdb->insert(
|
|
$this->tableName,
|
|
$data,
|
|
['%s', '%s', '%s', '%d', '%s', '%s', '%s']
|
|
);
|
|
} else {
|
|
// UPDATE
|
|
$this->wpdb->update(
|
|
$this->tableName,
|
|
$data,
|
|
['component_name' => $componentName],
|
|
['%s', '%s', '%s', '%d', '%s', '%s'],
|
|
['%s']
|
|
);
|
|
}
|
|
|
|
// Return the saved component by fetching it from the database
|
|
return $this->getByName($component->name());
|
|
}
|
|
|
|
/**
|
|
* Buscar componente por nombre
|
|
*
|
|
* @param ComponentName $name Nombre del componente
|
|
* @return Component|null Null si no existe
|
|
*/
|
|
public function findByName(ComponentName $name): ?Component
|
|
{
|
|
$sql = $this->wpdb->prepare(
|
|
"SELECT * FROM {$this->tableName} WHERE component_name = %s LIMIT 1",
|
|
$name->value()
|
|
);
|
|
|
|
$row = $this->wpdb->get_row($sql, ARRAY_A);
|
|
|
|
if ($row === null) {
|
|
return null;
|
|
}
|
|
|
|
return $this->rowToEntity($row);
|
|
}
|
|
|
|
/**
|
|
* Obtener componente por nombre (lanza excepción si no existe)
|
|
*
|
|
* @param ComponentName $name
|
|
* @return Component
|
|
* @throws ComponentNotFoundException
|
|
*/
|
|
public function getByName(ComponentName $name): Component
|
|
{
|
|
$component = $this->findByName($name);
|
|
|
|
if ($component === null) {
|
|
throw ComponentNotFoundException::withName($name->value());
|
|
}
|
|
|
|
return $component;
|
|
}
|
|
|
|
/**
|
|
* Obtener todos los componentes
|
|
*
|
|
* @return Component[]
|
|
*/
|
|
public function findAll(): array
|
|
{
|
|
$sql = "SELECT * FROM {$this->tableName} ORDER BY component_name ASC";
|
|
$rows = $this->wpdb->get_results($sql, ARRAY_A);
|
|
|
|
if (empty($rows)) {
|
|
return [];
|
|
}
|
|
|
|
return array_map(
|
|
fn($row) => $this->rowToEntity($row),
|
|
$rows
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Eliminar componente
|
|
*
|
|
* @param ComponentName $name Nombre del componente
|
|
* @return bool True si eliminó exitosamente
|
|
*/
|
|
public function delete(ComponentName $name): bool
|
|
{
|
|
$result = $this->wpdb->delete(
|
|
$this->tableName,
|
|
['component_name' => $name->value()],
|
|
['%s']
|
|
);
|
|
|
|
return $result !== false;
|
|
}
|
|
|
|
/**
|
|
* Obtener componentes habilitados
|
|
*
|
|
* @return Component[]
|
|
*/
|
|
public function findEnabled(): array
|
|
{
|
|
$sql = "SELECT * FROM {$this->tableName} WHERE is_enabled = 1 ORDER BY component_name ASC";
|
|
$rows = $this->wpdb->get_results($sql, ARRAY_A);
|
|
|
|
if (empty($rows)) {
|
|
return [];
|
|
}
|
|
|
|
return array_map(
|
|
fn($row) => $this->rowToEntity($row),
|
|
$rows
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Verificar si existe un componente con el nombre dado
|
|
*
|
|
* @param ComponentName $name
|
|
* @return bool
|
|
*/
|
|
public function exists(ComponentName $name): bool
|
|
{
|
|
return $this->findByName($name) !== null;
|
|
}
|
|
|
|
/**
|
|
* Obtener cantidad total de componentes
|
|
*
|
|
* @return int
|
|
*/
|
|
public function count(): int
|
|
{
|
|
$sql = "SELECT COUNT(*) FROM {$this->tableName}";
|
|
return (int) $this->wpdb->get_var($sql);
|
|
}
|
|
|
|
/**
|
|
* Obtener componentes por grupo de configuración
|
|
*
|
|
* @param string $group Grupo de configuración (visibility, content, styles, general)
|
|
* @return Component[]
|
|
*/
|
|
public function findByConfigGroup(string $group): array
|
|
{
|
|
// For now, return all components as we don't have a specific column for groups
|
|
// This would require additional schema design
|
|
return $this->findAll();
|
|
}
|
|
|
|
/**
|
|
* Convertir fila de BD a Entity
|
|
*
|
|
* @param array $row Fila de la base de datos
|
|
* @return Component
|
|
*/
|
|
private function rowToEntity(array $row): Component
|
|
{
|
|
// Decodificar JSON
|
|
$configuration = json_decode($row['configuration'], true) ?? [];
|
|
$visibility = json_decode($row['visibility'], true) ?? [];
|
|
|
|
// Crear Value Objects
|
|
$name = new ComponentName($row['component_name']);
|
|
$config = ComponentConfiguration::fromArray($configuration);
|
|
$vis = ComponentVisibility::fromArray($visibility);
|
|
|
|
// Crear Entity
|
|
return new Component(
|
|
$name,
|
|
$config,
|
|
$vis,
|
|
(bool) $row['is_enabled'],
|
|
$row['schema_version']
|
|
);
|
|
}
|
|
}
|