settings = new Admin\Inc\Form\Settings(); $this->entry_settings = new Admin\Inc\Entry\Settings(); $this->builder = new Admin\Inc\Form\Builder(); } $this->csrf_token_manager = new CsrfTokenManager(); $this->actions(); $this->filters(); \add_shortcode('form', [$this, 'shortcode']); } public function shortcode($atts) { $atts = shortcode_atts( array( 'name' => '' ), $atts, 'ajaxy-forms' ); $name = $atts['name'] ?? ''; if ('' === $name) { return ''; } else { $form = Data::parse_form($name); if ($form) { $parsed_form = $this->create_form($form, $form->get_initial_data()); if (is_wp_error($parsed_form)) { return \sprintf(__('Error creating form for %1$s: %2$s', "ajaxy-forms"), $name, $parsed_form->get_error_message()); } $this->on_submit($name, $parsed_form); return $this->render($name, $parsed_form, $form->get_theme()); } else { return \sprintf(__('Form %s not found', "ajaxy-forms"), $name); } } } public function create_form(Form $form, $initial_data = null) { // Set up the Validator component $validator = Validation::createValidator(); // Set up the Form component $formFactory = Forms::createFormFactoryBuilder() ->addExtension(new CsrfExtension($this->csrf_token_manager)) ->addExtension(new ValidatorExtension($validator)) ->getFormFactory(); $options = \array_replace([ 'csrf_protection' => false, 'csrf_message' => __('It appears you\'ve already submitted this form, or it may have timed out. Please refresh the page and try again.', "ajaxy-forms"), 'csrf_token_id' => 'form_intention_' . $form->get_name(), 'allow_extra_fields' => true, 'required' => false, ], $form->get_options()); unset($options['messages']); if (isset($options['attr']['class'])) { $options['attr']['class'] .= ' ajaxy-form'; } else { $options['attr']['class'] = 'ajaxy-form'; } $options['action'] = $form->is_ajax() ? get_rest_url(null, sprintf('ajaxy-forms/v1/form/%s', $form->get_name())) : ''; $builder = $formFactory->createNamedBuilder($form->get_name(), Type\FormType::class, $initial_data, $options); $start_repeater = ''; $repeater = []; foreach ($form->get_fields() as $field) { $field_options = $field; $field['constraints'] = $field['constraints'] ?? []; if (isset($field['required']) && $field['required'] == "1") { $field['constraints'][] = ['type' => 'not_blank', 'message' => $field_options['invalid_message'] ?? __('This field is required.')]; $field['required'] = ''; } if (!empty($field['constraints'])) { $field_options['constraints'] = \array_filter(\array_map(function ($constraint) { return Constraints::getInstance()->create_constraint($constraint); }, $field['constraints'] ?? []), function ($constraint) { return !is_null($constraint); }); } else { unset($field_options['constraints']); } if (!empty($field['attr']) || !empty($field['attributes'])) { $field_options['attr'] = $field['attr'] ?? $field['attributes']; } if (isset($field_options['rounding_mode'])) { $field_options['rounding_mode'] = intval($field_options['rounding_mode']); } if (isset($field_options['html5'])) { $field_options['html5'] = \boolval($field_options['html5']); } if ($field['type'] == 'number') { $field_options['input'] = 'string'; $field_options['data'] = '0'; } if ($field['type'] == 'checkbox') { if (isset($field_options['checked'])) { if ($field_options['checked'] == "1") { $field_options['data'] = true; } unset($field_options['checked']); } } if ($field['type'] == 'choice') { if (isset($field_options['choices']) && is_array($field_options['choices'])) { array_walk($field_options['choices'], function (&$choice) { if ($choice['value'] == '') { $choice['value'] = \sanitize_text_field($choice['label']); } }); $selected = array_values(\array_filter($field_options['choices'], function ($choice) { return $choice['selected'] ?? false; })); if (count($selected) > 0) { $field_options['data'] = $selected[0]['value']; } $field_options['choices'] = \array_combine(\array_column($field_options['choices'], 'value'), \array_column($field_options['choices'], 'label')); } } if ($field['type'] == 'time') { $field_options['input'] = 'array'; // $field_options['html5'] = false; } if ($field['type'] == 'date' || $field['type'] == 'time' || $field['type'] == 'datetime' || $field['type'] == 'birthday') { if (!isset($field_options['widget']) || !$field_options['widget'] || $field_options['widget'] == 'single_text') { $field_options['input'] = 'string'; $field_options['widget'] = 'single_text'; } else { $field_options['input'] = 'array'; } } unset($field_options['name']); unset($field_options['type']); if (isset($field_options['field_type'])) { $field_options['type'] = $field_options['field_type']; unset($field_options['field_type']); } try { if ($field['type'] == 'repeater_start') { $builder->add($field['name'] . '-start', $this->get_field('repeater_start'), $field_options); $start_repeater = $field['name']; continue; } if ($start_repeater && $start_repeater != '') { if ($field['type'] == 'repeater_end') { $builder->add($start_repeater, $this->get_field('repeater_end'), $field_options + [ 'name' => $start_repeater, 'fields' => $repeater ]); $start_repeater = ''; $repeater = []; } else { $repeater[] = [ 'name' => $field['name'], 'type' => $field['type'], 'class' => $this->get_field($field['type']), 'options' => $field_options ]; } continue; } if ($field['type'] == 'file') { Helper::create_file_field($builder, $field, $field_options); continue; } $builder->add($field['name'], $this->get_field($field['type']), $field_options); if ($field['type'] == 'checkbox' || $field['type'] == 'radio') { $builder->get($field['name'])->addModelTransformer(new CallbackTransformer( function ($activeAsString) { return (bool)(int)$activeAsString; }, function ($activeAsBoolean) { return (string)(int)$activeAsBoolean; } )); } } catch (\Throwable $e) { \error_log($e->getMessage()); } } $builder->add('_message', $this->get_field('html'), [ 'html' => '
' ]); try { return $builder->getForm(); } catch (\Throwable $e) { // \var_dump($e); return new WP_Error('form_error', $e->getMessage()); } } /** * Render the form * * @date 2024-04-11 * * @param string $name * @param \Isolated\Symfony\Component\Form\Form $form * @param string $theme * * @return void */ public function render($name, $form, $theme = null) { $twig = new \Isolated\Twig\Environment(new \Isolated\Twig\Loader\FilesystemLoader(array( AJAXY_FORMS_PLUGIN_DIR . 'inc/themes', AJAXY_FORMS_PLUGIN_DIR . 'vendor/symfony/twig-bridge/Resources/views/Form', )), [ 'debug' => WP_DEBUG, 'cache' => false ]); // Set up the Translation component $translator = new Translator('en'); $translator->addLoader('xlf', new XliffFileLoader()); $translator->addResource('xlf', AJAXY_FORMS_PLUGIN_DIR . 'vendor/symfony/form/Resources/translations/validators.en.xlf', 'en', 'validators'); $translator->addResource('xlf', AJAXY_FORMS_PLUGIN_DIR . 'vendor/symfony/validator/Resources/translations/validators.en.xlf', 'en', 'validators'); $twig->addExtension(new TranslationExtension($translator)); $twig->addExtension( new FormExtension($translator) ); $twig->addExtension(new \Isolated\Twig\Extension\DebugExtension()); $theme = $theme ?? 'tailwind_2_layout.html.twig'; $twig->addRuntimeLoader(new \Isolated\Twig\RuntimeLoader\FactoryRuntimeLoader( array( FormRenderer::class => function () use ($twig, $theme) { return new FormRenderer(new TwigRendererEngine(array($theme), $twig)); } ) )); if ($this->on_submit($name, $form)) { $registered_form = Data::parse_form($name); $messages = array_replace([ 'success' => __('Form submitted successfully', "ajaxy-forms"), 'error' => __('Form failed to submit, Please try again', "ajaxy-forms"), ], $registered_form->get_option('messages', [])); return sprintf('
%s
', $messages['success'] ?? ''); } $output = $twig->render($twig->createTemplate('{{ form(form) }}'), ['form' => $form->createView()]); return $output; } /** * On Form Submit * * @date 2024-04-10 * * @param \Isolated\Symfony\Component\Form\FormInterface $form * * @return void */ public function on_submit($form_name, $form) { $errors = []; $form->handleRequest(); if ($form->isSubmitted() && $form->isValid()) { $data = $form->getData(); $data = Helper::handle_files($data); $registered_form = Data::parse_form($form_name); $actions = $registered_form->get_actions(); foreach ($actions as $action) { try { \call_user_func($action, $data, $registered_form); } catch (\Throwable $e) { $errors[] = $e->getMessage(); continue; } } if (count($errors) > 0) { throw new \Exception(implode(', ', $errors)); } $this->csrf_token_manager->refreshToken('form_intention_' . $registered_form->get_name()); return true; } return false; } public function action_links($links, $file) { if ($file != AJAXY_FORMS_BASENAME) { return $links; } if (!current_user_can('manage_options')) { return $links; } $settings_link = sprintf( '%s', menu_page_url('ajaxy-forms-settings', false), __('Settings', "ajaxy-forms") ); array_unshift($links, $settings_link); return $links; } public function filters() { // add_filter('plugin_action_links', [$this, 'action_links'], 10, 2); } public function actions() { add_action('rest_api_init', function () { register_rest_route("ajaxy-forms" . '/v1', '/form/(?P.+)', array( 'methods' => 'GET,POST', 'permission_callback' => '__return_true', 'callback' => [$this, 'submit'], )); register_rest_route("ajaxy-forms" . '/v1', '/form-data/', array( 'methods' => 'GET,POST', 'permission_callback' => '__return_true', 'callback' => [$this, 'get_data'], )); }); add_action('wp_enqueue_scripts', array(&$this, 'scripts')); } public function scripts() { wp_enqueue_style("ajaxy-forms" . "-style", AJAXY_FORMS_PLUGIN_URL . '/assets/css/styles.css', [], "1.0"); wp_enqueue_script("ajaxy-forms" . '-script', AJAXY_FORMS_PLUGIN_URL . '/assets/js/script.js', array(), 1.0, true); wp_localize_script("ajaxy-forms" . '-script', 'ajaxyFormsSettings', array( 'nonce' => wp_create_nonce('wp_rest'), 'dataUrl' => get_rest_url(null, sprintf('ajaxy-forms/v1/form-data/')) )); } public function get_field($type) { $data = preg_split('/(?=[A-Z])/', $type); $type = \strtolower(implode('_', $data)); $field = Inc\Fields::getInstance()->get($type); return $field['class']; } public function register($name, $fields, $options = [], $actions = [], $initial_data = null) { Data::register_form($name, $fields, $options, $actions, $initial_data); } /** * Register a form action * * @date 2024-04-10 * * @param string $form_name * @param string $action_name * @param array|callable $optionsOrCallable * * @return void */ public function register_action($form_name, $action_name, $optionsOrCallable) { $form = Data::parse_form($form_name); if (!$form) { throw new \Exception('Please register the form first before registering the action'); } $form->add_action($action_name, $optionsOrCallable); } public function register_action_type($type, $options) { Inc\Actions::getInstance()->register($type, $options); } public function get_data(\WP_REST_Request $request) { $type = $request->get_param('type') ?? null; switch ($type) { case 'posts_by_term': $term_id = $request->get_param('term_id') ?? null; $taxonomy = $request->get_param('taxonomy') ?? 'category'; $post_type = $request->get_param('post_type') ?? 'post'; $orderby = $request->get_param('orderby') ?? 'date'; $order = $request->get_param('order') ?? 'DESC'; $meta_key = $request->get_param('meta_key') ?? ''; $meta_value = $request->get_param('meta_value') ?? ''; $exclude = $request->get_param('exclude') ?? ''; $include = $request->get_param('include') ?? ''; $posts = get_posts(array( 'post_type' => $post_type, 'order' => $order, 'orderby' => $orderby, 'meta_key' => $meta_key, 'meta_value' => $meta_value, 'exclude' => array_filter(explode(',', $exclude)), 'include' => array_filter(explode(',', $include)), 'posts_status' => 'publish', 'posts_per_page' => -1, 'tax_query' => array( array( 'taxonomy' => $taxonomy, 'field' => 'id', 'terms' => $term_id, ), ), )); $response = array_map(function ($post) { return array( 'id' => $post->ID, 'title' => $post->post_title ); }, $posts); return new \WP_REST_Response([ 'data' => $response, 'status' => 'success' ]); default: return new \WP_REST_Response(['status' => 'error', 'message' => 'Unsupported data type']); } } public function submit(\WP_REST_Request $request) { $form_name = $request->get_param('name') ?? null; if (!$form_name || trim($form_name) == "") { return new \WP_REST_Response(['status' => 'error', 'message' => 'Form not found']); } $form = Data::parse_form($form_name); if (!$form) { return new \WP_REST_Response(['status' => 'error', 'message' => 'Form not found']); } $messages = array_replace([ 'success' => __('Form submitted successfully', "ajaxy-forms"), 'error' => __('Form failed to submit, Please try again', "ajaxy-forms"), 'wp_error' => __('Error submitting form: %s', "ajaxy-forms"), ], $form->get_option('messages', [])); $data = $request->get_body_params()[$form_name] ?? []; $submitted_form = $this->create_form($form, $data); if (\is_wp_error($submitted_form)) { return new \WP_REST_Response([ 'status' => 'error', 'message' => \sprintf($messages['wp_error'], $submitted_form->get_error_message()), '_token' => $this->csrf_token_manager->getToken('form_intention_' . $form->get_name())->getValue() ]); } $valid = false; try { $valid = $this->on_submit($form_name, $submitted_form); } catch (\Exception $e) { return new \WP_REST_Response([ 'status' => 'error', 'message' => \sprintf($messages['wp_error'], $e->getMessage()), '_token' => $this->csrf_token_manager->getToken('form_intention_' . $form->get_name())->getValue() ]); } if ($valid) { $message = $form->get_message('success'); return new \WP_REST_Response([ 'status' => 'success', 'message' => $message ? $message : $messages['success'], '_token' => $this->csrf_token_manager->getToken('form_intention_' . $form->get_name())->getValue() ]); } $message = $form->get_message('error'); $errors = $this->get_form_error_messages($submitted_form); if (count($errors) > 0) { $message .= implode('
', $errors); } return new \WP_REST_Response([ 'status' => 'error', 'message' => $message ? $message : $messages['error'], 'fields' => $this->get_fields_error_messages($submitted_form) ]); } private function get_fields_error_messages(FormInterface $form) { $errors = array(); if ($form->count() && $form->isSubmitted()) { /** @var \Isolated\Symfony\Component\Form\Form */ foreach ($form as $child) { if (!$child->isValid()) { $children = $child->all(); $subError = false; if (\count($children) > 0) { foreach ($children as $sub_child) { if (!$sub_child->isValid()) { $errors[$child->getName()][$sub_child->getName()] = $this->get_form_error_messages($sub_child); $subError = true; } } } if (!$subError) { $errors[$child->getName()] = implode(', ', $this->get_form_error_messages($child)); } } } } return $errors; } private function get_form_error_messages(FormInterface $form) { $errors = array(); foreach ($form->getErrors() as $key => $error) { $template = $error->getMessageTemplate(); $parameters = $error->getMessageParameters(); foreach ($parameters as $var => $value) { $template = str_replace($var, $value, $template); } $errors[] = $template; } return $errors; } public function register_field($type, $options) { Inc\Fields::getInstance()->register($type, $options); } } register_activation_hook(__FILE__, function () { Inc\Data::install(); }); \Ajaxy\Forms\Plugin::init(); \register_form_field('html', [ "label" => "HTML", "class" => Inc\Types\HtmlType::class, "docs" => false, "disable_constraints" => true, "inherited" => [], "properties" => [[ "section" => "basic", "order" => 1, "label" => "Label", "type" => "text", "name" => "label", "help" => "Enter the label for the field, keep empty to hide it", ], [ "section" => "basic", "order" => 1, "label" => "Html", "type" => "textarea", "name" => "html", "help" => "Enter the HTML to display in the form", ]], "order" => 50, "keywords" => "html,custom", "common" => false ]); \register_form_action_type('storage', [ "label" => "Storage", "class" => Inc\Actions\Storage::class, "docs" => false, "properties" => Inc\Actions\Storage::get_properties() ]); \register_form_action_type('email', [ "label" => "Email", "class" => Inc\Actions\Email::class, "docs" => false, "properties" => Inc\Actions\Email::get_properties() ]); \register_form_action_type('sms', [ "label" => "SMS", "class" => Inc\Actions\Sms::class, "docs" => false, "properties" => Inc\Actions\Sms::get_properties() ]); \register_form_action_type('whatsapp', [ "label" => "Whatsapp", "class" => Inc\Actions\Whatsapp::class, "docs" => false, "properties" => Inc\Actions\Whatsapp::get_properties() ]); \register_form_action_type('webhook', [ "label" => "Webhook", "class" => Inc\Actions\Webhook::class, "docs" => false, "properties" => Inc\Actions\Webhook::get_properties() ]); \register_form_action_type('email_responder', [ "label" => "Auto Responder Email", "class" => Inc\Actions\Email::class, "docs" => false, "properties" => Inc\Actions\Email::get_properties() ]);