cache = Cache::for('forms', WEEK_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']); 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'], 'style' => 'jvb-icons-forms', ]); } /** * 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' => [] ]; return array_merge($defaults, $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 '
No valid form type selected. Please edit this block and select a form type.
'; } $cache_key = $this->cache->generateKey($block); return $this->cache->remember( $cache_key, function() use ($form_type, $block) { return $this->renderForm($form_type, $block); } ); } /** * 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 '
'; $this->renderFormStart($type, $form_id, $attributes); $this->renderFormFields($type, $show_labels); $this->renderTurnstile(); $this->renderFormEnd($type, $form_id); echo '
'; 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 '
'; echo '

' . esc_html($form_config['success_title']) . '

'; if (!empty($form_config['success_message'])) { foreach ((array) $form_config['success_message'] as $message) { echo '

' . wp_kses_post($message) . '

'; } } if ($submission_data) { echo '
'; echo '

Your submission:

'; echo ''; echo '
'; } if (!empty($this->form_contact)) { echo '

' . wp_kses_post($this->form_contact) . '

'; } echo '
'; return ob_get_clean(); } /** * Render error message */ protected function renderErrorMessage(string $error): string { $message = urldecode($error); $output = '
'; $output .= '

Error

'; $output .= '

' . esc_html($message) . '

'; if (!empty($this->form_contact)) { $output .= '

' . wp_kses_post($this->form_contact) . '

'; } $output .= '
'; 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 '

' . esc_html($form_config['title']) . '

'; } if (!empty($form_config['description'])) { foreach ((array) $form_config['description'] as $desc) { echo '

' . wp_kses_post($desc) . '

'; } } echo '
'; // 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; } // If sections are defined, render in sections if (!empty($form_config['sections'])) { $this->renderSections($type); } else { echo jvbFormStatus(); // Render fields directly foreach ($form_config['fields'] as $field_name => $field_config) { echo Form::render($field_name, null, $field_config); } $submit_text = $form_config['submit'] ?? 'Submit'; echo ''; } } /** * Render form sections */ protected function renderSections(string $type): void { $form_config = $this->forms[$type]; $sections = $form_config['sections']; $fields = $form_config['fields']; $total = count($sections); echo '
'; if ($total > 1) { echo '
'; jvbRenderProgressBar('Step 1 of ' . $total . ''); echo '
'; } // Render navigation if multiple sections if (count($sections) > 1) { echo ''; } 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 '
'; if (is_array($section) && !empty($section['title'])) { echo '

' . esc_html($section['title']) . '

'; } if (is_array($section) && !empty($section['description'])) { echo '

' . wp_kses_post($section['description']) . '

'; } // 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) { echo Form::render($field_name, null, $field_config); } // Add step navigation buttons echo '
'; if ($i > 0) { echo ''; } else { echo '
'; // Spacer for flex layout } if ($is_last) { $submit_text = $form_config['submit'] ?? 'Submit'; echo ''; } else { echo ''; } echo '
'; // .step-navigation echo '
'; $i++; } echo '
'; } /** * Render Cloudflare Turnstile */ protected function renderTurnstile(): void { if (!Site::hasIntegration('cloudflare')) { 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 ''; echo ''; echo ''; echo ''; echo '
'; } /** * 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 ]; } 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; } }