<?php

class WP_Form_front
{
    private $tags = [];
    private $actions = [];
    private $messages = [];
    private $settings;
    private $defaultSettings;
    private $spamProtection;
    private $form_id = null;
    private $allowedUploadExtensionsDefault = [
        'jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'txt', 'csv',
    ];

    public function __construct($settings)
    {
        $this->settings = $settings;
        $this->defaultSettings = $this->settings->loadDefaultSettings();
        $this->spamProtection = new WP_Form_Spam_Protection([$this, 'getIP']);
    }

    public function init()
    {
        $actions = apply_filters('madeit_forms_actions', []);
        foreach ($actions as $id => $value) {
            $this->addAction($id, $value);
        }

        $modules = apply_filters('madeit_forms_modules', []);
        foreach ($modules as $id => $value) {
            $this->addModule($id, $value);
        }

        if (!is_admin()) {
            wp_register_style('madeit-form-style', MADEIT_FORM_URL.'front/css/style.css', [], null);
            wp_enqueue_style('madeit-form-style');
            if (isset($this->defaultSettings['reCaptcha']['enabled']) && $this->defaultSettings['reCaptcha']['enabled']) {
                wp_enqueue_script('recaptcha', 'https://www.google.com/recaptcha/api.js', [], null, true);
            }
            wp_enqueue_script('madeit-form-script', MADEIT_FORM_URL.'front/js/script.js', ['jquery'], null, true);
        }
        $this->shortCodes();
    }

    public function shortCodes()
    {
        add_shortcode('form', [$this, 'shortcode_form']);
    }

    public function shortcode_form($attsOrig)
    {
        $atts = shortcode_atts(['id' => 0, 'ajax' => 'no', 'extra_id' => null, 'spam_action' => 'fail'], $attsOrig);
        extract($atts);

        $ajax = strtolower($ajax) == 'yes';

        $forms = get_posts([
            'post_type'   => 'ma_forms',
            'status'      => 'published',
            'numberposts' => -1,
            'meta_query'  => [
                [
                    'key'     => 'form_id',
                    'compare' => '=',
                    'value'   => ''.$id,
                ],
            ],
        ]);

        $translatedForm = null;
        if (count($forms) === 1) {
            $form = $forms[0];
            $translatedForm = $form;
        } else {
            $form = get_post($id);

            $objectId = apply_filters('wpml_object_id', $id, 'ma_forms');
            if (!empty($objectId) && $objectId !== $id) {
                //Translation found
                $translatedForm = get_post($objectId);
                if ($translatedForm == null) {
                    $translatedForm = $form;
                }
            } else {
                $translatedForm = $form;
            }
        }

        if ($form !== null && $form->post_type !== 'ma_forms') {
            return __("Can't display the form.", 'forms-by-made-it');
        }
        $this->form_id = $form->ID;

        ob_start();

        if (isset($_POST['form_id']) && $_POST['form_id'] == $form->ID) {
            //check spam
            $spam = false;

            //validate input fields
            $error = false;
            $error_msg = '';
            $messages = json_decode(str_replace("\'", "'", $this->dbToEnter(get_post_meta($form->ID, 'messages', true))), true);

            // Capability gating via filter (nonce intentionally omitted per caching concerns)
            $canSubmit = apply_filters('madeit_forms_can_submit', true, $form->ID, $form, $_POST);
            if (!$canSubmit) {
                $error = true;
                if (empty($error_msg)) {
                    $error_msg = __('Submission blocked.', 'forms-by-made-it');
                }
            }

            //insert form input
            $tags = [];
            $uploadableFields = [];
            if (get_post_meta($form->ID, 'form_type', true) === 'html') {
                $formValue = get_post_meta($form->ID, 'form', true);
                $formValue = str_replace('\"', '"', $formValue);
                $formValue = str_replace("\'", "'", $formValue);
                foreach ($_POST as $k => $v) {
                    $tag = $this->getTagNameFromPostInput($formValue, $k);
                    if ($tag !== false) {
                        if (is_callable($this->tags[$tag]['validation'])) {
                            $tagOptions = $this->getOptionsFromTag($formValue, $tag, $k);
                            $result = call_user_func($this->tags[$tag]['validation'], $tagOptions, $v, $messages);
                            if ($result !== true) {
                                $error = true;
                                $error_msg = $result;
                            }
                        }
                    }
                }
            } else {
                $blocks = parse_blocks($form->post_content);
                $blocks = $this->parseBlocks($blocks);

                foreach ($blocks as $block) {
                    if (isset($block['attrs']['name'])) {
                        $tag = $block['attrs']['name'];
                        $tags[] = $tag;
                        $type = $block['attrs']['type'] ?? 'text';

                        $label = $block['attrs']['label'] ?? ($block['attrs']['placeholder'] ?? $tag);

                        if (isset($block['attrs']['required']) && $block['attrs']['required']) {
                            if ($block['blockName'] === 'madeitforms/upload-field') {
                                if (!isset($_FILES[$tag]) || empty($_FILES[$tag])) {
                                    $error = true;
                                    $error_msg = $messages['invalid_required'].' ('.$label.')';
                                }
                            } elseif (!isset($_POST[$tag]) || empty($_POST[$tag])) {
                                $error = true;
                                $error_msg = $messages['invalid_required'].' ('.$label.')';
                            }
                        }

                        if (!empty($_POST[$tag]) && $type === 'email') {
                            if (!filter_var($_POST[$tag], FILTER_VALIDATE_EMAIL)) {
                                $error = true;
                                $error_msg = isset($messages['mod_text_invalid_email']) ? $messages['mod_text_invalid_email'] : $messages['validation_error'];
                                $error_msg .= ' ('.$label.')';
                            }
                        }

                        if (!empty($_POST[$tag]) && $type === 'url') {
                            if (!filter_var($_POST[$tag], FILTER_VALIDATE_URL)) {
                                $error = true;
                                $error_msg = isset($messages['mod_text_invalid_url']) ? $messages['mod_text_invalid_url'] : $messages['validation_error'];
                                $error_msg .= ' ('.$label.')';
                            }
                        }

                        if (!empty($_POST[$tag]) && $type === 'tel') {
                            if (!preg_match('%^[+]?[0-9()/ -]*$%', $_POST[$tag])) {
                                $error = true;
                                $error_msg = isset($messages['mod_text_invalid_phone']) ? $messages['mod_text_invalid_phone'] : $messages['validation_error'];
                                $error_msg .= ' ('.$label.')';
                            }
                        }

                        if (!empty($_POST[$tag]) && $type === 'number') {
                            if (!is_numeric($_POST[$tag])) {
                                $error = true;
                                $error_msg = isset($messages['mod_number_invalid_number']) ? $messages['mod_number_invalid_number'] : $messages['validation_error'];
                                $error_msg .= ' ('.$label.')';
                            }

                            if (isset($block['attrs']['minimum']) && $_POST[$tag] < $block['attrs']['minimum']) {
                                $error = true;
                                $error_msg = isset($messages['mod_number_number_too_small']) ? $messages['mod_number_number_too_small'] : $messages['validation_error'];
                                $error_msg .= ' ('.$label.')';
                            }

                            if (isset($block['attrs']['maximum']) && $_POST[$tag] > $block['attrs']['maximum']) {
                                $error = true;
                                $error_msg = isset($messages['mod_number_number_too_large']) ? $messages['mod_number_number_too_large'] : $messages['validation_error'];
                                $error_msg .= ' ('.$label.')';
                            }
                        }

                        if ($block['blockName'] === 'madeitforms/upload-field') {
                            $uploadableFields[$block['attrs']['name']] = $block['attrs'];
                        }
                    }
                }
            }

            if (isset($this->defaultSettings['reCaptcha']['enabled']) && $this->defaultSettings['reCaptcha']['enabled']) {
                $secretKey = $this->defaultSettings['reCaptcha']['secret'];
                if (!isset($_POST['g-recaptcha-response']) || empty($_POST['g-recaptcha-response'])) {
                    $error = true;
                    $error_msg = __("The captcha couldn't validate you.", 'forms-by-made-it');
                }
            }

            if ($this->isSpam($_POST)) {
                $spam = true;
                if ($spam_action === 'fail') {
                    $error = true;
                    $error_msg = __('Spam detected.', 'forms-by-made-it');
                }
            }

            $spamScore = null;
            if (isset($this->defaultSettings['reCaptcha']['enabled']) && $this->defaultSettings['reCaptcha']['enabled']) {
                $secretKey = $this->defaultSettings['reCaptcha']['secret'];
                if (!isset($_POST['g-recaptcha-response'])) {
                    $error = true;
                    $error_msg = isset($messages['check_captcha']) ? $messages['check_captcha'] : __("The captcha couldn't validate you.", 'forms-by-made-it');
                }
                $response = $_POST['g-recaptcha-response'];
                $remoteIp = $_SERVER['REMOTE_ADDR'];
                $reCaptchaValidationUrl = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=$secretKey&response=$response&remoteip=$remoteIp");
                $result = json_decode($reCaptchaValidationUrl, true);

                if ($this->defaultSettings['reCaptcha']['version'] === 'V3') {
                    $spamScore = $result['score'] ?? 0;
                    if ($result['score'] < $this->defaultSettings['reCaptcha']['minScore']) {
                        $error = true;
                        $error_msg = isset($messages['check_captcha']) ? $messages['check_captcha'] : __("The captcha couldn't validate you.", 'forms-by-made-it');
                    }
                } else {
                    if ($result['success'] != 1) {
                        $error = true;
                        $error_msg = isset($messages['check_captcha']) ? $messages['check_captcha'] : __("The captcha couldn't validate you.", 'forms-by-made-it');
                    }
                }
            }

            if (get_post_meta($form->ID, 'max_submits', true) == 1 && $this->hasAlreadyCompletedThisForm($form->ID)) {
                $error = true;
                $error_msg = isset($messages['already_submitted']) ? $messages['already_submitted'] : __('You have already submitted this form.', 'forms-by-made-it');
            }

            /* Try uploading files */
            if (isset($_FILES) && count($_FILES) > 0 && count($uploadableFields) > 0) {
                foreach ($_FILES as $k => $v) {
                    if (!isset($uploadableFields[$k])) {
                        continue;
                    }

                    //upload file and give URL
                    $file = $_FILES[$k];

                    // If field not required and no file provided, skip further validation
                    $isRequired = !empty($uploadableFields[$k]['required']);
                    $noFileProvided = (empty($file['name']) || (isset($file['error']) && (int) $file['error'] === UPLOAD_ERR_NO_FILE) || (isset($file['size']) && (int) $file['size'] === 0));
                    if (!$isRequired && $noFileProvided) {
                        continue;
                    }

                    //check if file is not too big
                    $phpMaxUploadSize = $this->getMaximumFileUploadSize();
                    if ($file['size'] > $phpMaxUploadSize) {
                        $error = true;
                        $error_msg = isset($messages['file_too_big']) ? $messages['file_too_big'] : __('The file is too big.', 'forms-by-made-it');
                        $error_msg .= ' ('.$uploadableFields[$k]['label'].')';
                    }

                    // Validate upload error status
                    if (isset($file['error']) && $file['error'] !== UPLOAD_ERR_OK) {
                        // If no file provided for required field, report; otherwise skip
                        if ($isRequired && (int) $file['error'] === UPLOAD_ERR_NO_FILE) {
                            $error = true;
                            $error_msg = isset($messages['invalid_required']) ? $messages['invalid_required'] : __('This field is required.', 'forms-by-made-it');
                            $error_msg .= ' ('.$uploadableFields[$k]['label'].')';
                        } elseif ((int) $file['error'] !== UPLOAD_ERR_NO_FILE) {
                            $error = true;
                            $error_msg = isset($messages['file_upload_error']) ? $messages['file_upload_error'] : __('Error uploading file.', 'forms-by-made-it');
                            $error_msg .= ' ('.$uploadableFields[$k]['label'].')';
                        } else {
                            // Not required and no file provided: skip
                            continue;
                        }
                    }

                    // MIME-first validation based on Gutenberg field attribute `filetype`
                    $allowedMimes = $this->getAllowedMimesForField($uploadableFields[$k]);
                    $check = wp_check_filetype_and_ext($file['tmp_name'], $file['name']);
                    $mime = isset($check['type']) ? strtolower($check['type']) : '';
                    $extLower = isset($check['ext']) ? strtolower($check['ext']) : '';

                    // Disallow dangerous extensions explicitly
                    $disallowed = apply_filters('madeit_forms_disallowed_upload_extensions', ['php', 'phtml', 'php3', 'php4', 'php5', 'php7', 'php8', 'phps', 'phar', 'cgi', 'pl', 'exe', 'sh', 'bash']);
                    if (!empty($extLower) && in_array($extLower, $disallowed, true)) {
                        $error = true;
                        $error_msg = isset($messages['file_type_not_allowed']) ? $messages['file_type_not_allowed'] : __('Disallowed file type.', 'forms-by-made-it');
                        $error_msg .= ' ('.$uploadableFields[$k]['label'].')';
                    }

                    // If Gutenberg field defines MIME(s), require a match
                    if (empty($error_msg) && !empty($allowedMimes)) {
                        if (empty($mime) || !$this->mimeMatchesAllowed($mime, $allowedMimes)) {
                            $error = true;
                            $error_msg = isset($messages['file_type_not_allowed']) ? $messages['file_type_not_allowed'] : __('Invalid or disallowed file type.', 'forms-by-made-it');
                            $error_msg .= ' ('.$uploadableFields[$k]['label'].')';
                        }
                    } else {
                        // Fallback: require WordPress to recognize the file
                        if (empty($check['ext']) || empty($check['type'])) {
                            $error = true;
                            if (empty($error_msg)) {
                                $error_msg = isset($messages['file_type_not_allowed']) ? $messages['file_type_not_allowed'] : __('Invalid or disallowed file type.', 'forms-by-made-it');
                                $error_msg .= ' ('.$uploadableFields[$k]['label'].')';
                            }
                        }
                    }
                }
            }

            $uploadedFiles = [];
            if (!$error && isset($_FILES) && count($_FILES) > 0 && count($uploadableFields) > 0) {
                /* Process file upload */
                $uploadDir = wp_upload_dir();
                $uploadDir = $uploadDir['basedir'].'/madeit-forms/'.$form->ID.'/';
                if (!file_exists($uploadDir)) {
                    mkdir($uploadDir, 0755, true);
                }
                $this->ensureUploadsProtection($uploadDir);

                foreach ($_FILES as $k => $v) {
                    //upload file and give URL
                    $file = $_FILES[$k];

                    error_log('File Upload: '.$file['name']);
                    error_log('Upload size: '.$file['size']);

                    if (empty($file['name']) || $file['size'] <= 0) {
                        continue;
                    }

                    // Determine safe mimetype and extension via WordPress check
                    $check = wp_check_filetype_and_ext($file['tmp_name'], $file['name']);
                    $mime = isset($check['type']) ? strtolower($check['type']) : '';
                    $ext = isset($check['ext']) ? strtolower($check['ext']) : '';
                    $allowedMimes = $this->getAllowedMimesForField($uploadableFields[$k]);

                    // Disallow dangerous extensions explicitly
                    $disallowed = apply_filters('madeit_forms_disallowed_upload_extensions', ['php', 'phtml', 'php3', 'php4', 'php5', 'php7', 'php8', 'phps', 'phar', 'cgi', 'pl', 'exe', 'sh', 'bash']);
                    if (!empty($ext) && in_array($ext, $disallowed, true)) {
                        $error = true;
                        $error_msg = isset($messages['file_type_not_allowed']) ? $messages['file_type_not_allowed'] : __('Disallowed file type.', 'forms-by-made-it');
                        $error_msg .= ' ('.$uploadableFields[$k]['label'].')';
                    }

                    if (empty($error_msg) && !empty($allowedMimes)) {
                        if (empty($mime) || !$this->mimeMatchesAllowed($mime, $allowedMimes)) {
                            $error = true;
                            $error_msg = isset($messages['file_type_not_allowed']) ? $messages['file_type_not_allowed'] : __('Invalid or disallowed file type.', 'forms-by-made-it');
                            $error_msg .= ' ('.$uploadableFields[$k]['label'].')';
                        }
                    } else {
                        if (empty($check['ext']) || empty($check['type'])) {
                            $error = true;
                            if (empty($error_msg)) {
                                $error_msg = isset($messages['file_type_not_allowed']) ? $messages['file_type_not_allowed'] : __('Invalid or disallowed file type.', 'forms-by-made-it');
                                $error_msg .= ' ('.$uploadableFields[$k]['label'].')';
                            }
                        }
                    }

                    // If no extension from WP, try to infer from MIME; fallback to .bin
                    if (empty($ext) && !empty($mime)) {
                        $ext = $this->extensionFromMime($mime);
                    }
                    if (empty($ext)) {
                        $ext = 'bin';
                    }

                    //generate random unique filename, keeping only validated extension
                    $filename = wp_unique_filename($uploadDir, md5(uniqid((string) random_int(1000, 9999), true)).'.'.$ext);

                    //move file to upload dir
                    $result = @move_uploaded_file($file['tmp_name'], $uploadDir.$filename);
                    if ($result === false) {
                        $error = true;
                        $error_msg = isset($messages['file_upload_error']) ? $messages['file_upload_error'] : __('Error uploading file.', 'forms-by-made-it');
                        $error_msg .= ' ('.$uploadableFields[$k]['label'].')';
                    }

                    $url = home_url().'/wp-content/uploads/madeit-forms/'.$form->ID.'/'.$filename;

                    $uploadedFiles[$k] = [
                        'url'      => $url,
                        'location' => $uploadDir.$filename,
                        'filename' => $filename,
                        'name'     => $file['name'],
                    ];
                }

                if ($error) {
                    //remove uploaded files
                    foreach ($uploadedFiles as $file) {
                        unlink($file['location']);
                    }
                }
            }

            $error_msg = apply_filters('madeit_forms_submit_error', $error_msg, $form->ID);
            if (!empty($error_msg)) {
                $error = true;
            }

            if ($error) {
                $this->notifyError($error_msg);

                echo '<div class="madeit-form-error">'.$error_msg.'</div>';
                $this->renderForm($form->ID, $form, $translatedForm, $ajax, $extra_id, $attsOrig);
                $content = ob_get_clean();

                return $content;
            }

            //set cookie
            $submittedTimes = isset($_COOKIE['madeit_form_'.$form->ID.'_submitted']) ? $_COOKIE['madeit_form_'.$form->ID.'_submitted'] : 0;
            setcookie('madeit_form_'.$form->ID.'_submitted', $submittedTimes, time() + 31556926);

            //insert into DB
            $postData = $_POST;
            if ($spamScore !== null) {
                $postData['spamScore'] = $spamScore;
            }
            unset($attsOrig['ajax']);
            unset($attsOrig['id']);
            $postData = array_merge($attsOrig, $postData);
            unset($postData['form_id']);
            unset($postData['madeit_form_rendered_at']);
            unset($postData['madeit_website']);
            foreach ($postData as $k => $v) {
                if (!in_array($k, $tags)) {
                    $postData[] = null;
                }
            }

            $inputId = -1;
            if (get_post_meta($form->ID, 'save_inputs', true) == 1) {
                $inputId = wp_insert_post([
                    'post_title'  => 'Form submit '.$form->post_title.' - '.$this->getIP(),
                    'post_status' => 'publish',
                    'post_type'   => 'ma_form_inputs',
                ]);

                $postData['input_id'] = $inputId;

                /* Process file upload */
                if (count($uploadedFiles) > 0) {
                    $uploadDir = wp_upload_dir();
                    $uploadDir = $uploadDir['basedir'].'/madeit-forms/'.$form->ID.'/'.$inputId.'/';
                    if (!file_exists($uploadDir)) {
                        mkdir($uploadDir, 0755, true);
                    }
                    $this->ensureUploadsProtection($uploadDir);

                    foreach ($uploadedFiles as $k => $v) {
                        //move file to new path
                        $url = home_url().'/wp-content/uploads/madeit-forms/'.$form->ID.'/'.$inputId.'/'.$v['filename'];

                        $result = rename($v['location'], $uploadDir.$v['filename']);
                        if ($result === false) {
                            $url = $v['url'];
                        } else {
                            unlink($v['location']);
                        }

                        $postData[$k] = $url;
                    }
                }
                /* End process file upload */

                $postData = apply_filters('madeit_forms_post_data', $postData, $form->ID, $inputId);
                $postData = apply_filters('madeit_forms_'.$form->ID.'_post_data', $postData, $inputId);

                update_post_meta($inputId, 'form_id', $form->ID);
                update_post_meta($inputId, 'data', $this->enterToDB(json_encode($postData)));
                update_post_meta($inputId, 'ip', $this->getIP());
                update_post_meta($inputId, 'user_agent', isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'UNKNOWN');
                update_post_meta($inputId, 'spam', $spam ? 1 : 0);
                update_post_meta($inputId, 'read', 0);
                update_post_meta($inputId, 'result', '');
            }

            //execute actions
            $actions = json_decode(str_replace("\'", "'", $this->dbToEnter(get_post_meta($form->ID, 'actions', true))), true);
            if (is_array($actions) && count($actions) > 0) {
                $formActions = apply_filters('madeit_forms_submit_actions', $actions);
                foreach ($formActions as $actID => $actionInfo) {
                    $action = $this->actions[$actionInfo['_id']];

                    unset($attsOrig['ajax']);
                    unset($attsOrig['id']);
                    $data = array_merge($attsOrig, [
                        'id' => $inputId,
                    ]);
                    foreach ($action['action_fields'] as $name => $info) {
                        $inputValue = isset($actionInfo[$name]) ? $actionInfo[$name] : $info['value'];
                        $data[$name] = $this->changeInputTag($inputValue, $postData);
                    }

                    if (is_callable($action['callback'])) {
                        $data = apply_filters('madeit_forms_action_data', $data, $form->ID, $inputId, $actionInfo, $postData);
                        $result = call_user_func($action['callback'], $data, $messages, $actionInfo, $form->ID, $inputId, $postData);
                        if (is_array($result) && isset($result['type'])) {
                            if ($result['type'] == 'JS') {
                                echo '<script>'.$result['code'].'</script>';
                            } elseif ($result['type'] == 'HTML') {
                                echo str_replace('\"', '"', $result['code']);
                            }
                        } elseif ($result !== true) {
                            $error = true;
                            $error_msg = $result;
                        }
                    }
                }
            }

            $error_msg = apply_filters('madeit_forms_submit_error', $error_msg, $form->ID);
            if (!empty($error_msg)) {
                $error = true;
            }

            if ($error) {
                $this->notifyError($error_msg);
                echo '<div class="madeit-form-error">'.$error_msg.'</div>';
                $this->renderForm($id, $form, $translatedForm, $ajax, $extra_id, $attsOrig);
            } else {
                echo '<div class="madeit-form-success">'.$messages['success'].'</div>';
            }
            //return success message
        } else {
            $this->renderForm($form->ID, $form, $translatedForm, $ajax, $extra_id, $attsOrig);
        }

        $content = ob_get_clean();

        return $content;
    }

    private function parseBlocks($blocks)
    {
        $return = [];

        foreach ($blocks as $block) {
            if (isset($block['attrs']['name'])) {
                $return[] = $block;
            }

            if (isset($block['innerBlocks']) && count($block['innerBlocks']) > 0) {
                $return = array_merge($return, $this->parseInnerBlocks($block['innerBlocks']));
            }
        }

        return $return;
    }

    private function parseInnerBlocks($blocks)
    {
        $return = [];

        foreach ($blocks as $block) {
            if (isset($block['attrs']['name'])) {
                $return[] = $block;
            }

            if (isset($block['innerBlocks']) && count($block['innerBlocks']) > 0) {
                $return = array_merge($return, $this->parseBlocks($block['innerBlocks']));
            }
        }

        return $return;
    }

    private function renderForm($id, $form, $translatedForm, $ajax = false, $extra_id = null, $atts = [])
    {
        if ($form === null) {
            return;
        }

        if ($form->post_status !== 'publish') {
            echo __('This form is not available.', 'forms-by-made-it');

            return;
        }

        ob_start();

        $this->form_id = $id;
        add_filter('madeit_forms_form_id', [$this, 'form_id']);
        $formHtmlId = 'form_'.$id;
        if ($extra_id) {
            $formHtmlId .= '_'.$extra_id;
        }
        echo '<form action="" method="post" id="'.$formHtmlId.'" ';

        if (strpos($translatedForm->post_content, 'type="file"') !== false) {
            echo 'enctype="multipart/form-data" class="madeit-forms-noajax"';
        } elseif ($ajax) {
            echo 'class="madeit-forms-ajax"';
        } else {
            echo 'class="madeit-forms-noajax"';
        }

        echo '>';

        echo '<input type="hidden" name="form_id" value="'.$id.'">';
        echo '<input type="hidden" name="madeit_form_rendered_at" value="'.time().'">';
        echo '<input type="text" name="madeit_website" value="" class="madeit-forms-honeypot" tabindex="-1" autocomplete="off" aria-hidden="true">';
        if (get_post_meta($form->ID, 'form_type', true) === 'html') {
            $formValue = get_post_meta($form->ID, 'form', true);
            $formValue = str_replace('\"', '"', $formValue);
            $formValue = str_replace("\'", "'", $formValue);
            echo do_shortcode($formValue);
        } else {
            $content = apply_filters('the_content', $translatedForm->post_content);

            $blocks = parse_blocks($form->post_content);
            $blocks = $this->parseBlocks($blocks);

            foreach ($blocks as $block) {
                if (isset($block['attrs']['name']) && $block['blockName'] === 'madeitforms/input-field') {
                    $value = $atts[$block['attrs']['name']] ?? '';
                    if (isset($_GET[$block['attrs']['name']])) {
                        $value = $_GET[$block['attrs']['name']];
                    }
                    if (isset($_POST[$block['attrs']['name']])) {
                        $value = $_POST[$block['attrs']['name']];
                    }
                    $content = str_replace('name="'.$block['attrs']['name'].'"', 'name="'.$block['attrs']['name'].'" value="'.$value.'"', $content);
                } elseif (isset($block['attrs']['name']) && $block['blockName'] === 'madeitforms/largeinput-field') {
                    $value = $atts[$block['attrs']['name']] ?? '';
                    if (isset($_GET[$block['attrs']['name']])) {
                        $value = $_GET[$block['attrs']['name']];
                    }
                    if (isset($_POST[$block['attrs']['name']])) {
                        $value = $_POST[$block['attrs']['name']];
                    }
                    $content = str_replace('name="'.$block['attrs']['name'].'" required placeholder="'.($block['attrs']['placeholder'] ?? '').'">', 'name="'.$block['attrs']['name'].'" required placeholder="'.($block['attrs']['placeholder'] ?? '').'">'.$value, $content);
                    $content = str_replace('name="'.$block['attrs']['name'].'" placeholder="'.($block['attrs']['placeholder'] ?? '').'">', 'name="'.$block['attrs']['name'].'" placeholder="'.($block['attrs']['placeholder'] ?? '').'">'.$value, $content);

                    $content = str_replace('name="'.$block['attrs']['name'].'" required>', 'name="'.$block['attrs']['name'].'" required placeholder="'.($block['attrs']['placeholder'] ?? '').'">'.$value, $content);
                    $content = str_replace('name="'.$block['attrs']['name'].'">', 'name="'.$block['attrs']['name'].'" placeholder="'.($block['attrs']['placeholder'] ?? '').'">'.$value, $content);
                } elseif (isset($block['attrs']['name']) && $block['blockName'] === 'madeitforms/multi-value-field') {
                    $selectedVal = $atts[$block['attrs']['name']] ?? '';
                    if (isset($_GET[$block['attrs']['name']])) {
                        $selectedVal = $_GET[$block['attrs']['name']];
                    }
                    if (isset($_POST[$block['attrs']['name']])) {
                        $selectedVal = $_POST[$block['attrs']['name']];
                    }
                    if (!is_array($selectedVal) && !empty($selectedVal)) {
                        $selectedVal = [$selectedVal];
                    }
                    foreach (explode("\n", $block['attrs']['values']) as $value) {
                        $content = str_replace('name="'.$block['attrs']['name'].'[]" value="'.$value.'"', 'name="'.$block['attrs']['name'].'[]" value="'.$value.'"'.(is_array($selectedVal) && in_array($value, $selectedVal) ? ' checked' : ''), $content);
                    }
                }
            }

            if (isset($this->defaultSettings['reCaptcha']['enabled']) && $this->defaultSettings['reCaptcha']['enabled']) {
                $captchaCallback = 'onSubmit'.rand();
                $captchaErrorCallback = 'onErrorSubmit'.rand();

                $captcha = ' data-sitekey="'.$this->defaultSettings['reCaptcha']['key'].'" data-callback="'.$captchaCallback.'" data-error-callback="'.$captchaErrorCallback.'"';
                $formId = 'form_'.$this->form_id();
                $captcha_js = '<script>function '.$captchaCallback."(token) { submitMadeitForm('".$formId."'); }</script>";
                $captcha_js .= '<script>function '.$captchaErrorCallback.'(token) { }</script>';

                $content = str_replace('<button class="', '<button class="g-recaptcha ', $content);
                $content = str_replace('<button ', '<button '.$captcha, $content);
                $content .= $captcha_js;
            }

            $content = $this->checkQuiz($content);

            echo $content;
        }
        echo '</form>';
        $formHtml = ob_get_clean();

        echo apply_filters('madeit_forms_form_html', $formHtml, $id);
    }

    public function form_id()
    {
        return $this->form_id;
    }

    private function changeInputTag($value, $params = [])
    {
        if (count($params) === 0) {
            $params = $_POST;
        }
        foreach ($params as $k => $v) {
            if (is_array($v)) {
                $v = implode(', ', $v);
            }
            $value = str_replace('['.$k.']', $v, $value);
        }

        return $value;
    }

    private function getTagNameFromPostInput($form, $inputKey)
    {
        if ($inputKey == 'btn_submit' || $inputKey == 'g-recaptcha-response') {
            return 'submit';
        }
        $pos = strpos($form, 'name="'.$inputKey.'"');
        if ($pos !== false) {
            $tags = explode('[', substr($form, 0, $pos));
            if (count($tags) > 0) {
                $spaces = explode(' ', $tags[count($tags) - 1]);
                if (isset($spaces[0])) {
                    return $spaces[0];
                }
            }
        }

        return false;
    }

    private function getOptionsFromTag($form, $tag, $name)
    {
        preg_match_all('/\['.$tag.'.*name="'.$name.'".*\]/', $form, $result);
        if (isset($result[0][0])) {
            $partWithTag = $result[0][0];

            $key = '';
            $data = [];
            foreach (explode('="', $partWithTag) as $o) {
                if ($key == '') {
                    $space = explode(' ', $o);
                    if (count($space) <= 1) {
                        $key = $space[0];
                    } else {
                        $key = $space[count($space) - 1];
                    }
                } else {
                    $data[$key] = substr($o, 0, strpos($o, '"'));
                    $key = trim(substr($o, strpos($o, '"') + 1));
                }
            }

            return $data;
        }

        return false;
    }

    public function addAction($id, $value)
    {
        $this->actions[$id] = $value;
        if (count($value['message_fields']) > 0) {
            $this->messages = array_merge($this->messages, $value['message_fields']);
        }
    }

    public function addModule($id, $value)
    {
        $this->tags[$id] = $value;
        if (count($value['message_fields']) > 0) {
            $this->messages = array_merge($this->messages, $value['message_fields']);
        }
    }

    public function getIP()
    {
        foreach (['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR'] as $key) {
            if (array_key_exists($key, $_SERVER) === true) {
                foreach (array_map('trim', explode(',', $_SERVER[$key])) as $ip) {
                    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
                        return $ip;
                    }
                }
            }
        }

        return 'UNKNOWN';
    }

    public function submitAjaxForm()
    {
        $id = $_POST['form_id'];

        $form = get_post($id);

        if ($form->post_type !== 'ma_forms') {
            echo json_encode(['success' => false, 'message' => __("Can't display the form.", 'forms-by-made-it')]);
            wp_die();
        }
        $this->form_id = $form->ID;

        // Capability gating via filter (nonce intentionally omitted per caching concerns)
        $canSubmit = apply_filters('madeit_forms_can_submit', true, $form->ID, $form, $_POST);
        if (!$canSubmit) {
            echo json_encode(['success' => false, 'message' => __('Submission blocked.', 'forms-by-made-it')]);
            wp_die();
        }

        if ($this->isSpam($_POST)) {
            $this->notifyError('Spam detected.');
            echo json_encode(['success' => false, 'message' => __('Spam detected.', 'forms-by-made-it')]);
            wp_die();
        }

        $error = false;
        $error_msg = '';
        $spamScore = null;
        if (isset($this->defaultSettings['reCaptcha']['enabled']) && $this->defaultSettings['reCaptcha']['enabled']) {
            $secretKey = $this->defaultSettings['reCaptcha']['secret'];
            if (!isset($_POST['g-recaptcha-response'])) {
                $error = true;
                $error_msg = isset($messages['check_captcha']) ? $messages['check_captcha'] : __("The captcha couldn't validate you.", 'forms-by-made-it');
            }
            $response = $_POST['g-recaptcha-response'];
            $remoteIp = $_SERVER['REMOTE_ADDR'];
            $reCaptchaValidationUrl = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=$secretKey&response=$response&remoteip=$remoteIp");
            $result = json_decode($reCaptchaValidationUrl, true);

            if ($this->defaultSettings['reCaptcha']['version'] === 'V3') {
                $spamScore = $result['score'] ?? null;
                if ($result['score'] < $this->defaultSettings['reCaptcha']['minScore']) {
                    $error = true;
                    $error_msg = isset($messages['check_captcha']) ? $messages['check_captcha'] : __('The spam filter suspects a problem. Contact us by phone or e-mail.', 'forms-by-made-it');
                }
            } else {
                if ($result['success'] != 1) {
                    $error = true;
                    $error_msg = isset($messages['check_captcha']) ? $messages['check_captcha'] : __("The captcha couldn't validate you.", 'forms-by-made-it');
                }
            }
        }

        if (get_post_meta($form->ID, 'max_submits', true) == 1 && $this->hasAlreadyCompletedThisForm($form->ID)) {
            $error = true;
            $error_msg = isset($messages['already_submitted']) ? $messages['already_submitted'] : __('You have already submitted this form.', 'forms-by-made-it');
        }

        $error_msg = apply_filters('madeit_forms_submit_error', $error_msg, $form->ID);
        if (!empty($error_msg)) {
            $error = true;
        }

        if ($error) {
            $this->notifyError($error_msg);
            echo json_encode(['success' => false, 'message' => $error_msg, 'spamscore' => $spamScore]);
            wp_die();
        }

        //validate input fields
        $error = false;
        $error_msg = '';
        $messages = json_decode(str_replace("\'", "'", $this->dbToEnter(get_post_meta($form->ID, 'messages', true))), true);

        if (get_post_meta($form->ID, 'form_type', true) === 'html') {
            $formValue = get_post_meta($form->ID, 'form', true);
            $formValue = str_replace('\"', '"', $formValue);
            $formValue = str_replace("\'", "'", $formValue);
            foreach ($_POST as $k => $v) {
                $tag = $this->getTagNameFromPostInput($formValue, $k);
                if ($tag !== false) {
                    if (is_callable($this->tags[$tag]['validation'])) {
                        $tagOptions = $this->getOptionsFromTag($formValue, $tag, $k);
                        $result = call_user_func($this->tags[$tag]['validation'], $tagOptions, $v, $messages);
                        if ($result !== true) {
                            $error = true;
                            $error_msg = $result;
                        }
                    }
                }
            }
        } else {
            $blocks = parse_blocks($form->post_content);
            $blocks = $this->parseBlocks($blocks);

            foreach ($blocks as $block) {
                if (isset($block['attrs']['name'])) {
                    $tag = $block['attrs']['name'];
                    $tags[] = $tag;
                    $type = $block['attrs']['type'] ?? 'text';

                    if (isset($block['attrs']['required']) && $block['attrs']['required']) {
                        if (!isset($_POST[$tag]) || empty($_POST[$tag])) {
                            $error = true;
                            $error_msg = $messages['invalid_required'].' ('.$tag.')';
                        }
                    }

                    if (!empty($_POST[$tag]) && $type === 'email') {
                        if (!filter_var($_POST[$tag], FILTER_VALIDATE_EMAIL)) {
                            $error = true;
                            $error_msg = isset($messages['mod_text_invalid_email']) ? $messages['mod_text_invalid_email'] : $messages['validation_error'];
                        }
                    }

                    if (!empty($_POST[$tag]) && $type === 'url') {
                        if (!filter_var($_POST[$tag], FILTER_VALIDATE_URL)) {
                            $error = true;
                            $error_msg = isset($messages['mod_text_invalid_url']) ? $messages['mod_text_invalid_url'] : $messages['validation_error'];
                        }
                    }

                    if (!empty($_POST[$tag]) && $type === 'tel') {
                        if (!preg_match('%^[+]?[0-9()/ -]*$%', $_POST[$tag])) {
                            $error = true;
                            $error_msg = isset($messages['mod_text_invalid_phone']) ? $messages['mod_text_invalid_phone'] : $messages['validation_error'];
                        }
                    }

                    if (!empty($_POST[$tag]) && $type === 'number') {
                        if (!is_numeric($_POST[$tag])) {
                            $error = true;
                            $error_msg = isset($messages['mod_number_invalid_number']) ? $messages['mod_number_invalid_number'] : $messages['validation_error'];
                        }

                        if (isset($block['attrs']['minimum']) && $_POST[$tag] < $block['attrs']['minimum']) {
                            $error = true;
                            $error_msg = isset($messages['mod_number_number_too_small']) ? $messages['mod_number_number_too_small'] : $messages['validation_error'];
                        }

                        if (isset($block['attrs']['maximum']) && $_POST[$tag] > $block['attrs']['maximum']) {
                            $error = true;
                            $error_msg = isset($messages['mod_number_number_too_large']) ? $messages['mod_number_number_too_large'] : $messages['validation_error'];
                        }
                    }
                }
            }
        }

        if ($error) {
            $this->notifyError($error_msg);
            echo json_encode(['success' => false, 'message' => $error_msg]);
            wp_die();
        }

        //check spam
        $spam = false;

        //insert into DB
        $postData = $_POST;
        if ($spamScore !== null) {
            $postData['spamScore'] = $spamScore;
        }
        unset($postData['form_id']);
        unset($postData['action']);
        unset($postData['madeit_form_rendered_at']);
        unset($postData['madeit_website']);

        $inputId = -1;
        if (get_post_meta($form->ID, 'save_inputs', true) == 1) {
            $inputId = wp_insert_post([
                'post_title'  => 'Form submit '.$form->post_title.' - '.$this->getIP(),
                'post_status' => 'publish',
                'post_type'   => 'ma_form_inputs',
            ]);

            $postData['input_id'] = $inputId;

            $postData = apply_filters('madeit_forms_post_data', $postData, $form->ID, $inputId);
            $postData = apply_filters('madeit_forms_'.$form->ID.'_post_data', $postData, $inputId);

            update_post_meta($inputId, 'form_id', $form->ID);
            update_post_meta($inputId, 'data', $this->enterToDB(json_encode($postData)));
            update_post_meta($inputId, 'ip', $this->getIP());
            update_post_meta($inputId, 'user_agent', isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'UNKNOWN');
            update_post_meta($inputId, 'spam', $spam ? 1 : 0);
            update_post_meta($inputId, 'read', 0);
            update_post_meta($inputId, 'result', '');
        }
        $outputHtml = '';

        $submittedTimes = isset($_COOKIE['madeit_form_'.$form->ID.'_submitted']) ? $_COOKIE['madeit_form_'.$form->ID.'_submitted'] : 0;
        setcookie('madeit_form_'.$form->ID.'_submitted', $submittedTimes, time() + 31556926);

        //execute actions
        $actions = json_decode(str_replace("\'", "'", $this->dbToEnter(get_post_meta($form->ID, 'actions', true))), true);
        if (count($actions) > 0) {
            $formActions = apply_filters('madeit_forms_submit_actions', $actions);
            foreach ($formActions as $actID => $actionInfo) {
                $action = $this->actions[$actionInfo['_id']];

                $data = [
                    'id' => $inputId,
                ];
                foreach ($action['action_fields'] as $name => $info) {
                    $inputValue = isset($actionInfo[$name]) ? $actionInfo[$name] : $info['value'];
                    $data[$name] = $this->changeInputTag($inputValue, $postData);
                }

                if (is_callable($action['callback'])) {
                    $data = apply_filters('madeit_forms_action_data', $data, $form->ID, $inputId, $actionInfo, $postData);
                    $result = call_user_func($action['callback'], $data, $messages, $actionInfo, $form->ID, $inputId, $postData);
                    if (is_array($result) && isset($result['type'])) {
                        if ($result['type'] == 'JS') {
                            $outputHtml .= '<script>'.$result['code'].'</script>';
                        } elseif ($result['type'] == 'HTML') {
                            $outputHtml .= str_replace('\"', '"', $result['code']);
                        }
                    } elseif ($result !== true) {
                        $error = true;
                        $error_msg = $result;
                    }
                }
            }
        }

        if ($error) {
            $this->notifyError($error_msg);
            echo json_encode(['success' => false, 'message' => $error_msg]);
            wp_die();
        } else {
            echo json_encode(['success' => true, 'message' => $messages['success'], 'html' => $outputHtml]);
            wp_die();
        }
    }

    public function generateViewImage()
    {
        if (isset($_GET['madeit_forms_view']) && $_GET['madeit_forms_view'] == 'yes' && isset($_GET['input_id'])) {
            $formInputId = $_GET['input_id'];
            $post = get_post($formInputId);
            if ($post->post_type === 'ma_form_inputs') {
                update_post_meta($post->ID, 'read', 1);
            }

            header('Content-Type: image/png');
            echo base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=');
            exit;
        }
    }

    public function addHooks()
    {
        add_action('init', [$this, 'init']);
        add_action('init', [$this, 'generateViewImage'], 1);

        add_action('wp_ajax_madeit_forms_submit', [$this, 'submitAjaxForm']);
        add_action('wp_ajax_nopriv_madeit_forms_submit', [$this, 'submitAjaxForm']);
    }

    public function enterToDB($data)
    {
        $data = str_replace('\r\n', '|--MAFORM-RN--|', $data);
        $data = str_replace('\r', '|--MAFORM-R--|', $data);
        $data = str_replace('\n', '|--MAFORM-N--|', $data);

        return $data;
    }

    public function dbToEnter($data)
    {
        $data = str_replace('|--MAFORM-RN--|', '\r\n', $data);
        $data = str_replace('|--MAFORM-R--|', '\r', $data);
        $data = str_replace('|--MAFORM-N--|', '\n', $data);

        $data = preg_replace('/u([\da-fA-F]{4})/', '&#x\1;', $data);

        return $data;
    }

    private function isSpam($data)
    {
        if (!$this->spamProtection instanceof WP_Form_Spam_Protection) {
            $this->spamProtection = new WP_Form_Spam_Protection([$this, 'getIP']);
        }

        return $this->spamProtection->isSpam($data);
    }

    private function hasAlreadyCompletedThisForm($formId)
    {
        $hasCompleted = false;

        if (isset($_COOKIE['madeit_form_'.$formId.'_submitted'])) {
            $hasCompleted = true;
        }

        return $hasCompleted;
    }

    private function checkQuiz($content)
    {
        if (strpos($content, 'wp-block-madeitforms-question-seperator') !== false) {
            // Is quiz
            $content = str_replace('wp-block-madeitforms-question-seperator madeit-forms-input-field', 'wp-block-madeitforms-question-seperator', $content);

            $contentSeperators = explode('<div class="wp-block-madeitforms-question-seperator"></div>', $content);
            $content = '';
            foreach ($contentSeperators as $key => $contentSeperator) {
                $content .= '<div class="madeit-forms-quiz-question '.($key > 0 ? 'hide-question' : '').'" data-question="'.$key.'">'.$contentSeperator;

                // Voeg knoppen toe als dit niet de laatste vraag is.
                $content .= '<div class="madeit-forms-quiz-question-buttons">';

                // Alles behalve de eerste pagina een vorige knop invoeren
                if ($key > 0) {
                    $classPrev = apply_filters('madeit_forms_question_prev_button_class', ['madeit-forms-quiz-question-button', 'madeit-forms-quiz-question-button-prev'], $key, $this->form_id);
                    $content .= '<button class="'.implode(' ', $classPrev).'" data-question="'.$key.'">'.__('Previous', 'forms-by-made-it').'</button>';
                }

                // Een volgende knop weergeven, behalve de laatste pagina.
                $classNext = apply_filters('madeit_forms_question_next_button_class', ['madeit-forms-quiz-question-button', 'madeit-forms-quiz-question-button-next', 'ms-auto'], $key, $this->form_id);
                $content .= '<button class="'.implode(' ', $classNext).'" data-question="'.$key.'" '.($key == count($contentSeperators) - 1 ? 'disabled' : '').'>'.apply_filters('madeit_forms_question_button_next', __('Next', 'forms-by-made-it'), $key, $this->form_id).'</button>';

                $content .= '</div>';
                $content .= '</div>';
            }

            $content = '<div class="madeit-forms-quiz-container" data-steps="'.count($contentSeperators).'" data-current-step="0">'.$content.'</div>';
        }

        return $content;
    }

    private function notifyError($error = null)
    {
        global $_POST;
        //Send post request to url with all $_POST data, error, form_id, IP and user agent
        $url = 'https://portal.madeit.be/forms/error';

        $data = [
            'website' => get_site_url(),
            'error'   => $error,
            'form_id' => $this->form_id,
            'ip'      => $this->getIP(),
            'ua'      => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'UNKNOWN',
            'data'    => json_encode($_POST, JSON_PRETTY_PRINT),
        ];

        error_log(json_encode($data, JSON_PRETTY_PRINT));

        //curl
        try {
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
            $result = curl_exec($ch);
            curl_close($ch);
        } catch (Exception $e) {
            error_log($e->getMessage());
        }
    }

    public function getMaximumFileUploadSize()
    {
        return min($this->convertPHPSizeToBytes(ini_get('post_max_size')), $this->convertPHPSizeToBytes(ini_get('upload_max_filesize')));
    }

    public function convertPHPSizeToBytes($sSize)
    {
        $sSuffix = strtoupper(substr($sSize, -1));
        if (!in_array($sSuffix, ['P', 'T', 'G', 'M', 'K'])) {
            return (int) $sSize;
        }
        $iValue = substr($sSize, 0, -1);
        switch ($sSuffix) {
            case 'P':
                $iValue *= 1024;
                // Fallthrough intended
            case 'T':
                $iValue *= 1024;
                // Fallthrough intended
            case 'G':
                $iValue *= 1024;
                // Fallthrough intended
            case 'M':
                $iValue *= 1024;
                // Fallthrough intended
            case 'K':
                $iValue *= 1024;
                break;
        }

        return (int) $iValue;
    }

    private function ensureUploadsProtection($dir)
    {
        $base = rtrim($dir, '/');
        $baseForms = dirname($base);
        // Ensure a .htaccess one level up (madeit-forms) and in current dir
        $targets = [$baseForms, $base];
        $rules = "# Security: block script execution in form uploads\n".
                 "<FilesMatch \\\"\\.(php|phtml|php3|php4|php5|php7|php8|phps|phar)$\\\">\n".
                 "  Require all denied\n".
                 "</FilesMatch>\n".
                 "Options -ExecCGI\n".
                 "RemoveHandler .php .phtml .phar\n".
                 "RemoveType .php .phtml .phar\n";
        foreach ($targets as $t) {
            if (!is_dir($t)) {
                @mkdir($t, 0755, true);
            }
            $ht = rtrim($t, '/').'/.htaccess';
            if (!file_exists($ht)) {
                @file_put_contents($ht, $rules);
            }
        }
    }

    private function getAllowedMimesForField($fieldAttrs)
    {
        $list = [];
        if (is_array($fieldAttrs)) {
            $keys = ['filetype'];
            foreach ($keys as $key) {
                if (!empty($fieldAttrs[$key])) {
                    if (is_array($fieldAttrs[$key])) {
                        $parts = $fieldAttrs[$key];
                    } else {
                        $parts = preg_split('/[,\s]+/', (string) $fieldAttrs[$key]);
                    }
                    foreach ($parts as $p) {
                        $p = trim(strtolower((string) $p));
                        if ($p !== '') {
                            $list[] = $p;
                        }
                    }
                }
            }
        }
        $list = array_values(array_unique($list));

        return apply_filters('madeit_forms_allowed_upload_mimes', $list, $fieldAttrs, $this->form_id);
    }

    private function mimeMatchesAllowed($mime, $allowed)
    {
        $mime = strtolower((string) $mime);
        foreach ((array) $allowed as $allow) {
            $allow = strtolower((string) $allow);
            if ($allow === $mime) {
                return true;
            }
            if (substr($allow, -2) === '/*') {
                $prefix = substr($allow, 0, strpos($allow, '/*'));
                if (strpos($mime, $prefix.'/') === 0) {
                    return true;
                }
            }
        }

        return false;
    }

    private function extensionFromMime($mime)
    {
        $mime = strtolower((string) $mime);
        $map = wp_get_mime_types(); // ext(s) => mime
        foreach ($map as $exts => $m) {
            if (strtolower($m) === $mime) {
                $first = explode('|', $exts)[0];

                return strtolower($first);
            }
        }
        // Fallbacks for common types possibly missing
        $fallback = [
            'application/pdf' => 'pdf',
            'text/plain'      => 'txt',
        ];

        return isset($fallback[$mime]) ? $fallback[$mime] : '';
    }
}
