fix(exclusions): Corregir Renderers que ignoraban sistema de exclusiones

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>
This commit is contained in:
FrankZamora
2025-12-03 19:52:44 -06:00
parent c28fedd6e7
commit f4b45b7e17
29 changed files with 342 additions and 20 deletions

View File

@@ -96,6 +96,9 @@ final readonly class ComponentConfiguration
// Sistema de visibilidad por página
'_page_visibility', // Visibilidad por tipo de página (home, posts, pages, archives, search)
// Sistema de exclusiones (Plan 99.11)
'_exclusions', // Reglas de exclusión por categoría, post ID, URL pattern
];
/**

View File

@@ -86,10 +86,21 @@ final class UrlPatternExclusion extends ExclusionRule
}
/**
* Evalua coincidencia por substring
* Evalua coincidencia por substring o wildcard
*
* Soporta wildcards simples:
* - `*sct*` coincide con URLs que contengan "sct"
* - `*` se convierte a `.*` en regex
* - Sin wildcards: busca substring literal
*/
private function matchesSubstring(string $pattern, string $requestUri, string $url): bool
{
// Detectar si tiene wildcards (*)
if (str_contains($pattern, '*')) {
return $this->matchesWildcard($pattern, $requestUri, $url);
}
// Substring literal
if ($requestUri !== '' && str_contains($requestUri, $pattern)) {
return true;
}
@@ -101,6 +112,31 @@ final class UrlPatternExclusion extends ExclusionRule
return false;
}
/**
* Evalua coincidencia con patron wildcard
*
* Convierte wildcards (*) a regex (.*)
*/
private function matchesWildcard(string $pattern, string $requestUri, string $url): bool
{
// Convertir wildcard a regex:
// 1. Escapar caracteres especiales de regex (excepto *)
// 2. Convertir * a .*
$regexPattern = preg_quote($pattern, '#');
$regexPattern = str_replace('\\*', '.*', $regexPattern);
$regexPattern = '#' . $regexPattern . '#i';
if ($requestUri !== '' && preg_match($regexPattern, $requestUri) === 1) {
return true;
}
if ($url !== '' && preg_match($regexPattern, $url) === 1) {
return true;
}
return false;
}
public function hasValues(): bool
{
return !empty($this->urlPatterns);

View File

@@ -109,28 +109,67 @@ final class WordPressComponentSettingsRepository implements ComponentSettingsRep
/**
* {@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);
// Intentar actualizar
$result = $this->wpdb->update(
$this->tableName,
['attribute_value' => $serializedValue],
[
'component_name' => $componentName,
'group_name' => $groupName,
'attribute_name' => $attributeName
],
['%s'],
['%s', '%s', '%s']
);
// 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}
*/