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:
0
Shared/Infrastructure/Services/.gitkeep
Normal file
0
Shared/Infrastructure/Services/.gitkeep
Normal file
180
Shared/Infrastructure/Services/CSSGeneratorService.php
Normal file
180
Shared/Infrastructure/Services/CSSGeneratorService.php
Normal file
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Shared\Infrastructure\Services;
|
||||
|
||||
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
||||
|
||||
/**
|
||||
* Class CSSGeneratorService
|
||||
*
|
||||
* Implementación concreta del generador de CSS.
|
||||
* Convierte arrays de configuración de estilos en reglas CSS válidas y formateadas.
|
||||
*
|
||||
* Responsabilidades:
|
||||
* - Generar string CSS a partir de selector y estilos
|
||||
* - Convertir propiedades snake_case → kebab-case
|
||||
* - Normalizar nombres de propiedades (text_color → color)
|
||||
* - Formatear reglas CSS con indentación legible
|
||||
* - Sanitizar valores para prevenir inyección
|
||||
*
|
||||
* @package ROITheme\Shared\Infrastructure\Services
|
||||
*/
|
||||
final class CSSGeneratorService implements CSSGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* Mapa de nombres de propiedades CSS normalizadas.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private const PROPERTY_MAP = [
|
||||
'text-color' => 'color',
|
||||
'bg-color' => 'background-color',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function generate(string $selector, array $styles): string
|
||||
{
|
||||
if (empty($styles)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Filtrar valores vacíos o null
|
||||
$styles = $this->filterEmptyValues($styles);
|
||||
|
||||
if (empty($styles)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Convertir array de estilos a propiedades CSS
|
||||
$cssProperties = $this->buildCSSProperties($styles);
|
||||
|
||||
// Formatear regla CSS completa
|
||||
return $this->formatCSSRule($selector, $cssProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filtra valores vacíos, null o que solo contienen espacios en blanco.
|
||||
*
|
||||
* @param array<string, mixed> $styles Array de estilos
|
||||
* @return array<string, string> Array filtrado
|
||||
*/
|
||||
private function filterEmptyValues(array $styles): array
|
||||
{
|
||||
return array_filter(
|
||||
$styles,
|
||||
fn($value) => $value !== null && $value !== '' && trim((string)$value) !== ''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convierte array de estilos a propiedades CSS formateadas.
|
||||
*
|
||||
* @param array<string, string> $styles Array de estilos
|
||||
* @return array<int, string> Array de propiedades CSS formateadas
|
||||
*/
|
||||
private function buildCSSProperties(array $styles): array
|
||||
{
|
||||
$properties = [];
|
||||
|
||||
foreach ($styles as $property => $value) {
|
||||
// Convertir snake_case a kebab-case
|
||||
$cssProperty = $this->convertToKebabCase($property);
|
||||
|
||||
// Normalizar nombre de propiedad
|
||||
$cssProperty = $this->normalizePropertyName($cssProperty);
|
||||
|
||||
// Sanitizar valor
|
||||
$sanitizedValue = $this->sanitizeValue((string)$value);
|
||||
|
||||
// Agregar propiedad formateada
|
||||
$properties[] = sprintf('%s: %s;', $cssProperty, $sanitizedValue);
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convierte snake_case a kebab-case.
|
||||
*
|
||||
* Ejemplos:
|
||||
* - background_color → background-color
|
||||
* - font_size → font-size
|
||||
* - padding_top → padding-top
|
||||
*
|
||||
* @param string $property Nombre de propiedad en snake_case
|
||||
* @return string Nombre de propiedad en kebab-case
|
||||
*/
|
||||
private function convertToKebabCase(string $property): string
|
||||
{
|
||||
return str_replace('_', '-', strtolower($property));
|
||||
}
|
||||
|
||||
/**
|
||||
* Normaliza nombres de propiedades CSS a su forma estándar.
|
||||
*
|
||||
* Mapea alias comunes a nombres de propiedades CSS estándar:
|
||||
* - text-color → color
|
||||
* - bg-color → background-color
|
||||
*
|
||||
* @param string $property Nombre de propiedad
|
||||
* @return string Nombre de propiedad normalizado
|
||||
*/
|
||||
private function normalizePropertyName(string $property): string
|
||||
{
|
||||
return self::PROPERTY_MAP[$property] ?? $property;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza valores CSS para prevenir inyección de código.
|
||||
*
|
||||
* Remueve tags HTML y caracteres potencialmente peligrosos,
|
||||
* manteniendo valores CSS válidos como colores, unidades, etc.
|
||||
*
|
||||
* @param string $value Valor CSS sin sanitizar
|
||||
* @return string Valor CSS sanitizado
|
||||
*/
|
||||
private function sanitizeValue(string $value): string
|
||||
{
|
||||
// Remover tags HTML
|
||||
$value = strip_tags($value);
|
||||
|
||||
// Remover caracteres de control excepto espacios
|
||||
$value = preg_replace('/[^\P{C}\s]/u', '', $value);
|
||||
|
||||
// Trim espacios
|
||||
$value = trim($value);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatea la regla CSS completa con selector y propiedades.
|
||||
*
|
||||
* Genera CSS con formato legible:
|
||||
* ```css
|
||||
* .selector {
|
||||
* property: value;
|
||||
* property2: value2;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param string $selector Selector CSS
|
||||
* @param array<int, string> $properties Array de propiedades formateadas
|
||||
* @return string Regla CSS completa
|
||||
*/
|
||||
private function formatCSSRule(string $selector, array $properties): string
|
||||
{
|
||||
if (empty($properties)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
"%s {\n %s\n}",
|
||||
$selector,
|
||||
implode("\n ", $properties)
|
||||
);
|
||||
}
|
||||
}
|
||||
51
Shared/Infrastructure/Services/CleanupService.php
Normal file
51
Shared/Infrastructure/Services/CleanupService.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Shared\Infrastructure\Services;
|
||||
|
||||
use ROITheme\Component\Infrastructure\Persistence\Wordpress\WordPressComponentRepository;
|
||||
use ROITheme\Component\Infrastructure\Persistence\Wordpress\WordPressDefaultsRepository;
|
||||
|
||||
/**
|
||||
* CleanupService - Limpieza de componentes obsoletos
|
||||
*
|
||||
* RESPONSABILIDAD: Eliminar componentes que ya no existen en schema
|
||||
*
|
||||
* @package ROITheme\Infrastructure\Services
|
||||
*/
|
||||
final class CleanupService
|
||||
{
|
||||
public function __construct(
|
||||
private WordPressComponentRepository $componentRepository,
|
||||
private WordPressDefaultsRepository $defaultsRepository
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Eliminar componentes que no tienen schema
|
||||
*
|
||||
* @return array ['removed' => array]
|
||||
*/
|
||||
public function removeObsolete(): array
|
||||
{
|
||||
// Obtener todos los componentes actuales
|
||||
$components = $this->componentRepository->findAll();
|
||||
|
||||
// Obtener schemas disponibles
|
||||
$schemas = $this->defaultsRepository->findAll();
|
||||
$validNames = array_keys($schemas);
|
||||
|
||||
$removed = [];
|
||||
|
||||
foreach ($components as $component) {
|
||||
$name = $component->name()->value();
|
||||
|
||||
// Si el componente no tiene schema, es obsoleto
|
||||
if (!in_array($name, $validNames)) {
|
||||
$this->componentRepository->delete($name);
|
||||
$removed[] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
return ['removed' => $removed];
|
||||
}
|
||||
}
|
||||
164
Shared/Infrastructure/Services/SchemaSyncService.php
Normal file
164
Shared/Infrastructure/Services/SchemaSyncService.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Shared\Infrastructure\Services;
|
||||
|
||||
use ROITheme\Shared\Infrastructure\Persistence\Wordpress\WordPressDefaultsRepository;
|
||||
|
||||
/**
|
||||
* SchemaSyncService - Sincronizar schemas JSON → BD
|
||||
*
|
||||
* RESPONSABILIDAD: Leer schemas desde archivos JSON y sincronizar con BD
|
||||
*
|
||||
* FLUJO:
|
||||
* 1. Leer archivos JSON de schemas
|
||||
* 2. Comparar con BD actual
|
||||
* 3. Agregar/Actualizar/Eliminar según diferencias
|
||||
*
|
||||
* @package ROITheme\Infrastructure\Services
|
||||
*/
|
||||
final class SchemaSyncService
|
||||
{
|
||||
private string $schemasPath;
|
||||
|
||||
public function __construct(
|
||||
private WordPressDefaultsRepository $defaultsRepository,
|
||||
string $schemasPath
|
||||
) {
|
||||
$this->schemasPath = rtrim($schemasPath, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sincronizar todos los schemas
|
||||
*
|
||||
* @return array ['success' => bool, 'data' => array]
|
||||
*/
|
||||
public function syncAll(): array
|
||||
{
|
||||
try {
|
||||
// 1. Leer schemas desde JSON
|
||||
$schemas = $this->readSchemasFromJson();
|
||||
|
||||
if (empty($schemas)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'No schemas found in JSON files'
|
||||
];
|
||||
}
|
||||
|
||||
// 2. Obtener schemas actuales de BD
|
||||
$currentSchemas = $this->defaultsRepository->findAll();
|
||||
|
||||
// 3. Determinar cambios
|
||||
$schemaNames = array_keys($schemas);
|
||||
$currentNames = array_keys($currentSchemas);
|
||||
|
||||
$toAdd = array_diff($schemaNames, $currentNames);
|
||||
$toUpdate = array_intersect($schemaNames, $currentNames);
|
||||
$toDelete = array_diff($currentNames, $schemaNames);
|
||||
|
||||
// 4. Aplicar cambios
|
||||
$added = [];
|
||||
$updated = [];
|
||||
$deleted = [];
|
||||
|
||||
foreach ($toAdd as $name) {
|
||||
$this->defaultsRepository->saveDefaults($name, $schemas[$name]);
|
||||
$added[] = $name;
|
||||
}
|
||||
|
||||
foreach ($toUpdate as $name) {
|
||||
$this->defaultsRepository->updateDefaults($name, $schemas[$name]);
|
||||
$updated[] = $name;
|
||||
}
|
||||
|
||||
foreach ($toDelete as $name) {
|
||||
$this->defaultsRepository->deleteDefaults($name);
|
||||
$deleted[] = $name;
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'added' => $added,
|
||||
'updated' => $updated,
|
||||
'deleted' => $deleted
|
||||
]
|
||||
];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sincronizar un componente específico
|
||||
*
|
||||
* @param string $componentName
|
||||
* @return array
|
||||
*/
|
||||
public function syncComponent(string $componentName): array
|
||||
{
|
||||
try {
|
||||
$schemas = $this->readSchemasFromJson();
|
||||
|
||||
if (!isset($schemas[$componentName])) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => "Schema not found for: {$componentName}"
|
||||
];
|
||||
}
|
||||
|
||||
$this->defaultsRepository->saveDefaults($componentName, $schemas[$componentName]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => ['synced' => $componentName]
|
||||
];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Leer schemas desde archivos JSON
|
||||
*
|
||||
* @return array Array asociativo [componentName => schema]
|
||||
*/
|
||||
private function readSchemasFromJson(): array
|
||||
{
|
||||
$schemas = [];
|
||||
|
||||
// Escanear directorio de schemas
|
||||
$files = glob($this->schemasPath . '/*.json');
|
||||
|
||||
if (empty($files)) {
|
||||
throw new \RuntimeException("No schema files found in: {$this->schemasPath}");
|
||||
}
|
||||
|
||||
foreach ($files as $file) {
|
||||
$content = file_get_contents($file);
|
||||
$schema = json_decode($content, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new \RuntimeException('Invalid JSON in file ' . basename($file) . ': ' . json_last_error_msg());
|
||||
}
|
||||
|
||||
if (!isset($schema['component_name'])) {
|
||||
throw new \RuntimeException('Missing component_name in schema file: ' . basename($file));
|
||||
}
|
||||
|
||||
$componentName = $schema['component_name'];
|
||||
$schemas[$componentName] = $schema;
|
||||
}
|
||||
|
||||
return $schemas;
|
||||
}
|
||||
}
|
||||
124
Shared/Infrastructure/Services/WordPressCacheService.php
Normal file
124
Shared/Infrastructure/Services/WordPressCacheService.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Shared\Infrastructure\Services;
|
||||
|
||||
use ROITheme\Shared\Domain\Contracts\CacheServiceInterface;
|
||||
|
||||
/**
|
||||
* WordPressCacheService - Cache con Transients API
|
||||
*
|
||||
* RESPONSABILIDAD: Gestionar cache de componentes
|
||||
*
|
||||
* IMPLEMENTACIÓN: WordPress Transients
|
||||
* - get_transient()
|
||||
* - set_transient()
|
||||
* - delete_transient()
|
||||
*
|
||||
* VENTAJAS:
|
||||
* - Compatible con object cache (Redis, Memcached)
|
||||
* - Expiración automática
|
||||
* - API simple
|
||||
*
|
||||
* @package ROITheme\Infrastructure\Services
|
||||
*/
|
||||
final class WordPressCacheService implements CacheServiceInterface
|
||||
{
|
||||
private const PREFIX = 'roi_theme_';
|
||||
|
||||
public function __construct(
|
||||
private \wpdb $wpdb
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Obtener valor del cache
|
||||
*
|
||||
* @param string $key Clave del cache
|
||||
* @return mixed|null Valor o null si no existe/expiró
|
||||
*/
|
||||
public function get(string $key): mixed
|
||||
{
|
||||
$transient = get_transient($this->getFullKey($key));
|
||||
|
||||
// WordPress devuelve false si no existe
|
||||
return $transient === false ? null : $transient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guardar valor en cache
|
||||
*
|
||||
* @param string $key Clave del cache
|
||||
* @param mixed $value Valor a guardar
|
||||
* @param int $expiration Tiempo de vida en segundos (default 1 hora)
|
||||
* @return bool True si guardó exitosamente
|
||||
*/
|
||||
public function set(string $key, mixed $value, int $expiration = 3600): bool
|
||||
{
|
||||
return set_transient($this->getFullKey($key), $value, $expiration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Eliminar entrada de cache
|
||||
*
|
||||
* @param string $key Clave del cache
|
||||
* @return bool True si eliminó exitosamente
|
||||
*/
|
||||
public function delete(string $key): bool
|
||||
{
|
||||
return $this->invalidate($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Limpiar todo el cache
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function flush(): bool
|
||||
{
|
||||
return $this->invalidateAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidar (eliminar) entrada de cache
|
||||
*
|
||||
* @param string $key Clave del cache
|
||||
* @return bool True si eliminó exitosamente
|
||||
*/
|
||||
public function invalidate(string $key): bool
|
||||
{
|
||||
return delete_transient($this->getFullKey($key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidar todo el cache de componentes
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function invalidateAll(): bool
|
||||
{
|
||||
// Obtener todos los componentes
|
||||
$components = $this->wpdb->get_col(
|
||||
"SELECT DISTINCT component_name FROM {$this->wpdb->prefix}roi_theme_components"
|
||||
);
|
||||
|
||||
$success = true;
|
||||
|
||||
foreach ($components as $componentName) {
|
||||
$result = $this->invalidate("component_{$componentName}");
|
||||
$success = $success && $result;
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener clave completa con prefijo
|
||||
*
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
private function getFullKey(string $key): string
|
||||
{
|
||||
return self::PREFIX . $key;
|
||||
}
|
||||
}
|
||||
172
Shared/Infrastructure/Services/WordPressValidationService.php
Normal file
172
Shared/Infrastructure/Services/WordPressValidationService.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Shared\Infrastructure\Services;
|
||||
|
||||
use ROITheme\Shared\Domain\Contracts\ValidationServiceInterface;
|
||||
use ROITheme\Shared\Domain\ValidationResult;
|
||||
use ROITheme\Component\Infrastructure\Persistence\Wordpress\WordPressDefaultsRepository;
|
||||
|
||||
/**
|
||||
* WordPressValidationService - Validación contra schemas
|
||||
*
|
||||
* RESPONSABILIDAD: Validar y sanitizar datos de componentes
|
||||
*
|
||||
* ESTRATEGIA:
|
||||
* 1. Obtener schema del componente desde BD
|
||||
* 2. Validar estructura contra schema
|
||||
* 3. Sanitizar datos usando funciones de WordPress
|
||||
*
|
||||
* @package ROITheme\Infrastructure\Services
|
||||
*/
|
||||
final class WordPressValidationService implements ValidationServiceInterface
|
||||
{
|
||||
public function __construct(
|
||||
private WordPressDefaultsRepository $defaultsRepository
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Validar datos contra schema
|
||||
*
|
||||
* @param array $data Datos a validar
|
||||
* @param string $componentName Nombre del componente (para obtener schema)
|
||||
* @return ValidationResult
|
||||
*/
|
||||
public function validate(array $data, string $componentName): ValidationResult
|
||||
{
|
||||
// 1. Obtener schema
|
||||
$schema = $this->defaultsRepository->find($componentName);
|
||||
|
||||
if ($schema === null) {
|
||||
return ValidationResult::failure([
|
||||
"Schema not found for component: {$componentName}"
|
||||
]);
|
||||
}
|
||||
|
||||
// 2. Sanitizar datos primero
|
||||
$sanitized = $this->sanitize($data, $componentName);
|
||||
|
||||
// 3. Validar estructura
|
||||
$errors = [];
|
||||
|
||||
foreach ($sanitized as $groupName => $fields) {
|
||||
// Verificar que el grupo existe en schema
|
||||
if (!isset($schema[$groupName])) {
|
||||
$errors[$groupName] = "Unknown group: {$groupName}";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validar cada campo del grupo
|
||||
if (is_array($fields)) {
|
||||
foreach ($fields as $key => $value) {
|
||||
if (!isset($schema[$groupName][$key])) {
|
||||
$errors["{$groupName}.{$key}"] = "Unknown field: {$groupName}.{$key}";
|
||||
}
|
||||
|
||||
// Validaciones adicionales pueden agregarse aquí
|
||||
// Por ejemplo, validar tipos, rangos, formatos, etc.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
return ValidationResult::failure($errors);
|
||||
}
|
||||
|
||||
return ValidationResult::success($sanitized);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizar datos recursivamente
|
||||
*
|
||||
* Usa funciones de WordPress según el tipo de dato
|
||||
*
|
||||
* @param array $data Datos a sanitizar
|
||||
* @param string $componentName Nombre del componente
|
||||
* @return array Datos sanitizados
|
||||
*/
|
||||
public function sanitize(array $data, string $componentName): array
|
||||
{
|
||||
$sanitized = [];
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
// Recursivo para arrays anidados
|
||||
$sanitized[$key] = $this->sanitizeValue($value);
|
||||
} else {
|
||||
$sanitized[$key] = $this->sanitizeValue($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $sanitized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizar un valor individual
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
private function sanitizeValue(mixed $value): mixed
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$sanitized = [];
|
||||
foreach ($value as $k => $v) {
|
||||
$sanitized[$k] = $this->sanitizeValue($v);
|
||||
}
|
||||
return $sanitized;
|
||||
}
|
||||
|
||||
if (is_bool($value)) {
|
||||
return (bool) $value;
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
return is_float($value) ? (float) $value : (int) $value;
|
||||
}
|
||||
|
||||
if (is_string($value) && filter_var($value, FILTER_VALIDATE_URL)) {
|
||||
return esc_url_raw($value);
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
return sanitize_text_field($value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validar una URL
|
||||
*
|
||||
* @param string $url
|
||||
* @return bool
|
||||
*/
|
||||
public function isValidUrl(string $url): bool
|
||||
{
|
||||
return filter_var($url, FILTER_VALIDATE_URL) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validar un color hexadecimal
|
||||
*
|
||||
* @param string $color
|
||||
* @return bool
|
||||
*/
|
||||
public function isValidColor(string $color): bool
|
||||
{
|
||||
return preg_match('/^#[0-9A-F]{6}$/i', $color) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validar nombre de componente
|
||||
*
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public function isValidComponentName(string $name): bool
|
||||
{
|
||||
// Solo letras minúsculas, números y guiones bajos
|
||||
return preg_match('/^[a-z0-9_]+$/', $name) === 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user