- Verificación de entorno XAMPP (PHP 8.0.30, Composer 2.9.1, WP-CLI 2.12.0) - Configuración de Composer con PSR-4 para 24 namespaces - Configuración de PHPUnit con 140 tests preparados - Configuración de PHPCS con WordPress Coding Standards - Scripts de backup y rollback con mejoras de seguridad - Estructura de contextos (admin/, public/, shared/) - Schemas JSON para 11 componentes del sistema - Código fuente inicial con arquitectura limpia en src/ - Documentación de procedimientos de emergencia 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
239 lines
10 KiB
PHP
239 lines
10 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace ROITheme\ContactFormSection\Infrastructure\Presentation\Public;
|
|
|
|
use ROITheme\Component\Domain\Component;
|
|
use ROITheme\Component\Domain\RendererInterface;
|
|
|
|
final class ContactFormSectionRenderer implements RendererInterface
|
|
{
|
|
public function render(Component $component): string
|
|
{
|
|
$data = $component->getData();
|
|
|
|
if (!($data['section']['show_section'] ?? true)) {
|
|
return '';
|
|
}
|
|
|
|
$componentId = $component->getId();
|
|
$sectionTitle = $data['section']['section_title'] ?? '¿Tienes alguna pregunta?';
|
|
$sectionSubtitle = $data['section']['section_subtitle'] ?? 'Completa el formulario y nuestro equipo te responderá en menos de 24 horas.';
|
|
$backgroundClass = $data['styles']['background_color'] ?? 'bg-secondary bg-opacity-25';
|
|
$customStyles = $this->generateCustomStyles($componentId, $data['styles'] ?? []);
|
|
|
|
ob_start();
|
|
?>
|
|
<?php if (!empty($customStyles)): ?>
|
|
<style>
|
|
<?php echo $customStyles; ?>
|
|
</style>
|
|
<?php endif; ?>
|
|
|
|
<section id="<?php echo esc_attr($componentId); ?>" class="contact-form-section <?php echo esc_attr($backgroundClass); ?> py-5">
|
|
<div class="container">
|
|
<div class="row justify-content-center mb-5">
|
|
<div class="col-lg-8 text-center">
|
|
<h2 class="h2 mb-3"><?php echo esc_html($sectionTitle); ?></h2>
|
|
<?php if (!empty($sectionSubtitle)): ?>
|
|
<p class="lead text-muted"><?php echo esc_html($sectionSubtitle); ?></p>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-4">
|
|
<div class="col-lg-5">
|
|
<?php echo $this->renderContactInfo($data); ?>
|
|
</div>
|
|
|
|
<div class="col-lg-7">
|
|
<?php echo $this->renderForm($componentId, $data); ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<?php
|
|
return ob_get_clean();
|
|
}
|
|
|
|
private function renderContactInfo(array $data): string
|
|
{
|
|
$contactInfo = $data['contact_info'] ?? [];
|
|
|
|
ob_start();
|
|
?>
|
|
<div class="contact-info h-100">
|
|
<h3 class="h4 mb-4">Información de contacto</h3>
|
|
<div class="contact-items">
|
|
<?php if ($contactInfo['phone_enabled'] ?? true): ?>
|
|
<div class="contact-item mb-4">
|
|
<div class="d-flex align-items-start">
|
|
<i class="bi bi-telephone-fill contact-icon me-3"></i>
|
|
<div>
|
|
<h4 class="h6 mb-1"><?php echo esc_html($contactInfo['phone_label'] ?? 'Teléfono'); ?></h4>
|
|
<p class="mb-0"><?php echo esc_html($contactInfo['phone_value'] ?? '+52 55 1234 5678'); ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($contactInfo['email_enabled'] ?? true): ?>
|
|
<div class="contact-item mb-4">
|
|
<div class="d-flex align-items-start">
|
|
<i class="bi bi-envelope-fill contact-icon me-3"></i>
|
|
<div>
|
|
<h4 class="h6 mb-1"><?php echo esc_html($contactInfo['email_label'] ?? 'Email'); ?></h4>
|
|
<p class="mb-0"><?php echo esc_html($contactInfo['email_value'] ?? 'contacto@example.com'); ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($contactInfo['location_enabled'] ?? true): ?>
|
|
<div class="contact-item mb-4">
|
|
<div class="d-flex align-items-start">
|
|
<i class="bi bi-geo-alt-fill contact-icon me-3"></i>
|
|
<div>
|
|
<h4 class="h6 mb-1"><?php echo esc_html($contactInfo['location_label'] ?? 'Ubicación'); ?></h4>
|
|
<p class="mb-0"><?php echo esc_html($contactInfo['location_value'] ?? 'Ciudad de México, México'); ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
return ob_get_clean();
|
|
}
|
|
|
|
private function renderForm(string $componentId, array $data): string
|
|
{
|
|
$submitButtonText = $data['form']['submit_button_text'] ?? 'Enviar Mensaje';
|
|
$submitButtonIcon = $data['form']['submit_button_icon'] ?? 'bi-send-fill';
|
|
$successMessage = $data['form']['success_message'] ?? '¡Gracias! Tu mensaje ha sido enviado correctamente.';
|
|
$errorMessage = $data['form']['error_message'] ?? 'Hubo un error al enviar el mensaje.';
|
|
|
|
ob_start();
|
|
?>
|
|
<div class="contact-form-wrapper">
|
|
<form id="contact-form-<?php echo esc_attr($componentId); ?>" class="contact-form needs-validation" novalidate>
|
|
<?php wp_nonce_field('roi_contact_form_' . $componentId, 'roi_contact_nonce'); ?>
|
|
<input type="hidden" name="action" value="roi_contact_form_submit">
|
|
<input type="hidden" name="component_id" value="<?php echo esc_attr($componentId); ?>">
|
|
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<input type="text" class="form-control" name="fullName" placeholder="Nombre completo *" required>
|
|
<div class="invalid-feedback">Por favor ingresa tu nombre</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<input type="text" class="form-control" name="company" placeholder="Empresa">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<input type="tel" class="form-control" name="whatsapp" placeholder="WhatsApp *" required>
|
|
<div class="invalid-feedback">Por favor ingresa tu WhatsApp</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<input type="email" class="form-control" name="email" placeholder="Correo electrónico *" required>
|
|
<div class="invalid-feedback">Por favor ingresa un email válido</div>
|
|
</div>
|
|
<div class="col-12">
|
|
<textarea class="form-control" name="comments" rows="4" placeholder="¿En qué podemos ayudarte?"></textarea>
|
|
</div>
|
|
<div class="col-12">
|
|
<button type="submit" class="btn btn-primary btn-lg w-100">
|
|
<i class="bi <?php echo esc_attr($submitButtonIcon); ?> me-2"></i>
|
|
<?php echo esc_html($submitButtonText); ?>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="alert alert-success mt-3 d-none" role="alert" id="form-success-<?php echo esc_attr($componentId); ?>">
|
|
<?php echo esc_html($successMessage); ?>
|
|
</div>
|
|
<div class="alert alert-danger mt-3 d-none" role="alert" id="form-error-<?php echo esc_attr($componentId); ?>">
|
|
<?php echo esc_html($errorMessage); ?>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<script>
|
|
(function() {
|
|
const form = document.getElementById('contact-form-<?php echo esc_js($componentId); ?>');
|
|
if (!form) return;
|
|
|
|
form.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
if (!form.checkValidity()) {
|
|
e.stopPropagation();
|
|
form.classList.add('was-validated');
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData(form);
|
|
const submitBtn = form.querySelector('button[type="submit"]');
|
|
const successAlert = document.getElementById('form-success-<?php echo esc_js($componentId); ?>');
|
|
const errorAlert = document.getElementById('form-error-<?php echo esc_js($componentId); ?>');
|
|
|
|
submitBtn.disabled = true;
|
|
successAlert.classList.add('d-none');
|
|
errorAlert.classList.add('d-none');
|
|
|
|
fetch('<?php echo esc_url(admin_url('admin-ajax.php')); ?>', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
successAlert.classList.remove('d-none');
|
|
form.reset();
|
|
form.classList.remove('was-validated');
|
|
} else {
|
|
errorAlert.classList.remove('d-none');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
errorAlert.classList.remove('d-none');
|
|
})
|
|
.finally(() => {
|
|
submitBtn.disabled = false;
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
<?php
|
|
return ob_get_clean();
|
|
}
|
|
|
|
private function generateCustomStyles(string $componentId, array $styles): string
|
|
{
|
|
if (empty($styles)) {
|
|
return '';
|
|
}
|
|
|
|
$css = [];
|
|
|
|
if (isset($styles['icon_color'])) {
|
|
$css[] = "#$componentId .contact-icon { color: {$styles['icon_color']}; font-size: 1.5rem; }";
|
|
}
|
|
|
|
if (isset($styles['button_bg_color'])) {
|
|
$css[] = "#$componentId .btn-primary { background-color: {$styles['button_bg_color']}; border-color: {$styles['button_bg_color']}; }";
|
|
}
|
|
|
|
if (isset($styles['button_hover_bg'])) {
|
|
$css[] = "#$componentId .btn-primary:hover { background-color: {$styles['button_hover_bg']}; border-color: {$styles['button_hover_bg']}; }";
|
|
}
|
|
|
|
return implode("\n", $css);
|
|
}
|
|
|
|
public function supports(string $componentType): bool
|
|
{
|
|
return $componentType === 'contact-form-section';
|
|
}
|
|
}
|