<?php
|
namespace JVBase\blocks;
|
|
use JVBase\managers\CacheManager;
|
use JVBase\meta\MetaManager;
|
use JVBase\managers\CloudflareTurnstile;
|
use Exception;
|
use WP_Block;
|
|
if (!defined('ABSPATH')) {
|
exit; // Exit if accessed directly
|
}
|
|
/**
|
* Form Block Class
|
*
|
* Handles form rendering and registration using the same pattern as other blocks
|
*/
|
class FormBlock
|
{
|
protected static FormBlock|null $instance = null;
|
protected CacheManager $cache;
|
protected array $forms;
|
protected string $form_contact;
|
|
protected string $path = JVB_DIR.'/build/forms';
|
|
public static function getInstance():FormBlock
|
{
|
if (self::$instance === null) {
|
self::$instance = new self();
|
}
|
return self::$instance;
|
}
|
|
public function __construct()
|
{
|
$this->cache = new CacheManager('form_blocks', HOUR_IN_SECONDS);
|
|
// Initialize forms from filter
|
$this->forms = $this->registerForms();
|
$this->form_contact = apply_filters('jvb_form_contact', '');
|
|
// Hook into the CustomBlocks render system
|
add_filter('jvb_render_block_jvb_forms', [$this, 'render'], 10, 2);
|
|
// Register forms data for the block editor
|
add_action('enqueue_block_editor_assets', [$this, 'localizeFormsData']);
|
add_action('init', [$this, 'registerBlock']);
|
}
|
|
public function registerBlock()
|
{
|
register_block_type($this->path, [
|
'render_callback' => [$this, 'render']
|
]);
|
}
|
|
/**
|
* Register available forms
|
*/
|
protected function registerForms(): array
|
{
|
// Default forms can be registered here
|
$default_forms = [];
|
|
// Allow other plugins to register forms
|
$forms = apply_filters('jvb_register_forms', $default_forms);
|
|
// Process forms to ensure they have proper structure
|
$processed_forms = [];
|
foreach ($forms as $form_key => $form_config) {
|
$processed_forms[$form_key] = $this->processFormConfig($form_config);
|
}
|
|
return $processed_forms;
|
}
|
|
/**
|
* Process form configuration to ensure proper structure
|
*/
|
protected function processFormConfig(array $config): array
|
{
|
$defaults = [
|
'title' => 'Form',
|
'description' => [],
|
'submit' => 'Submit',
|
'success_title' => 'Thank You!',
|
'success_message' => ['Your message has been sent successfully.'],
|
'email_to' => get_option('admin_email'),
|
'email_subject' => 'New Form Submission',
|
'fields' => [],
|
'sections' => []
|
];
|
|
$config = array_merge($defaults, $config);
|
|
return $config;
|
}
|
|
/**
|
* Render the form block
|
*/
|
public function render(array $block, string $content): string
|
{
|
$form_type = $block['formType'];
|
|
if (empty($form_type) || !isset($this->forms[$form_type])) {
|
return '<div class="jvb-form-error">No valid form type selected. Please edit this block and select a form type.</div>';
|
}
|
|
$cache_key = $this->cache->generateKey($block);
|
$cached = $this->cache->get($cache_key);
|
$cached = false;
|
if ($cached) {
|
return $cached;
|
}
|
|
$rendered = $this->renderForm($form_type, $block);
|
|
$this->cache->set($cache_key, $rendered);
|
return $rendered;
|
}
|
|
/**
|
* Render the complete form
|
*/
|
protected function renderForm(string $type, array $attributes): string
|
{
|
$form_config = $this->forms[$type];
|
$custom_email_to = $attributes['customEmailTo'] ?? '';
|
$show_labels = $attributes['showLabels'] ?? true;
|
|
// Override email recipient if specified
|
if (!empty($custom_email_to)) {
|
$form_config['email_to'] = $custom_email_to;
|
}
|
|
// Allow filtering of email recipient
|
$form_config['email_to'] = apply_filters('jvb_form_email_to', $form_config['email_to'], $type, $attributes);
|
|
$submitted = get_query_var('jvb_submitted', false);
|
$error = get_query_var('jvb_form_error', false);
|
|
ob_start();
|
|
// Handle success state
|
if ($submitted) {
|
return $this->renderSuccessMessage($type, $submitted);
|
}
|
|
// Handle error state
|
if ($error) {
|
echo $this->renderErrorMessage($error);
|
}
|
|
// Generate unique form ID
|
$form_id = uniqid($type . '_');
|
|
// Render form
|
echo '<div class="jvb-form-block">';
|
$this->renderFormStart($type, $form_id, $attributes);
|
$this->renderFormFields($type, $show_labels);
|
$this->renderTurnstile();
|
$this->renderFormEnd($type, $form_id);
|
echo '</div>';
|
|
return ob_get_clean();
|
}
|
|
/**
|
* Render success message
|
*/
|
protected function renderSuccessMessage(string $type, string $submission_id): string
|
{
|
$form_config = $this->forms[$type];
|
$submission_data = $this->cache->get('submission_' . $submission_id);
|
|
ob_start();
|
|
echo '<div class="form-success">';
|
echo '<h2>' . esc_html($form_config['success_title']) . '</h2>';
|
|
if (!empty($form_config['success_message'])) {
|
foreach ((array) $form_config['success_message'] as $message) {
|
echo '<p>' . wp_kses_post($message) . '</p>';
|
}
|
}
|
|
if ($submission_data) {
|
echo '<div class="submission-summary">';
|
echo '<h3>Your submission:</h3>';
|
echo '<ul>';
|
foreach ($submission_data as $key => $value) {
|
if (!in_array($key, ['action', 'form_id', 'form_type', 'timestamp', '_wpnonce'])) {
|
$label = $this->getFieldLabel($type, $key);
|
if (is_array($value)) {
|
$value = implode(', ', $value);
|
}
|
echo '<li><strong>' . esc_html($label) . ':</strong> ' . esc_html($value) . '</li>';
|
}
|
}
|
echo '</ul>';
|
echo '</div>';
|
}
|
|
if (!empty($this->form_contact)) {
|
echo '<p>' . wp_kses_post($this->form_contact) . '</p>';
|
}
|
|
echo '</div>';
|
|
return ob_get_clean();
|
}
|
|
/**
|
* Render error message
|
*/
|
protected function renderErrorMessage(string $error): string
|
{
|
$message = urldecode($error);
|
$output = '<div class="form-error">';
|
$output .= '<h2>Error</h2>';
|
$output .= '<p>' . esc_html($message) . '</p>';
|
if (!empty($this->form_contact)) {
|
$output .= '<p>' . wp_kses_post($this->form_contact) . '</p>';
|
}
|
$output .= '</div>';
|
|
return $output;
|
}
|
|
/**
|
* Get field label for display
|
*/
|
protected function getFieldLabel(string $form_type, string $field_name): string
|
{
|
$form_config = $this->forms[$form_type];
|
|
if (isset($form_config['fields'][$field_name]['label'])) {
|
return $form_config['fields'][$field_name]['label'];
|
}
|
|
return ucfirst(str_replace('_', ' ', $field_name));
|
}
|
|
/**
|
* Render form start tag
|
*/
|
protected function renderFormStart(string $type, string $form_id, array $attributes): void
|
{
|
$form_config = $this->forms[$type];
|
|
// Add form title and description
|
if (!empty($form_config['title'])) {
|
echo '<h2>' . esc_html($form_config['title']) . '</h2>';
|
}
|
|
if (!empty($form_config['description'])) {
|
foreach ((array) $form_config['description'] as $desc) {
|
echo '<p>' . wp_kses_post($desc) . '</p>';
|
}
|
}
|
|
echo '<form id="' . esc_attr($form_id) . '" data-form-id="'.esc_attr($type).'" data-save="form" data-noautosave>';
|
wp_nonce_field('jvb_form_' . $type);
|
}
|
|
/**
|
* Render form fields
|
*/
|
protected function renderFormFields(string $type, bool $show_labels): void
|
{
|
$form_config = $this->forms[$type];
|
|
if (empty($form_config['fields'])) {
|
return;
|
}
|
|
// Create MetaManager instance for form rendering
|
$meta = new MetaManager();
|
|
// If sections are defined, render in sections
|
if (!empty($form_config['sections'])) {
|
$this->renderSections($type, $meta);
|
} else {
|
echo jvbFormStatus();
|
// Render fields directly
|
foreach ($form_config['fields'] as $field_name => $field_config) {
|
$meta->render('form', $field_name, $field_config);
|
}
|
}
|
}
|
|
/**
|
* Render form sections
|
*/
|
protected function renderSections(string $type, MetaManager $meta): void
|
{
|
$form_config = $this->forms[$type];
|
$sections = $form_config['sections'];
|
$fields = $form_config['fields'];
|
$total = count($sections);
|
|
echo '<div class="container">';
|
if ($total > 1) {
|
echo '<div class="form-progress">';
|
jvbRenderProgressBar('<span class="step-text">Step <span class="current">1</span> of ' . $total . '</span>');
|
echo '</div>';
|
}
|
|
// Render navigation if multiple sections
|
if (count($sections) > 1) {
|
echo '<nav class="tabs row start" role="tablist">';
|
$i = 1;
|
foreach ($sections as $slug => $section) {
|
$active_class = $i === 1 ? ' active' : '';
|
$aria_selected = $i === 1 ? 'true' : 'false';
|
|
echo '<button type="button" class="tab' . $active_class . '" data-tab="' . esc_attr($slug) . '" data-step="'.$i.'" role="tab" aria-selected="' . $aria_selected . '">';
|
echo '<span class="step-number">' . $i . '</span>';
|
echo '<h2>' . esc_html($section['label'] ?? $section) . '</h2>';
|
echo '</button>';
|
$i++;
|
}
|
echo '</nav>';
|
}
|
echo jvbFormStatus();
|
|
// Render section content
|
$i = 0;
|
$total = count($sections);
|
foreach ($sections as $slug => $section) {
|
$active_class = $i === 0 ? ' active' : '';
|
$is_last = $i === ($total -1);
|
|
echo '<section id="' . esc_attr($slug) . '" class="tab-content' . $active_class . '" data-tab="' . esc_attr($slug) . '" data-step="'.($i + 1).'" role="tabpanel">';
|
|
if (is_array($section) && !empty($section['title'])) {
|
echo '<h2>' . esc_html($section['title']) . '</h2>';
|
}
|
|
if (is_array($section) && !empty($section['description'])) {
|
echo '<p class="section-description">' . wp_kses_post($section['description']) . '</p>';
|
}
|
|
// Render fields for this section
|
$section_fields = array_filter($fields, function($field) use ($slug) {
|
return isset($field['section']) && $field['section'] === $slug;
|
});
|
|
foreach ($section_fields as $field_name => $field_config) {
|
$meta->render('form', $field_name, $field_config);
|
}
|
|
// Add step navigation buttons
|
echo '<div class="step-navigation row btw">';
|
|
if ($i > 0) {
|
echo '<button type="button" class="button secondary prev-step" data-action="prev-step">';
|
echo jvbIcon('caret-left');
|
echo '<span>Previous</span>';
|
echo '</button>';
|
} else {
|
echo '<div></div>'; // Spacer for flex layout
|
}
|
|
if ($is_last) {
|
$submit_text = $form_config['submit'] ?? 'Submit';
|
echo '<button type="submit" class="button primary">' . esc_html($submit_text) . '</button>';
|
} else {
|
echo '<button type="button" class="button primary next-step" data-action="next-step">';
|
echo '<span>Next Step</span>';
|
echo jvbIcon('caret-right');
|
echo '</button>';
|
}
|
|
echo '</div>'; // .step-navigation
|
|
echo '</section>';
|
$i++;
|
}
|
|
echo '</div>';
|
}
|
|
/**
|
* Render Cloudflare Turnstile
|
*/
|
protected function renderTurnstile(): void
|
{
|
if (!jvbSiteUsesCloudflare()) {
|
return;
|
}
|
|
$cloudflare = JVB()->connect('cloudflare');
|
if ($cloudflare->isSetUp()) {
|
$cloudflare->renderTurnstile();
|
}
|
}
|
|
/**
|
* Render form end tag
|
*/
|
protected function renderFormEnd(string $type, string $form_id): void
|
{
|
$form_config = $this->forms[$type];
|
|
|
// Hidden fields
|
echo '<input type="hidden" name="action" value="jvb_form_submission">';
|
echo '<input type="hidden" name="form_id" value="' . esc_attr($form_id) . '">';
|
echo '<input type="hidden" name="form_type" value="' . esc_attr($type) . '">';
|
echo '<input type="hidden" name="timestamp" value="' . time() . '">';
|
|
echo '</form>';
|
}
|
|
/**
|
* Localize forms data for block editor
|
*/
|
public function localizeFormsData(): void
|
{
|
$form_types = [
|
[
|
'label' => __('Select a form type', 'jvb'),
|
'value' => ''
|
]
|
];
|
|
foreach ($this->forms as $form_key => $form_config) {
|
$form_types[] = [
|
'label' => $form_config['title'] ?? ucwords(str_replace('-', ' ', $form_key)),
|
'value' => $form_key
|
];
|
}
|
|
error_log('Form Localization: '.print_r([
|
'formTypes' => $form_types,
|
'availableForms' => $this->forms,
|
'nonce' => wp_create_nonce('jvbForm')
|
], true));
|
wp_localize_script('jvb-forms-editor-script', 'jvbFormsData', [
|
'formTypes' => $form_types,
|
'availableForms' => $this->forms,
|
'nonce' => wp_create_nonce('jvbForm')
|
]);
|
}
|
|
/**
|
* Get registered forms
|
*/
|
public static function getForms(): array
|
{
|
return self::getInstance()->forms??[];
|
}
|
|
/**
|
* Get specific form configuration
|
*/
|
public static function getForm($type):array|null
|
{
|
return self::getInstance()->forms[$type] ?? null;
|
}
|
}
|