| | |
| | | <?php |
| | | namespace JVBase\blocks; |
| | | |
| | | use JVBase\managers\CacheManager; |
| | | use JVBase\meta\MetaManager; |
| | | use JVBase\managers\CloudflareTurnstile; |
| | | use Exception; |
| | | use WP_Block; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\meta\Form; |
| | | use JVBase\base\Site; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; // Exit if accessed directly |
| | |
| | | */ |
| | | class FormBlock |
| | | { |
| | | protected static FormBlock|null $instance = null; |
| | | protected CacheManager $cache; |
| | | protected static ?FormBlock $instance = null; |
| | | protected Cache $cache; |
| | | protected array $forms; |
| | | protected string $form_contact; |
| | | |
| | |
| | | |
| | | public function __construct() |
| | | { |
| | | $this->cache = new CacheManager('form_blocks', HOUR_IN_SECONDS); |
| | | $this->cache = Cache::for('forms', WEEK_IN_SECONDS); |
| | | |
| | | // Initialize forms from filter |
| | | $this->forms = $this->registerForms(); |
| | |
| | | |
| | | // Register forms data for the block editor |
| | | add_action('enqueue_block_editor_assets', [$this, 'localizeFormsData']); |
| | | |
| | | add_action('init', [$this, 'registerBlock']); |
| | | add_filter('render_block', [$this, 'maybeEnqueueScripts'], 10, 2); |
| | | } |
| | | |
| | | /** |
| | | * Enqueue scripts when rendering form block |
| | | */ |
| | | public function maybeEnqueueScripts(string $block_content, array $block): string |
| | | { |
| | | // Only process our form blocks |
| | | if ($block['blockName'] !== 'jvb/forms') { |
| | | return $block_content; |
| | | } |
| | | |
| | | // Enqueue Turnstile if needed |
| | | if (Site::hasIntegration('cloudflare')) { |
| | | $cloudflare = JVB()->connect('cloudflare'); |
| | | if ($cloudflare->isSetUp()) { |
| | | $cloudflare->enqueueTurnstileScripts(); |
| | | } |
| | | } |
| | | |
| | | return $block_content; |
| | | } |
| | | |
| | | public function registerBlock() |
| | | { |
| | | register_block_type($this->path, [ |
| | | 'render_callback' => [$this, 'render'] |
| | | 'render_callback' => [$this, 'render'], |
| | | 'style' => 'jvb-icons-forms', |
| | | ]); |
| | | } |
| | | |
| | |
| | | 'sections' => [] |
| | | ]; |
| | | |
| | | $config = array_merge($defaults, $config); |
| | | |
| | | return $config; |
| | | return array_merge($defaults, $config); |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | $cache_key = $this->cache->generateKey($block); |
| | | $cached = $this->cache->get($cache_key); |
| | | |
| | | if ($cached) { |
| | | return $cached; |
| | | } |
| | | |
| | | $rendered = $this->renderForm($form_type, $block); |
| | | |
| | | $this->cache->set($cache_key, $rendered); |
| | | return $rendered; |
| | | return $this->cache->remember( |
| | | $cache_key, |
| | | function() use ($form_type, $block) { |
| | | return $this->renderForm($form_type, $block); |
| | | } |
| | | ); |
| | | } |
| | | |
| | | /** |
| | |
| | | $this->renderTurnstile(); |
| | | $this->renderFormEnd($type, $form_id); |
| | | echo '</div>'; |
| | | |
| | | return ob_get_clean(); |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | echo '<form id="' . esc_attr($form_id) . '" data-form-id="'.esc_attr($type).'" data-save="form" data-noautosave>'; |
| | | wp_nonce_field('jvb_form_' . $type); |
| | | echo '<form id="' . esc_attr($form_id) . '" data-form-id="'.esc_attr($type).'" data-save="form">'; |
| | | // wp_nonce_field('jvb_form_' . $type); |
| | | } |
| | | |
| | | /** |
| | |
| | | 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); |
| | | $this->renderSections($type); |
| | | } else { |
| | | echo jvbFormStatus(); |
| | | // Render fields directly |
| | | foreach ($form_config['fields'] as $field_name => $field_config) { |
| | | $meta->render('form', $field_name, $field_config); |
| | | echo Form::render($field_name, null, $field_config); |
| | | } |
| | | $submit_text = $form_config['submit'] ?? 'Submit'; |
| | | echo '<button type="submit" class="button primary">' . esc_html($submit_text) . '</button>'; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Render form sections |
| | | */ |
| | | protected function renderSections(string $type, MetaManager $meta): void |
| | | protected function renderSections(string $type): 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">'; |
| | | echo '<nav class="tabs row left" 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) . '" role="tab" aria-selected="' . $aria_selected . '">'; |
| | | 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) . '" role="tabpanel">'; |
| | | 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>'; |
| | |
| | | }); |
| | | |
| | | foreach ($section_fields as $field_name => $field_config) { |
| | | $meta->render('form', $field_name, $field_config); |
| | | echo Form::render($field_name, null, $field_config); |
| | | } |
| | | |
| | | if ($i === ($total - 1)){ |
| | | $submit_text = $form_config['submit'] ?? 'Submit'; |
| | | // Submit button |
| | | echo '<div class="form-actions">'; |
| | | echo '<button type="submit">' . esc_html($submit_text) . '</button>'; |
| | | echo '</div>'; |
| | | // Add step navigation buttons |
| | | echo '<div class="step-navigation row x-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++; |
| | | } |
| | |
| | | */ |
| | | protected function renderTurnstile(): void |
| | | { |
| | | if (!jvbSiteUsesCloudflare()) { |
| | | if (!Site::hasIntegration('cloudflare')) { |
| | | return; |
| | | } |
| | | |
| | |
| | | echo '<input type="hidden" name="timestamp" value="' . time() . '">'; |
| | | |
| | | echo '</form>'; |
| | | |
| | | echo '<template class="formSummary"> |
| | | <section class="form-summary"> |
| | | <h2></h2> |
| | | <div class="message"><p>You\'ll get an email with this information, too. If you need to make any changes, respond to that email.</p></div> |
| | | <h3>Summary:</h3>'; |
| | | |
| | | foreach($form_config['fields'] as $field=>$config) { |
| | | $label = $config['summaryTitle'] ?? $config['label']; |
| | | |
| | | echo '<div id="'.$field.'"><h4>'.$label.'</h4><div></div></div>'; |
| | | } |
| | | echo '</section></template>'; |
| | | } |
| | | |
| | | /** |
| | |
| | | ]; |
| | | } |
| | | |
| | | 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, |