328 lines
13 KiB
PHP
328 lines
13 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace ROITheme\Admin\RecaptchaSettings\Infrastructure\Ui;
|
|
|
|
use ROITheme\Admin\Infrastructure\Ui\AdminDashboardRenderer;
|
|
|
|
/**
|
|
* FormBuilder para reCAPTCHA Settings
|
|
*
|
|
* RESPONSABILIDAD: Generar formulario de configuracion de Google reCAPTCHA v3
|
|
* para proteccion anti-spam de formularios.
|
|
*
|
|
* @package ROITheme\Admin\RecaptchaSettings\Infrastructure\Ui
|
|
*/
|
|
final class RecaptchaSettingsFormBuilder
|
|
{
|
|
public function __construct(
|
|
private AdminDashboardRenderer $renderer
|
|
) {}
|
|
|
|
public function buildForm(string $componentId): string
|
|
{
|
|
$html = '';
|
|
|
|
$html .= $this->buildHeader($componentId);
|
|
|
|
// LAYOUT 2 COLUMNAS
|
|
$html .= '<div class="row g-3">';
|
|
|
|
// COLUMNA IZQUIERDA (6 cols)
|
|
$html .= ' <div class="col-lg-6">';
|
|
$html .= $this->buildVisibilityGroup($componentId);
|
|
$html .= $this->buildCredentialsGroup($componentId);
|
|
$html .= ' </div>';
|
|
|
|
// COLUMNA DERECHA (6 cols)
|
|
$html .= ' <div class="col-lg-6">';
|
|
$html .= $this->buildBehaviorGroup($componentId);
|
|
$html .= $this->buildHelpSection();
|
|
$html .= ' </div>';
|
|
|
|
$html .= '</div>';
|
|
|
|
return $html;
|
|
}
|
|
|
|
private function buildHeader(string $componentId): string
|
|
{
|
|
$html = '<div class="rounded p-4 mb-4 shadow text-white" ';
|
|
$html .= 'style="background: linear-gradient(135deg, #0E2337 0%, #1e3a5f 100%); border-left: 4px solid #FF8600;">';
|
|
$html .= ' <div class="d-flex align-items-center justify-content-between flex-wrap gap-3">';
|
|
$html .= ' <div>';
|
|
$html .= ' <h3 class="h4 mb-1 fw-bold">';
|
|
$html .= ' <i class="bi bi-shield-check me-2" style="color: #FF8600;"></i>';
|
|
$html .= ' Google reCAPTCHA v3';
|
|
$html .= ' </h3>';
|
|
$html .= ' <p class="mb-0 small" style="opacity: 0.85;">';
|
|
$html .= ' Proteccion anti-spam invisible para formularios';
|
|
$html .= ' </p>';
|
|
$html .= ' </div>';
|
|
$html .= ' <button type="button" class="btn btn-sm btn-outline-light btn-reset-defaults" data-component="recaptcha-settings">';
|
|
$html .= ' <i class="bi bi-arrow-counterclockwise me-1"></i>';
|
|
$html .= ' Restaurar valores por defecto';
|
|
$html .= ' </button>';
|
|
$html .= ' </div>';
|
|
$html .= '</div>';
|
|
|
|
return $html;
|
|
}
|
|
|
|
private function buildVisibilityGroup(string $componentId): string
|
|
{
|
|
$html = '<div class="card shadow-sm mb-4" style="border-left: 4px solid #FF8600;">';
|
|
$html .= ' <div class="card-body">';
|
|
$html .= ' <h5 class="fw-bold mb-3" style="color: #1e3a5f;">';
|
|
$html .= ' <i class="bi bi-toggle-on me-2" style="color: #FF8600;"></i>';
|
|
$html .= ' Estado';
|
|
$html .= ' </h5>';
|
|
|
|
$isEnabled = $this->renderer->getFieldValue($componentId, 'visibility', 'is_enabled', false);
|
|
$html .= $this->buildToggle(
|
|
'recaptchaIsEnabled',
|
|
'Habilitar reCAPTCHA v3',
|
|
$isEnabled,
|
|
'Activa la proteccion reCAPTCHA en Newsletter y Formulario de Contacto'
|
|
);
|
|
|
|
$html .= ' </div>';
|
|
$html .= '</div>';
|
|
|
|
return $html;
|
|
}
|
|
|
|
private function buildCredentialsGroup(string $componentId): string
|
|
{
|
|
$html = '<div class="card shadow-sm mb-4" style="border-left: 4px solid #1e3a5f;">';
|
|
$html .= ' <div class="card-body">';
|
|
$html .= ' <h5 class="fw-bold mb-3" style="color: #1e3a5f;">';
|
|
$html .= ' <i class="bi bi-key me-2" style="color: #FF8600;"></i>';
|
|
$html .= ' Credenciales';
|
|
$html .= ' </h5>';
|
|
|
|
$html .= ' <div class="row g-3">';
|
|
|
|
// Site Key
|
|
$html .= ' <div class="col-md-6">';
|
|
$siteKey = $this->renderer->getFieldValue($componentId, 'credentials', 'site_key', '');
|
|
$html .= $this->buildTextInput(
|
|
'recaptchaSiteKey',
|
|
'Site Key (Clave del sitio)',
|
|
$siteKey,
|
|
'Clave publica visible en frontend'
|
|
);
|
|
$html .= ' </div>';
|
|
|
|
// Secret Key
|
|
$html .= ' <div class="col-md-6">';
|
|
$secretKey = $this->renderer->getFieldValue($componentId, 'credentials', 'secret_key', '');
|
|
$html .= $this->buildPasswordInput(
|
|
'recaptchaSecretKey',
|
|
'Secret Key (Clave secreta)',
|
|
$secretKey,
|
|
'Clave privada - NUNCA se expone en frontend'
|
|
);
|
|
$html .= ' </div>';
|
|
|
|
$html .= ' </div>';
|
|
|
|
$html .= ' <div class="alert alert-warning small mb-0 mt-3">';
|
|
$html .= ' <i class="bi bi-exclamation-triangle me-1"></i>';
|
|
$html .= ' Obtiene tus claves en <a href="https://www.google.com/recaptcha/admin" target="_blank" class="alert-link">Google reCAPTCHA Admin</a>. ';
|
|
$html .= ' Asegurate de seleccionar <strong>reCAPTCHA v3</strong>.';
|
|
$html .= ' </div>';
|
|
|
|
$html .= ' </div>';
|
|
$html .= '</div>';
|
|
|
|
return $html;
|
|
}
|
|
|
|
private function buildBehaviorGroup(string $componentId): string
|
|
{
|
|
$html = '<div class="card shadow-sm mb-4" style="border-left: 4px solid #1e3a5f;">';
|
|
$html .= ' <div class="card-body">';
|
|
$html .= ' <h5 class="fw-bold mb-3" style="color: #1e3a5f;">';
|
|
$html .= ' <i class="bi bi-sliders me-2" style="color: #FF8600;"></i>';
|
|
$html .= ' Comportamiento';
|
|
$html .= ' </h5>';
|
|
|
|
$html .= ' <div class="row g-3">';
|
|
|
|
// Score Threshold
|
|
$html .= ' <div class="col-md-6">';
|
|
$threshold = $this->renderer->getFieldValue($componentId, 'behavior', 'score_threshold', '0.5');
|
|
$html .= $this->buildSelect(
|
|
'recaptchaScoreThreshold',
|
|
'Umbral de Score',
|
|
$threshold,
|
|
[
|
|
'0.3' => '0.3 - Permisivo (menos bloqueos)',
|
|
'0.5' => '0.5 - Balanceado (recomendado)',
|
|
'0.7' => '0.7 - Estricto',
|
|
'0.9' => '0.9 - Muy estricto (mas bloqueos)'
|
|
],
|
|
'Score minimo para considerar humano (0=bot, 1=humano)'
|
|
);
|
|
$html .= ' </div>';
|
|
|
|
// Fail Open
|
|
$html .= ' <div class="col-md-6">';
|
|
$failOpen = $this->renderer->getFieldValue($componentId, 'behavior', 'fail_open', true);
|
|
$html .= $this->buildToggle(
|
|
'recaptchaFailOpen',
|
|
'Permitir si API falla',
|
|
$failOpen,
|
|
'Si Google no responde, permite el envio (recomendado)'
|
|
);
|
|
$html .= ' </div>';
|
|
|
|
$html .= ' </div>';
|
|
|
|
$html .= ' <hr class="my-3">';
|
|
|
|
$html .= ' <div class="row g-3">';
|
|
|
|
// Action Newsletter
|
|
$html .= ' <div class="col-md-4">';
|
|
$actionNewsletter = $this->renderer->getFieldValue($componentId, 'behavior', 'action_newsletter', 'newsletter_submit');
|
|
$html .= $this->buildTextInput(
|
|
'recaptchaActionNewsletter',
|
|
'Accion Newsletter',
|
|
$actionNewsletter,
|
|
'Identificador para formulario newsletter'
|
|
);
|
|
$html .= ' </div>';
|
|
|
|
// Action Contact
|
|
$html .= ' <div class="col-md-4">';
|
|
$actionContact = $this->renderer->getFieldValue($componentId, 'behavior', 'action_contact', 'contact_submit');
|
|
$html .= $this->buildTextInput(
|
|
'recaptchaActionContact',
|
|
'Accion Contacto',
|
|
$actionContact,
|
|
'Identificador para formulario contacto'
|
|
);
|
|
$html .= ' </div>';
|
|
|
|
// Log Blocked
|
|
$html .= ' <div class="col-md-4">';
|
|
$logBlocked = $this->renderer->getFieldValue($componentId, 'behavior', 'log_blocked', true);
|
|
$html .= $this->buildToggle(
|
|
'recaptchaLogBlocked',
|
|
'Registrar bloqueados',
|
|
$logBlocked,
|
|
'Guarda log de intentos bloqueados'
|
|
);
|
|
$html .= ' </div>';
|
|
|
|
$html .= ' </div>';
|
|
|
|
$html .= ' </div>';
|
|
$html .= '</div>';
|
|
|
|
return $html;
|
|
}
|
|
|
|
private function buildHelpSection(): string
|
|
{
|
|
$html = '<div class="card shadow-sm mb-3 bg-light">';
|
|
$html .= ' <div class="card-body">';
|
|
$html .= ' <h6 class="fw-bold mb-2" style="color: #1e3a5f;">';
|
|
$html .= ' <i class="bi bi-info-circle me-1" style="color: #FF8600;"></i>';
|
|
$html .= ' Como funciona reCAPTCHA v3';
|
|
$html .= ' </h6>';
|
|
$html .= ' <ul class="small mb-0">';
|
|
$html .= ' <li><strong>Invisible:</strong> No requiere interaccion del usuario (sin checkboxes ni imagenes)</li>';
|
|
$html .= ' <li><strong>Score:</strong> Google asigna un score de 0.0 (bot) a 1.0 (humano)</li>';
|
|
$html .= ' <li><strong>Protege:</strong> Newsletter Footer y Formulario de Contacto</li>';
|
|
$html .= ' <li><strong>Logs:</strong> Los intentos bloqueados se registran en <code>wp-content/debug.log</code></li>';
|
|
$html .= ' </ul>';
|
|
$html .= ' </div>';
|
|
$html .= '</div>';
|
|
|
|
return $html;
|
|
}
|
|
|
|
private function buildToggle(string $id, string $label, mixed $value, string $helpText = ''): string
|
|
{
|
|
$checked = ($value === true || $value === '1' || $value === 1) ? ' checked' : '';
|
|
|
|
$html = ' <div class="mb-3">';
|
|
$html .= ' <div class="form-check form-switch">';
|
|
$html .= ' <input class="form-check-input" type="checkbox" id="' . esc_attr($id) . '"' . $checked . '>';
|
|
$html .= ' <label class="form-check-label fw-semibold" for="' . esc_attr($id) . '">';
|
|
$html .= ' ' . esc_html($label);
|
|
$html .= ' </label>';
|
|
$html .= ' </div>';
|
|
if (!empty($helpText)) {
|
|
$html .= ' <div class="form-text small">' . esc_html($helpText) . '</div>';
|
|
}
|
|
$html .= ' </div>';
|
|
|
|
return $html;
|
|
}
|
|
|
|
private function buildTextInput(string $id, string $label, mixed $value, string $helpText = ''): string
|
|
{
|
|
$value = is_string($value) ? $value : '';
|
|
|
|
$html = ' <div class="mb-3">';
|
|
$html .= ' <label for="' . esc_attr($id) . '" class="form-label small mb-1 fw-semibold">';
|
|
$html .= ' ' . esc_html($label);
|
|
$html .= ' </label>';
|
|
$html .= ' <input type="text" class="form-control form-control-sm" id="' . esc_attr($id) . '" value="' . esc_attr($value) . '">';
|
|
if (!empty($helpText)) {
|
|
$html .= ' <div class="form-text small">' . esc_html($helpText) . '</div>';
|
|
}
|
|
$html .= ' </div>';
|
|
|
|
return $html;
|
|
}
|
|
|
|
private function buildPasswordInput(string $id, string $label, mixed $value, string $helpText = ''): string
|
|
{
|
|
$value = is_string($value) ? $value : '';
|
|
|
|
$html = ' <div class="mb-3">';
|
|
$html .= ' <label for="' . esc_attr($id) . '" class="form-label small mb-1 fw-semibold">';
|
|
$html .= ' ' . esc_html($label);
|
|
$html .= ' </label>';
|
|
$html .= ' <div class="input-group input-group-sm">';
|
|
$html .= ' <input type="password" class="form-control form-control-sm" id="' . esc_attr($id) . '" value="' . esc_attr($value) . '">';
|
|
$html .= ' <button class="btn btn-outline-secondary" type="button" onclick="togglePasswordVisibility(\'' . esc_attr($id) . '\')">';
|
|
$html .= ' <i class="bi bi-eye"></i>';
|
|
$html .= ' </button>';
|
|
$html .= ' </div>';
|
|
if (!empty($helpText)) {
|
|
$html .= ' <div class="form-text small">' . esc_html($helpText) . '</div>';
|
|
}
|
|
$html .= ' </div>';
|
|
|
|
return $html;
|
|
}
|
|
|
|
private function buildSelect(string $id, string $label, mixed $value, array $options, string $helpText = ''): string
|
|
{
|
|
$value = is_string($value) ? $value : '';
|
|
|
|
$html = ' <div class="mb-3">';
|
|
$html .= ' <label for="' . esc_attr($id) . '" class="form-label small mb-1 fw-semibold">';
|
|
$html .= ' ' . esc_html($label);
|
|
$html .= ' </label>';
|
|
$html .= ' <select class="form-select form-select-sm" id="' . esc_attr($id) . '">';
|
|
foreach ($options as $optionValue => $optionLabel) {
|
|
$selected = ($value === (string) $optionValue) ? ' selected' : '';
|
|
$html .= ' <option value="' . esc_attr($optionValue) . '"' . $selected . '>' . esc_html($optionLabel) . '</option>';
|
|
}
|
|
$html .= ' </select>';
|
|
if (!empty($helpText)) {
|
|
$html .= ' <div class="form-text small">' . esc_html($helpText) . '</div>';
|
|
}
|
|
$html .= ' </div>';
|
|
|
|
return $html;
|
|
}
|
|
}
|