Plan 99.11 - Correcciones críticas: - FooterRenderer: Añadir PageVisibilityHelper::shouldShow() - HeroSectionRenderer: Añadir PageVisibilityHelper::shouldShow() - AdsensePlacementRenderer: Añadir PageVisibilityHelper::shouldShow() Mejoras adicionales: - UrlPatternExclusion: Soporte wildcards (*sct* → regex) - ExclusionFormPartial: UI mejorada con placeholders - ComponentConfiguration: Grupo _exclusions validado - 12 FormBuilders: Integración UI de exclusiones - 12 FieldMappers: Mapeo campos de exclusión Verificado: Footer oculto en post con categoría excluida SCT 🤖 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\Contracts\ComponentSettingsRepositoryInterface;
|
||
|
||
/**
|
||
* Implementaci<63>n de repositorio de configuraciones usando WordPress/MySQL
|
||
*
|
||
* Infrastructure Layer - WordPress specific
|
||
* Trabaja con la tabla wp_roi_theme_component_settings
|
||
*
|
||
* @package ROITheme\Shared\Infrastructure\Persistence\WordPress
|
||
*/
|
||
final class WordPressComponentSettingsRepository implements ComponentSettingsRepositoryInterface
|
||
{
|
||
private string $tableName;
|
||
|
||
public function __construct(
|
||
private \wpdb $wpdb
|
||
) {
|
||
$this->tableName = $this->wpdb->prefix . 'roi_theme_component_settings';
|
||
}
|
||
|
||
/**
|
||
* {@inheritDoc}
|
||
*/
|
||
public function getComponentSettings(string $componentName): array
|
||
{
|
||
$sql = $this->wpdb->prepare(
|
||
"SELECT group_name, attribute_name, attribute_value
|
||
FROM {$this->tableName}
|
||
WHERE component_name = %s
|
||
ORDER BY group_name, attribute_name",
|
||
$componentName
|
||
);
|
||
|
||
$rows = $this->wpdb->get_results($sql, ARRAY_A);
|
||
|
||
if (empty($rows)) {
|
||
return [];
|
||
}
|
||
|
||
// Agrupar por grupo
|
||
$settings = [];
|
||
foreach ($rows as $row) {
|
||
$groupName = $row['group_name'];
|
||
$attributeName = $row['attribute_name'];
|
||
$value = $row['attribute_value'];
|
||
|
||
// Convertir valores seg<65>n tipo
|
||
$value = $this->unserializeValue($value);
|
||
|
||
if (!isset($settings[$groupName])) {
|
||
$settings[$groupName] = [];
|
||
}
|
||
|
||
$settings[$groupName][$attributeName] = $value;
|
||
}
|
||
|
||
return $settings;
|
||
}
|
||
|
||
/**
|
||
* {@inheritDoc}
|
||
*/
|
||
public function saveComponentSettings(string $componentName, array $settings): int
|
||
{
|
||
$updated = 0;
|
||
|
||
foreach ($settings as $groupName => $attributes) {
|
||
foreach ($attributes as $attributeName => $value) {
|
||
if ($this->saveFieldValue($componentName, $groupName, $attributeName, $value)) {
|
||
$updated++;
|
||
}
|
||
}
|
||
}
|
||
|
||
return $updated;
|
||
}
|
||
|
||
/**
|
||
* {@inheritDoc}
|
||
*/
|
||
public function getFieldValue(string $componentName, string $groupName, string $attributeName): mixed
|
||
{
|
||
$sql = $this->wpdb->prepare(
|
||
"SELECT attribute_value
|
||
FROM {$this->tableName}
|
||
WHERE component_name = %s
|
||
AND group_name = %s
|
||
AND attribute_name = %s
|
||
LIMIT 1",
|
||
$componentName,
|
||
$groupName,
|
||
$attributeName
|
||
);
|
||
|
||
$value = $this->wpdb->get_var($sql);
|
||
|
||
if ($value === null) {
|
||
return null;
|
||
}
|
||
|
||
return $this->unserializeValue($value);
|
||
}
|
||
|
||
/**
|
||
* {@inheritDoc}
|
||
*
|
||
* Implementa UPSERT: si el registro no existe, lo crea; si existe, lo actualiza.
|
||
* Esto es necesario para grupos especiales como _page_visibility y _exclusions
|
||
* que no vienen del schema JSON.
|
||
*/
|
||
public function saveFieldValue(string $componentName, string $groupName, string $attributeName, mixed $value): bool
|
||
{
|
||
// Serializar valor
|
||
$serializedValue = $this->serializeValue($value);
|
||
|
||
// Verificar si el registro existe
|
||
$exists = $this->fieldExists($componentName, $groupName, $attributeName);
|
||
|
||
if ($exists) {
|
||
// UPDATE
|
||
$result = $this->wpdb->update(
|
||
$this->tableName,
|
||
['attribute_value' => $serializedValue],
|
||
[
|
||
'component_name' => $componentName,
|
||
'group_name' => $groupName,
|
||
'attribute_name' => $attributeName
|
||
],
|
||
['%s'],
|
||
['%s', '%s', '%s']
|
||
);
|
||
} else {
|
||
// INSERT - crear nuevo registro
|
||
$result = $this->wpdb->insert(
|
||
$this->tableName,
|
||
[
|
||
'component_name' => $componentName,
|
||
'group_name' => $groupName,
|
||
'attribute_name' => $attributeName,
|
||
'attribute_value' => $serializedValue
|
||
],
|
||
['%s', '%s', '%s', '%s']
|
||
);
|
||
}
|
||
|
||
return $result !== false;
|
||
}
|
||
|
||
/**
|
||
* Verifica si un campo existe en la BD
|
||
*/
|
||
private function fieldExists(string $componentName, string $groupName, string $attributeName): bool
|
||
{
|
||
$sql = $this->wpdb->prepare(
|
||
"SELECT COUNT(*) FROM {$this->tableName}
|
||
WHERE component_name = %s
|
||
AND group_name = %s
|
||
AND attribute_name = %s",
|
||
$componentName,
|
||
$groupName,
|
||
$attributeName
|
||
);
|
||
|
||
return (int) $this->wpdb->get_var($sql) > 0;
|
||
}
|
||
|
||
/**
|
||
* {@inheritDoc}
|
||
*/
|
||
public function resetToDefaults(string $componentName, string $schemaPath): int
|
||
{
|
||
if (!file_exists($schemaPath)) {
|
||
return 0;
|
||
}
|
||
|
||
$schema = json_decode(file_get_contents($schemaPath), true);
|
||
|
||
if (!$schema || !isset($schema['groups'])) {
|
||
return 0;
|
||
}
|
||
|
||
$updated = 0;
|
||
|
||
// Iterar grupos y campos del schema
|
||
foreach ($schema['groups'] as $groupName => $group) {
|
||
foreach ($group['fields'] as $fieldName => $field) {
|
||
$defaultValue = $field['default'] ?? '';
|
||
|
||
if ($this->saveFieldValue($componentName, $groupName, $fieldName, $defaultValue)) {
|
||
$updated++;
|
||
}
|
||
}
|
||
}
|
||
|
||
return $updated;
|
||
}
|
||
|
||
/**
|
||
* Serializa un valor para guardarlo en la BD
|
||
*
|
||
* @param mixed $value
|
||
* @return string
|
||
*/
|
||
private function serializeValue(mixed $value): string
|
||
{
|
||
if (is_bool($value)) {
|
||
return $value ? '1' : '0';
|
||
}
|
||
|
||
if (is_array($value)) {
|
||
return json_encode($value);
|
||
}
|
||
|
||
return (string) $value;
|
||
}
|
||
|
||
/**
|
||
* Deserializa un valor desde la BD
|
||
*
|
||
* @param string $value
|
||
* @return mixed
|
||
*/
|
||
private function unserializeValue(string $value): mixed
|
||
{
|
||
// Intentar decodificar JSON
|
||
if (str_starts_with($value, '{') || str_starts_with($value, '[')) {
|
||
$decoded = json_decode($value, true);
|
||
if (json_last_error() === JSON_ERROR_NONE) {
|
||
return $decoded;
|
||
}
|
||
}
|
||
|
||
// Convertir booleanos
|
||
if ($value === '1' || $value === '0') {
|
||
return $value === '1';
|
||
}
|
||
|
||
// Devolver como est<73>
|
||
return $value;
|
||
}
|
||
}
|