<?php

if (!defined('ABSPATH')) {
    exit;
}

/*----------------------------------------
メール表示用の送信データを生成
----------------------------------------*/
function immf_get_display_data(
    $fields,
    $submitted_data
) {

    $display_data = $submitted_data;

    foreach ($fields as $field) {

        $name = sanitize_key(
            $field['name'] ?? ''
        );

        $type = sanitize_key(
            $field['type'] ?? ''
        );

        if (
            $name === '' ||
            !array_key_exists(
                $name,
                $submitted_data
            )
        ) {
            continue;
        }

        if (
            !in_array(
                $type,
                [
                    'select',
                    'radio',
                    'checkbox',
                ],
                true
            )
        ) {
            continue;
        }

        $option_values = preg_split(
            '/\r\n|\r|\n/',
            $field['options'] ?? ''
        );

        $option_labels = [];

        foreach ($option_values as $option) {

            $option = trim($option);

            if ($option === '') {
                continue;
            }

            $parts = array_map(
                'trim',
                explode(
                    '|',
                    $option,
                    2
                )
            );

            $option_value = $parts[0];

            $option_label = $parts[1]
                ?? $parts[0];

            $option_labels[$option_value] =
                $option_label;
        }

        $value = $submitted_data[$name];

        if (
            is_array($value) &&
            array_is_list($value)
        ) {

            $display_data[$name] = array_map(
                static function ($item) use (
                    $option_labels
                ) {

                    return $option_labels[$item]
                        ?? sanitize_text_field(
                            $item
                        );
                },
                $value
            );

        } elseif (is_scalar($value)) {

            $display_data[$name] =
                $option_labels[$value]
                ?? (string) $value;
        }
    }

    return $display_data;
}


/*----------------------------------------
すべてのフォーム項目をテキストへ変換
----------------------------------------*/
function immf_get_all_fields_text(
    $fields,
    $display_data
) {

    $lines = [];

    foreach ($fields as $field) {

        $name = sanitize_key(
            $field['name'] ?? ''
        );

        if (
            $name === '' ||
            !array_key_exists(
                $name,
                $display_data
            )
        ) {
            continue;
        }

        $label = sanitize_text_field(
            $field['label'] ?? $name
        );

        $value = $display_data[$name];

        if (
            is_array($value) &&
            array_is_list($value)
        ) {

            $value = implode(
                '、',
                array_map(
                    'sanitize_text_field',
                    $value
                )
            );

        } elseif (
            is_array($value) &&
            isset($value['name'])
        ) {

            $value = sanitize_file_name(
                $value['name']
            );

        } elseif (is_scalar($value)) {

            $value = (string) $value;

        } else {

            $value = '';

        }

        $lines[] =
            $label
            . '：'
            . $value;
    }

    return implode(
        "\n",
        $lines
    );

}


/*----------------------------------------
メール本文：差し込みタグを置換
----------------------------------------*/
function immf_replace_contact_form_mail_tags(
    $text,
    $submitted_data
) {

    if (!is_string($text)) {
        return '';
    }

    $replacements = [];

    foreach ($submitted_data as $name => $value) {

        $tag = '{' . $name . '}';


        /*
        * チェックボックスなどの
        * 複数選択値
        */
        if (
            is_array($value) &&
            array_is_list($value)
        ) {

            $replacement = implode(
                '、',
                array_map(
                    'sanitize_text_field',
                    $value
                )
            );


        /*
        * テキスト・メールアドレスなどの
        * 通常の入力値
        */
        } elseif (is_scalar($value)) {

            $replacement = (string) $value;


        /*
        * 添付ファイル情報などは
        * メール本文へ直接差し込まない
        */
        } else {

            $replacement = '';

        }

        $replacements[$tag] = $replacement;
    }

    return strtr(
        $text,
        $replacements
    );
}

/*----------------------------------------
メールヘッダー用の表示名をサニタイズ
----------------------------------------*/
function immf_sanitize_mail_header_name(
    $name
) {

    $name = sanitize_text_field($name);

    return str_replace(
        ["\r", "\n"],
        '',
        $name
    );
}

/*----------------------------------------
確認画面用の一時ファイル情報を取得
----------------------------------------*/
function immf_get_temp_file_data(
    $token,
    $post_id,
    $field_name
) {

    $token = sanitize_key($token);
    $field_name = sanitize_key($field_name);
    $post_id = absint($post_id);

    if (
        $token === '' ||
        $field_name === '' ||
        $post_id === 0
    ) {
        return null;
    }

    $transient_key =
        'immf_temp_file_'
        . $token;

    $file_data = get_transient(
        $transient_key
    );

    if (
        !is_array($file_data) ||
        (int) ($file_data['post_id'] ?? 0)
            !== $post_id ||
        sanitize_key(
            $file_data['field_name'] ?? ''
        ) !== $field_name
    ) {
        return null;
    }

    $file_name = sanitize_file_name(
        $file_data['name'] ?? ''
    );

    $file_path = wp_normalize_path(
        $file_data['path'] ?? ''
    );

    $temp_directory = trailingslashit(
        wp_normalize_path(
            get_temp_dir()
        )
    );

    if (
        $file_name === '' ||
        $file_path === '' ||
        !str_starts_with(
            $file_path,
            $temp_directory
        ) ||
        !is_file($file_path) ||
        !is_readable($file_path)
    ) {
        return null;
    }

    return [
        'token' => $token,
        'name' => $file_name,
        'path' => $file_path,
        'transient_key' => $transient_key,
    ];
}

/*----------------------------------------
期限切れの一時ファイルを削除
----------------------------------------*/
function immf_cleanup_temp_file(
    $file_path,
    $transient_key
) {

    $file_path = wp_normalize_path(
        $file_path
    );

    $transient_key = sanitize_key(
        $transient_key
    );

    $temp_directory = trailingslashit(
        wp_normalize_path(
            get_temp_dir()
        )
    );

    if (
        $file_path !== '' &&
        str_starts_with(
            $file_path,
            $temp_directory
        ) &&
        is_file($file_path)
    ) {
        wp_delete_file($file_path);
    }

    if (
        str_starts_with(
            $transient_key,
            'immf_temp_file_'
        )
    ) {
        delete_transient($transient_key);
    }
}

add_action(
    'immf_cleanup_temp_file',
    'immf_cleanup_temp_file',
    10,
    2
);

/*----------------------------------------
送信元IPアドレスを取得
----------------------------------------*/
function immf_get_client_ip() {

    $ip_address = isset(
        $_SERVER['REMOTE_ADDR']
    )
        ? sanitize_text_field(
            wp_unslash(
                $_SERVER['REMOTE_ADDR']
            )
        )
        : '';

    if (
        $ip_address === '' ||
        !filter_var(
            $ip_address,
            FILTER_VALIDATE_IP
        )
    ) {
        return '';
    }

    return $ip_address;
}

/*----------------------------------------
メールフォーム：送信リクエスト受付
----------------------------------------*/
function immf_handle_contact_form_submission() {

    // POSTリクエスト以外は処理しない
    $request_method = isset(
        $_SERVER['REQUEST_METHOD']
    )
        ? sanitize_text_field(
            wp_unslash(
                $_SERVER['REQUEST_METHOD']
            )
        )
        : '';

    if (strtoupper($request_method) !== 'POST') {
        return;
    }

    // 送信処理の種類を取得
    $action = isset(
        $_POST['im_mail_form_action']
    )
        ? sanitize_key(
            wp_unslash(
                $_POST['im_mail_form_action']
            )
        )
        : '';


    // 確認・修正・送信以外は処理しない
    if (
        !in_array(
            $action,
            [
                'confirm',
                'back',
                'submit',
            ],
            true
        )
    ) {
        return;
    }

    // フォームの投稿IDを取得
    $post_id = isset(
        $_POST['im_mail_form_post_id']
    )
        ? absint(
            $_POST['im_mail_form_post_id']
        )
        : 0;

    if ($post_id === 0) {
        return;
    }

    // 公開中のメールフォーム投稿か確認
    if (
        get_post_type($post_id) !== 'im_mail_form' ||
        get_post_status($post_id) !== 'publish'
    ) {
        return;
    }

    // nonceが存在するか確認
    if (
        !isset($_POST['im_mail_form_nonce'])
    ) {
        return;
    }

    // nonceを検証
    $nonce = sanitize_text_field(
        wp_unslash(
            $_POST['im_mail_form_nonce']
        )
    );

    if (
        !wp_verify_nonce(
            $nonce,
            'im_mail_form_submit_' . $post_id
        )
    ) {
        return;
    }

    /*----------------------------------------
    Honeypot
    ----------------------------------------*/
    $honeypot = isset($_POST['website'])
        ? sanitize_text_field(
            wp_unslash(
                $_POST['website']
            )
        )
        : '';

    if ($honeypot !== '') {
        return;
    }

    /*----------------------------------------
    Time Trap
    ----------------------------------------*/
    $form_timestamp = isset(
        $_POST['im_mail_form_timestamp']
    )
        ? absint(
            wp_unslash(
                $_POST['im_mail_form_timestamp']
            )
        )
        : 0;

    if (
        $action === 'submit' &&
        (
            $form_timestamp === 0 ||
            (time() - $form_timestamp) < 3
        )
    ) {
        return;
    }

    /*----------------------------------------
    Rate Limit
    ----------------------------------------*/
    if ($action === 'submit') {

        $ip_address = immf_get_client_ip();

        if ($ip_address !== '') {

            $ip_hash = md5($ip_address);

            $block_key =
                'immf_rate_limit_block_'
                . $ip_hash;

            if (get_transient($block_key)) {
                return;
            }

            $rate_limit_key =
                'immf_rate_limit_'
                . $ip_hash;

            $count = (int) get_transient(
                $rate_limit_key
            );

            $count++;

            if ($count > 20) {

                set_transient(
                    $block_key,
                    true,
                    30 * MINUTE_IN_SECONDS
                );

                delete_transient(
                    $rate_limit_key
                );

                return;
            }

            set_transient(
                $rate_limit_key,
                $count,
                MINUTE_IN_SECONDS
            );
        }
    }

    /*----------------------------------------
    フォーム項目を取得
    ----------------------------------------*/
    $fields = get_post_meta(
        $post_id,
        'immf_fields',
        true
    );

    if (
        !is_array($fields) ||
        empty($fields)
    ) {
        return;
    }


    /*----------------------------------------
    送信値とエラーの初期化
    ----------------------------------------*/
    $submitted_data = [];
    $errors = [];

    /*----------------------------------------
    フォームの入力値を取得
    ----------------------------------------*/
    $posted_data = isset(
        $_POST['im_mail_form_data']
    ) && is_array(
        $_POST['im_mail_form_data']
    )
        ? wp_unslash(
            // Values are sanitized by field type in the validation loop below.
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
            $_POST['im_mail_form_data']
        )
        : [];

    /*----------------------------------------
    確認画面から一時ファイル情報を取得
    ----------------------------------------*/
    $posted_confirm_files = isset(
        $_POST['im_mail_form_confirm_files']
    ) && is_array(
        $_POST['im_mail_form_confirm_files']
    )
        ? wp_unslash(
            // File names and tokens are sanitized and verified below.
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
            $_POST['im_mail_form_confirm_files']
        )
        : [];

    /*----------------------------------------
    一時ファイルの削除状態を取得
    ----------------------------------------*/
    $posted_remove_confirm_files = isset(
        $_POST['im_mail_form_remove_confirm_files']
    ) && is_array(
        $_POST['im_mail_form_remove_confirm_files']
    )
        ? array_map(
            'sanitize_key',
            wp_unslash(
                // File names and tokens are sanitized and verified below.
                // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
                $_POST['im_mail_form_remove_confirm_files']
            )
        )
        : [];

    /*----------------------------------------
    各フォーム項目の値を取得・検証
    ----------------------------------------*/
    foreach ($fields as $field) {

        $label = sanitize_text_field(
            $field['label'] ?? ''
        );

        $name = sanitize_key(
            $field['name'] ?? ''
        );

        $type = sanitize_key(
            $field['type'] ?? 'text'
        );

        /*----------------------------------------
        フォーム種類に応じたバリデーションを取得
        ----------------------------------------*/
        $validation_by_type = [
            'email' => 'email',
            'tel' => 'tel',
            'date' => 'date',
            'file' => 'file',
            'select' => '',
            'radio' => '',
            'checkbox' => '',
            'hidden' => '',
        ];

        $validation = $validation_by_type[$type]
            ?? sanitize_key(
                $field['validation'] ?? ''
            );

        /*----------------------------------------
        選択可能なバリデーションを制限
        ----------------------------------------*/
        $allowed_validations = [
            'text' => [
                '',
                'number',
                'alphanumeric',
            ],
            'textarea' => [
                '',
            ],
        ];

        if (
            isset($allowed_validations[$type]) &&
            !in_array(
                $validation,
                $allowed_validations[$type],
                true
            )
        ) {
            $validation = '';
        }

        $required =
            ($field['required'] ?? '0') === '1';

        
        /*----------------------------------------
        設定済みの選択肢を取得
        ----------------------------------------*/
        $option_values = preg_split(
            '/\r\n|\r|\n/',
            $field['options'] ?? ''
        );

        $option_values = array_map(
            static function ($option) {

                $option = trim($option);

                if ($option === '') {
                    return '';
                }

                $parts = array_map(
                    'trim',
                    explode(
                        '|',
                        $option,
                        2
                    )
                );

                return $parts[0];
            },
            $option_values
        );

        $option_values = array_values(
            array_filter(
                $option_values,
                static function ($option) {
                    return $option !== '';
                }
            )
        );

        if ($name === '') {
            continue;
        }


        /*----------------------------------------
        hidden項目
        ----------------------------------------*/
        if ($type === 'hidden') {

            $submitted_data[$name] =
                sanitize_text_field(
                    $field['hidden_value'] ?? ''
                );

            continue;
        }


        /*----------------------------------------
        チェックボックス
        ----------------------------------------*/
        if ($type === 'checkbox') {

            $value = isset($posted_data[$name])
                && is_array($posted_data[$name])
                    ? array_map(
                        'sanitize_text_field',
                        $posted_data[$name]
                    )
                    : [];


            /*----------------------------------------
            必須選択の検証
            ----------------------------------------*/
            if ($required && empty($value)) {
                $errors[$name] = sprintf(
                    /* translators: %s: Form field label. */
                    __(
                        'Please select %s.',
                        'im-mail-form'
                    ),
                    $label
                );
            }


            /*----------------------------------------
            設定外の選択肢を検証
            ----------------------------------------*/
            $invalid_values = array_diff(
                $value,
                $option_values
            );

            if (!empty($invalid_values)) {
                $errors[$name] = sprintf(
                    /* translators: %s: Form field label. */
                    __(
                        '%s contains an invalid option.',
                        'im-mail-form'
                    ),
                    $label
                );
            }


            $submitted_data[$name] = $value;

            continue;
        }


        /*----------------------------------------
        ファイル項目
        ----------------------------------------*/
        if ($type === 'file') {

            /*----------------------------------------
            確認画面から一時ファイル情報を取得
            ----------------------------------------*/
            $confirm_file = isset(
                $posted_confirm_files[$name]
            ) && is_array(
                $posted_confirm_files[$name]
            )
                ? $posted_confirm_files[$name]
                : null;

            /*----------------------------------------
            一時ファイルの削除状態を判定
            ----------------------------------------*/
            $remove_confirm_file = isset(
                $posted_remove_confirm_files[$name]
            ) && (
                $posted_remove_confirm_files[$name]
                === '1'
            );

            /*----------------------------------------
            確認画面経由の一時ファイルを検証
            ----------------------------------------*/
            $confirmed_file = null;

            if (
                $remove_confirm_file &&
                $confirm_file
            ) {

                $confirm_file_token = isset(
                    $confirm_file['token']
                )
                    ? sanitize_key(
                        $confirm_file['token']
                    )
                    : '';

                $temp_file_data =
                    immf_get_temp_file_data(
                        $confirm_file_token,
                        $post_id,
                        $name
                    );

                if ($temp_file_data) {

                    wp_delete_file(
                        $temp_file_data['path']
                    );

                    delete_transient(
                        $temp_file_data['transient_key']
                    );
                }

                $confirm_file = null;
            }

            if ($confirm_file) {

                $confirm_file_token = isset(
                    $confirm_file['token']
                )
                    ? sanitize_key(
                        $confirm_file['token']
                    )
                    : '';

                $temp_file_data =
                    immf_get_temp_file_data(
                        $confirm_file_token,
                        $post_id,
                        $name
                    );

                if ($temp_file_data) {

                    $confirmed_file = [
                        'name' => $temp_file_data['name'],
                        'tmp_name' => $temp_file_data['path'],
                        'error' => UPLOAD_ERR_OK,
                        'size' => filesize(
                            $temp_file_data['path']
                        ),
                        'transient_key' =>
                            $temp_file_data['transient_key'],
                    ];

                } else {

                    $errors[$name] = sprintf(
                        /* translators: %s: File field label. */
                        __(
                            'The temporary file for %s is invalid.',
                            'im-mail-form'
                        ),
                        $label
                    );

                }
            }


            $file = null;

            if (
                isset($_FILES['im_mail_form_files']) &&
                is_array(
                    $_FILES['im_mail_form_files']
                )
            ) {

                $uploaded_files =
                    // Uploaded file data is validated field by field below.
                    // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
                    $_FILES['im_mail_form_files'];

                if (
                    isset(
                        $uploaded_files['name'][$name],
                        $uploaded_files['tmp_name'][$name],
                        $uploaded_files['error'][$name],
                        $uploaded_files['size'][$name]
                    )
                ) {

                    $file = [
                        'name' => $uploaded_files['name'][$name],
                        'type' => $uploaded_files['type'][$name] ?? '',
                        'tmp_name' =>
                            $uploaded_files['tmp_name'][$name],
                        'error' =>
                            $uploaded_files['error'][$name],
                        'size' =>
                            $uploaded_files['size'][$name],
                    ];
                }
            }


            /*----------------------------------------
            新しいファイルがない場合は
            確認画面経由の一時ファイルを使用
            ----------------------------------------*/
            if (
                $confirmed_file !== null &&
                (
                    $file === null ||
                    (
                        (int) (
                            $file['error']
                            ?? UPLOAD_ERR_NO_FILE
                        )
                    ) === UPLOAD_ERR_NO_FILE
                )
            ) {
                $file = $confirmed_file;
            }

            $upload_error = isset($file['error'])
                ? (int) $file['error']
                : UPLOAD_ERR_NO_FILE;

            $has_file =
                $upload_error !== UPLOAD_ERR_NO_FILE;


            /*----------------------------------------
            必須ファイルの検証
            ----------------------------------------*/
            if ($required && !$has_file) {
                $errors[$name] = sprintf(
                    /* translators: %s: File field label. */
                    __(
                        'Please select a file for %s.',
                        'im-mail-form'
                    ),
                    $label
                );
            }


            /*----------------------------------------
            アップロードエラーの検証
            ----------------------------------------*/
            if (
                $has_file &&
                $upload_error !== UPLOAD_ERR_OK
            ) {
                $errors[$name] = sprintf(
                    /* translators: %s: File field label. */
                    __(
                        'Failed to upload the file for %s.',
                        'im-mail-form'
                    ),
                    $label
                );
            }

            /*----------------------------------------
            ファイル形式の検証
            ----------------------------------------*/
            if (
                $has_file &&
                $upload_error === UPLOAD_ERR_OK
            ) {

                $file_type_settings = [
                    'jpeg' => [
                        'label' => 'JPG / JPEG',
                        'extensions' => [
                            'jpg',
                            'jpeg',
                        ],
                    ],
                    'png' => [
                        'label' => 'PNG',
                        'extensions' => [
                            'png',
                        ],
                    ],
                    'webp' => [
                        'label' => 'WebP',
                        'extensions' => [
                            'webp',
                        ],
                    ],
                    'pdf' => [
                        'label' => 'PDF',
                        'extensions' => [
                            'pdf',
                        ],
                    ],
                ];

                $allowed_file_types =
                    $field['allowed_file_types'] ?? [];

                $allowed_file_extensions = [];
                $allowed_file_type_labels = [];

                foreach (
                    $allowed_file_types as $file_type
                ) {

                    if (
                        !isset(
                            $file_type_settings[$file_type]
                        )
                    ) {
                        continue;
                    }

                    $allowed_file_extensions = array_merge(
                        $allowed_file_extensions,
                        $file_type_settings[$file_type]['extensions']
                    );

                    $allowed_file_type_labels[] =
                        $file_type_settings[$file_type]['label'];

                }

            }

            /*----------------------------------------
            ファイルサイズの検証
            ----------------------------------------*/
            if (
                $has_file &&
                $upload_error === UPLOAD_ERR_OK
            ) {
                $file_size = isset($file['size'])
                    ? (int) $file['size']
                    : 0;

                $wordpress_max_upload_size =
                    wp_max_upload_size();

                $configured_max_file_size = isset(
                    $field['max_file_size']
                ) && $field['max_file_size'] !== ''
                    ? absint(
                        $field['max_file_size']
                    ) * MB_IN_BYTES
                    : $wordpress_max_upload_size;

                $max_file_size = min(
                    $wordpress_max_upload_size,
                    $configured_max_file_size
                );

                if ($file_size > $max_file_size) {
                    $errors[$name] = sprintf(
                        /* translators: 1: File field label, 2: Maximum file size. */
                        __(
                            'Please select a file for %1$s that is no larger than %2$s.',
                            'im-mail-form'
                        ),
                        $label,
                        size_format($max_file_size)
                    );
                }
            }

            /*----------------------------------------
            許可された拡張子の検証
            ----------------------------------------*/
            if (
                $has_file &&
                $upload_error === UPLOAD_ERR_OK &&
                !isset($errors[$name])
            ) {
                $file_extension = strtolower(
                    pathinfo(
                        $file['name'] ?? '',
                        PATHINFO_EXTENSION
                    )
                );

                if (
                    !in_array(
                        $file_extension,
                        $allowed_file_extensions,
                        true
                    )
                ) {
                    $errors[$name] = sprintf(
                        /* translators: 1: File field label, 2: Allowed file formats. */
                        __(
                            'Please select a file for %1$s in one of the following formats: %2$s.',
                            'im-mail-form'
                        ),
                        $label,
                        implode(
                            ', ',
                            $allowed_file_type_labels
                        )
                    );
                }
            }

            /*----------------------------------------
            ファイル形式の検証
            ----------------------------------------*/
            if (
                $has_file &&
                $upload_error === UPLOAD_ERR_OK &&
                !isset($errors[$name])
            ) {
                $file_name = isset($file['name'])
                    ? sanitize_file_name($file['name'])
                    : '';

                $file_type = wp_check_filetype_and_ext(
                    $file['tmp_name'],
                    $file_name
                );

                if (
                    empty($file_type['ext']) ||
                    empty($file_type['type']) ||
                    !in_array(
                        strtolower($file_type['ext']),
                        $allowed_file_extensions,
                        true
                    )
                ) {
                    $errors[$name] = sprintf(
                        /* translators: 1: File field label, 2: Allowed file formats. */
                        __(
                            'Please select a file for %1$s in one of the following formats: %2$s.',
                            'im-mail-form'
                        ),
                        $label,
                        implode(
                            ', ',
                            $allowed_file_type_labels
                        )
                    );
                }
            }

            /*----------------------------------------
            正常なファイル情報を保持
            ----------------------------------------*/
            $submitted_data[$name] =
                $has_file &&
                $upload_error === UPLOAD_ERR_OK &&
                !isset($errors[$name])
                    ? $file
                    : null;

            continue;
        }


        /*----------------------------------------
        通常項目
        ----------------------------------------*/
        $value = isset($posted_data[$name])
            && is_scalar($posted_data[$name])
                ? sanitize_textarea_field(
                    (string) $posted_data[$name]
                )
                : '';


        /*----------------------------------------
        セレクト・ラジオの選択値を検証
        ----------------------------------------*/
        if (
            $value !== '' &&
            in_array(
                $type,
                ['select', 'radio'],
                true
            ) &&
            !in_array(
                $value,
                $option_values,
                true
            )
        ) {
            $errors[$name] = sprintf(
                /* translators: %s: Form field label. */
                __(
                    'An invalid option was selected for %s.',
                    'im-mail-form'
                ),
                $label
            );
        }


        /*----------------------------------------
        必須入力の検証
        ----------------------------------------*/
        if ($required && $value === '') {
            $errors[$name] = sprintf(
                /* translators: %s: Field label. */
                __(
                    'Please enter %s.',
                    'im-mail-form'
                ),
                $label
            );
        }

        /*----------------------------------------
        メールアドレス全角文字の検証
        ----------------------------------------*/
        if (
            $value !== '' &&
            $validation === 'email' &&
            preg_match('/[^\x20-\x7E]/', $value)
        ) {
            $errors[$name] = sprintf(
                /* translators: %s: Form field label. */
                __(
                    'Please enter %s using ASCII characters only.',
                    'im-mail-form'
                ),
                $label
            );
        }

        /*----------------------------------------
        メールアドレス形式の検証
        ----------------------------------------*/
        if (
            $value !== '' &&
            $validation === 'email' &&
            !isset($errors[$name]) &&
            !is_email($value)
        ) {
            $errors[$name] = sprintf(
                /* translators: %s: Form field label. */
                __(
                    'Please enter a valid value for %s.',
                    'im-mail-form'
                ),
                $label
            );
        }

        /*----------------------------------------
        半角数字のみの検証
        ----------------------------------------*/
        if (
            $value !== '' &&
            $validation === 'number' &&
            !ctype_digit($value)
        ) {
            $errors[$name] = sprintf(
                /* translators: %s: Form field label. */
                __(
                    'Please enter %s using numbers only.',
                    'im-mail-form'
                ),
                $label
            );
        }

        /*----------------------------------------
        半角英数字・記号の検証
        ----------------------------------------*/
        if (
            $value !== '' &&
            $validation === 'alphanumeric' &&
            !preg_match(
                '/^[A-Za-z0-9_.@-]+$/',
                $value
            )
        ) {
            $errors[$name] = sprintf(
                /* translators: %s: Form field label. */
                __(
                    'Please enter %s using only letters, numbers, and the symbols - _ . @.',
                    'im-mail-form'
                ),
                $label
            );
        }

        /*----------------------------------------
        電話番号の検証
        ----------------------------------------*/
        if (
            $value !== '' &&
            $validation === 'tel' &&
            !ctype_digit($value)
        ) {
            $errors[$name] = sprintf(
                /* translators: %s: Form field label. */
                __(
                    'Please enter %s using numbers only.',
                    'im-mail-form'
                ),
                $label
            );
        }

        /*----------------------------------------
        日付形式の検証
        ----------------------------------------*/
        if (
            $value !== '' &&
            $validation === 'date'
        ) {
            $date = DateTime::createFromFormat(
                'Y-m-d',
                $value
            );

            $is_valid_date =
                $date !== false &&
                $date->format('Y-m-d') === $value;

            if (!$is_valid_date) {
                $errors[$name] = sprintf(
                    /* translators: %s: Form field label. */
                    __(
                        'Please enter a valid date for %s.',
                        'im-mail-form'
                    ),
                    $label
                );
            }
        }

        /*----------------------------------------
        文字数の検証
        ----------------------------------------*/
        $min_length = isset(
            $field['min_length']
        )
            ? absint($field['min_length'])
            : 0;

        $max_length = isset(
            $field['max_length']
        )
            ? absint($field['max_length'])
            : 0;

        $value_length = mb_strlen(
            $value
        );


        /*----------------------------------------
        最小文字数の検証
        ----------------------------------------*/
        if (
            $value !== '' &&
            $min_length > 0 &&
            $value_length < $min_length
        ) {
            $errors[$name] = sprintf(
                /* translators: 1: Form field label, 2: Minimum number of characters. */
                _n(
                    'Please enter at least %2$d character for %1$s.',
                    'Please enter at least %2$d characters for %1$s.',
                    $min_length,
                    'im-mail-form'
                ),
                $label,
                $min_length
            );
        }


        /*----------------------------------------
        最大文字数の検証
        ----------------------------------------*/
        if (
            $value !== '' &&
            $max_length > 0 &&
            $value_length > $max_length
        ) {
            $errors[$name] = sprintf(
                /* translators: 1: Form field label, 2: Maximum number of characters. */
                _n(
                    'Please enter no more than %2$d character for %1$s.',
                    'Please enter no more than %2$d characters for %1$s.',
                    $max_length,
                    'im-mail-form'
                ),
                $label,
                $max_length
            );
        }

        $submitted_data[$name] = $value;
    }

    $confirm_files = [];

    /*----------------------------------------
    検証結果を一時保持
    ----------------------------------------*/
    $GLOBALS['immf_submission'] = [
        'post_id' => $post_id,
        'data'    => $submitted_data,
        'errors'  => $errors,
    ];


    /*----------------------------------------
    確認画面用の添付ファイルを一時保存
    ----------------------------------------*/
    $confirm_files = [];

    if ($action === 'confirm') {

        foreach ($submitted_data as $name => $value) {

            // ファイル情報以外は処理しない
            if (
                !is_array($value) ||
                !isset(
                    $value['tmp_name'],
                    $value['name']
                )
            ) {
                continue;
            }

            /*
            * すでに一時保存されているファイルは
            * 現在のトークンをそのまま引き継ぐ
            */
            if (
                isset(
                    $value['transient_key'],
                    $posted_confirm_files[$name]['name'],
                    $posted_confirm_files[$name]['token']
                )
            ) {

                $confirm_files[$name] = [
                    'name' => sanitize_file_name(
                        $posted_confirm_files[$name]['name']
                    ),
                    'token' => sanitize_key(
                        $posted_confirm_files[$name]['token']
                    ),
                ];

                continue;
            }

            $tmp_name = $value['tmp_name'];

            if (
                is_string($tmp_name) &&
                $tmp_name !== '' &&
                (
                    is_uploaded_file($tmp_name) ||
                    (
                        is_file($tmp_name) &&
                        is_readable($tmp_name)
                    )
                )
            ) {
                $attachments[] = $tmp_name;
            }

            $file_name = sanitize_file_name(
                $value['name']
            );

            /*
            * wp_handle_upload()を使用するため、
            * WordPressのファイル関連関数を読み込む
            */
            if (!function_exists('wp_handle_upload')) {
                require_once ABSPATH
                    . 'wp-admin/includes/file.php';
            }

            $temp_directory = trailingslashit(
                wp_normalize_path(
                    get_temp_dir()
                )
            );

            /*
            * 確認画面の往復用ファイルは公開のアップロード
            * ディレクトリではなく、システムの一時ディレクトリへ
            * 保存するため、この呼び出しの間だけupload_dirを
            * 差し替える。
            */
            $use_temp_upload_dir =
                static function ($upload_dir) use (
                    $temp_directory
                ) {

                    $upload_dir['path'] =
                        untrailingslashit($temp_directory);

                    $upload_dir['basedir'] =
                        untrailingslashit($temp_directory);

                    $upload_dir['subdir'] = '';
                    $upload_dir['url'] = '';
                    $upload_dir['baseurl'] = '';

                    return $upload_dir;

                };

            add_filter(
                'upload_dir',
                $use_temp_upload_dir
            );

            $file_data = [
                'name'     => $file_name,
                'type'     => $value['type'] ?? '',
                'tmp_name' => $tmp_name,
                'error'    => $value['error'] ?? 0,
                'size'     => $value['size'] ?? 0,
            ];

            $uploaded_file = wp_handle_upload(
                $file_data,
                [
                    'test_form' => false,
                ]
            );

            remove_filter(
                'upload_dir',
                $use_temp_upload_dir
            );

            if (
                !is_array($uploaded_file) ||
                isset($uploaded_file['error']) ||
                empty($uploaded_file['file'])
            ) {
                $errors[$name] = __(
                    'Failed to save the attachment temporarily.',
                    'im-mail-form'
                );

                continue;
            }

            $temp_file = $uploaded_file['file'];

            $token = wp_generate_password(
                32,
                false,
                false
            );

            $transient_key =
                'immf_temp_file_'
                . $token;

            set_transient(
                $transient_key,
                [
                    'post_id' => $post_id,
                    'field_name' => $name,
                    'name' => $file_name,
                    'path' => $temp_file,
                ],
                HOUR_IN_SECONDS
            );

            wp_schedule_single_event(
                time() + HOUR_IN_SECONDS,
                'immf_cleanup_temp_file',
                [
                    $temp_file,
                    $transient_key,
                ]
            );

            $confirm_files[$name] = [
                'name' => $file_name,
                'token' => $token,
            ];
        }
    }

    /*----------------------------------------
    入力エラーがある場合は
    一時ファイル情報を保持して処理を中止
    ----------------------------------------*/
    if (!empty($errors)) {

        $GLOBALS['immf_submission'] = [
            'post_id'       => $post_id,
            'data'          => $submitted_data,
            'errors'        => $errors,
            'confirm'       => false,
            'confirm_files' => $confirm_files,
        ];

        return;
    }

    /*----------------------------------------
    修正する場合は入力画面へ戻す
    ----------------------------------------*/
    if ($action === 'back') {

        $GLOBALS['immf_submission'] = [
            'post_id'      => $post_id,
            'data'         => $submitted_data,
            'errors'       => [],
            'confirm'      => false,
            'confirm_files' => $posted_confirm_files,
        ];

        return;
    }

    /*----------------------------------------
    確認画面を表示する場合はメール送信しない
    ----------------------------------------*/
    if ($action === 'confirm') {

        $GLOBALS['immf_submission'] = [
            'post_id'        => $post_id,
            'data'           => $submitted_data,
            'errors'         => [],
            'confirm'        => true,
            'confirm_files'  => $confirm_files,
            'form_timestamp' => $form_timestamp,
        ];

        return;
    }

    /*----------------------------------------
    問い合わせ番号を生成
    ----------------------------------------*/
    $inquiry_number =
        wp_date('ymd')
        . random_int(
            100000,
            999999
        );


    /*----------------------------------------
    メール添付用のファイルパスを取得
    ----------------------------------------*/
    $attachments = [];

    foreach ($submitted_data as $value) {

        /*
        * ファイル情報以外は処理しない
        */
        if (
            !is_array($value) ||
            !isset($value['tmp_name'])
        ) {
            continue;
        }

        $tmp_name = $value['tmp_name'];

        /*
        * アップロード直後または
        * 確認画面用に一時保存したファイルを追加
        */
        if (
            is_string($tmp_name) &&
            $tmp_name !== '' &&
            (
                is_uploaded_file($tmp_name) ||
                (
                    is_file($tmp_name) &&
                    is_readable($tmp_name)
                )
            )
        ) {
            $attachments[] = $tmp_name;
        }
    }


    /*----------------------------------------
    管理者向けメール設定を取得
    ----------------------------------------*/
    $admin_to = get_post_meta(
        $post_id,
        'immf_admin_to',
        true
    );

    $admin_from_name =
        immf_sanitize_mail_header_name(
            get_post_meta(
                $post_id,
                'immf_admin_from_name',
                true
            )
        );

    $admin_from = get_post_meta(
        $post_id,
        'immf_admin_from',
        true
    );

    $admin_cc =
        immf_sanitize_email_list(
            get_post_meta(
                $post_id,
                'immf_admin_cc',
                true
            )
        );

    $admin_bcc = get_post_meta(
        $post_id,
        'immf_admin_bcc',
        true
    );

    $admin_subject = get_post_meta(
        $post_id,
        'immf_admin_subject',
        true
    );

    $admin_body = get_post_meta(
        $post_id,
        'immf_admin_body',
        true
    );

    /*----------------------------------------
    メール表示用データを生成
    ----------------------------------------*/
    $display_data = immf_get_display_data(
        $fields,
        $submitted_data
    );

    $display_data['all_fields'] =
        immf_get_all_fields_text(
            $fields,
            $display_data
        );

    $display_data['inquiry_number'] =
        $inquiry_number;

    $display_data['form_title'] =
        get_the_title($post_id);

    /*----------------------------------------
    管理者向けメール：差し込みタグを置換
    ----------------------------------------*/
    $admin_subject = immf_replace_contact_form_mail_tags(
        $admin_subject,
        $display_data
    );

    $admin_body = immf_replace_contact_form_mail_tags(
        $admin_body,
        $display_data
    );

    /*----------------------------------------
    管理者向けメールヘッダーを生成
    ----------------------------------------*/
    $admin_headers = [];

    if (
        $admin_from_name !== '' &&
        is_email($admin_from)
    ) {
        $admin_headers[] =
            'From: '
            . $admin_from_name
            . ' <'
            . $admin_from
            . '>';
    }

    if ($admin_cc !== '') {
        $admin_headers[] =
            'Cc: '
            . $admin_cc;
    }

    if ($admin_bcc !== '') {
        $admin_headers[] =
            'Bcc: '
            . $admin_bcc;
    }

    $admin_headers[] =
        'Content-Type: text/plain; charset=UTF-8';

        /*----------------------------------------
        管理者向けメールを送信
        ----------------------------------------*/
        $admin_mail_sent = wp_mail(
            $admin_to,
            $admin_subject,
            $admin_body,
            $admin_headers,
            $attachments
        );


        /*----------------------------------------
        自動返信メールの使用設定を取得
        ----------------------------------------*/
        $use_reply = get_post_meta(
            $post_id,
            'immf_use_reply',
            true
        );

        /*
        * 自動返信を使用しない場合はnull。
        * 後で送信結果の判定に使用します。
        */
        $reply_mail_sent = null;

        /*
        * 履歴保存用の初期値。
        * 自動返信を使用しない場合はこの値のまま履歴へ記録します。
        */
        $reply_log_status = 'not_sent';
        $reply_log_skip_reason = 'disabled';
        $reply_log_to = '';
        $reply_log_bcc = '';


        /*----------------------------------------
        自動返信メールを送信
        ----------------------------------------*/
        if ($use_reply === '1') {

            /*----------------------------------------
            自動返信先フィールドの指定を取得
            ----------------------------------------*/
            $reply_to_field = sanitize_key(
                get_post_meta(
                    $post_id,
                    'immf_reply_to_field',
                    true
                )
            );

            /*----------------------------------------
            自動返信先のメールアドレスを取得
            ----------------------------------------*/
            $reply_to = '';

            foreach ($fields as $field) {

                $field_name = sanitize_key(
                    $field['name'] ?? ''
                );

                $field_type = sanitize_key(
                    $field['type'] ?? ''
                );

                $field_validation = sanitize_key(
                    $field['validation'] ?? ''
                );

                if ($field_name === '') {
                    continue;
                }

                /*
                * typeまたはvalidationがemailの項目を
                * 自動返信先として使用します。
                */
                if (
                    $field_type !== 'email' &&
                    $field_validation !== 'email'
                ) {
                    continue;
                }

                /*
                * 自動返信先フィールドが指定されている場合は
                * そのフィールドのみを対象にします（既存フォームは
                * 未指定のためこの分岐を通らず、従来どおり
                * 先頭のメール項目を探索します）。
                */
                if (
                    $reply_to_field !== '' &&
                    $field_name !== $reply_to_field
                ) {
                    continue;
                }

                $email_value =
                    $submitted_data[$field_name] ?? '';

                if (
                    is_string($email_value) &&
                    is_email($email_value)
                ) {
                    $reply_to = sanitize_email(
                        $email_value
                    );

                    break;
                }

                // 指定フィールドが無効な場合は他の項目へフォールバックしない
                if ($reply_to_field !== '') {
                    break;
                }
            }


            /*----------------------------------------
            自動返信メール設定を取得
            ----------------------------------------*/
            $reply_from_name =
                immf_sanitize_mail_header_name(
                    get_post_meta(
                        $post_id,
                        'immf_reply_from_name',
                        true
                    )
                );

            $reply_from = get_post_meta(
                $post_id,
                'immf_reply_from',
                true
            );

            $reply_bcc = get_post_meta(
                $post_id,
                'immf_reply_bcc',
                true
            );

            $reply_subject = get_post_meta(
                $post_id,
                'immf_reply_subject',
                true
            );

            $reply_body = get_post_meta(
                $post_id,
                'immf_reply_body',
                true
            );

            /*----------------------------------------
            自動返信メール：差し込みタグを置換
            ----------------------------------------*/
            $reply_subject = immf_replace_contact_form_mail_tags(
                $reply_subject,
                $display_data
            );

            $reply_body = immf_replace_contact_form_mail_tags(
                $reply_body,
                $display_data
            );

            /*----------------------------------------
            自動返信メールヘッダーを生成
            ----------------------------------------*/
            $reply_headers = [];

            if (
                $reply_from_name !== '' &&
                is_email($reply_from)
            ) {
                $reply_headers[] =
                    'From: '
                    . $reply_from_name
                    . ' <'
                    . $reply_from
                    . '>';
            }

            if ($reply_bcc !== '') {
                $reply_headers[] =
                    'Bcc: '
                    . $reply_bcc;
            }

            $reply_headers[] =
                'Content-Type: text/plain; charset=UTF-8';


            /*----------------------------------------
            自動返信メールを送信
            ----------------------------------------*/
            if ($reply_to !== '') {

                $reply_mail_sent = wp_mail(
                    $reply_to,
                    $reply_subject,
                    $reply_body,
                    $reply_headers
                );

            } else {

                $reply_mail_sent = null;

            }

            /*----------------------------------------
            自動返信メールの送信結果を履歴用に記録
            ----------------------------------------*/
            if ($reply_to === '') {

                $reply_log_skip_reason = 'no_recipient';

            } else {

                $reply_log_skip_reason = '';
                $reply_log_to = $reply_to;
                $reply_log_bcc = $reply_bcc;
                $reply_log_status = $reply_mail_sent
                    ? 'success'
                    : 'failed';

            }

        }


        /*----------------------------------------
        メール送信履歴を保存
        ----------------------------------------*/
        immf_insert_mail_log([
            'form_post_id'      => $post_id,
            'form_title'        => $display_data['form_title'],
            'form_id'           => get_post_meta(
                $post_id,
                'immf_id',
                true
            ),
            'sent_at_gmt'       => current_time(
                'mysql',
                true
            ),
            'ip_address'        => immf_get_client_ip(),
            'admin_status'      => $admin_mail_sent
                ? 'success'
                : 'failed',
            'admin_to'          => $admin_to,
            'admin_cc'          => $admin_cc,
            'admin_bcc'         => $admin_bcc,
            'reply_status'      => $reply_log_status,
            'reply_skip_reason' => $reply_log_skip_reason,
            'reply_to'          => $reply_log_to,
            'reply_bcc'         => $reply_log_bcc,
        ]);


        /*----------------------------------------
        確認画面用の一時ファイルを削除
        ----------------------------------------*/
        foreach ($posted_confirm_files as $name => $confirm_file) {

            if (!is_array($confirm_file)) {
                continue;
            }

            $confirm_file_token = isset(
                $confirm_file['token']
            )
                ? sanitize_key(
                    $confirm_file['token']
                )
                : '';

            $temp_file_data =
                immf_get_temp_file_data(
                    $confirm_file_token,
                    $post_id,
                    $name
                );

            if (!$temp_file_data) {
                continue;
            }

            wp_delete_file(
                $temp_file_data['path']
            );

            delete_transient(
                $temp_file_data['transient_key']
            );
        }


        /*----------------------------------------
        メール送信結果を判定
        ----------------------------------------*/
        $mail_success =
            $admin_mail_sent === true &&
            (
                $reply_mail_sent === null ||
                $reply_mail_sent === true
            );


        /*----------------------------------------
        送信結果を一時保持
        ----------------------------------------*/
        $GLOBALS['immf_submission'] = [
            'post_id'   => $post_id,
            'data'      => $submitted_data,
            'errors'    => [],
            'success'   => $mail_success,
            'mail_error' => !$mail_success,
        ];

        /*----------------------------------------
        送信成功時は完了状態を保存してリダイレクト
        ----------------------------------------*/
        if ($mail_success) {

            $completion_token =
                wp_generate_uuid4();

            $completion_key =
                'immf_completed_'
                . $completion_token;

            set_transient(
                $completion_key,
                [
                    'post_id'          => $post_id,
                    'inquiry_number'   => $inquiry_number,
                    'display_data'     => $display_data,
                ],
                MINUTE_IN_SECONDS
            );

            /*----------------------------------------
            完了後のリダイレクト設定を取得
            ----------------------------------------*/
            $redirect_enabled = get_post_meta(
                $post_id,
                'immf_redirect_enabled',
                true
            );

            $configured_redirect_url = get_post_meta(
                $post_id,
                'immf_redirect_url',
                true
            );

            $configured_redirect_url = esc_url_raw(
                $configured_redirect_url,
                [
                    'http',
                    'https',
                ]
            );


            /*----------------------------------------
            指定URLへリダイレクト
            ----------------------------------------*/
            if (
                $redirect_enabled === '1' &&
                $configured_redirect_url !== '' &&
                filter_var(
                    $configured_redirect_url,
                    FILTER_VALIDATE_URL
                )
            ) {

                // The URL is configured by an authorized administrator and validated above.
                // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect
                wp_redirect(
                    $configured_redirect_url
                );

                exit;
            }


            /*----------------------------------------
            元ページへ戻して完了メッセージを表示
            ----------------------------------------*/
            $completion_url = add_query_arg(
                'im_mail_form_completed',
                $completion_token,
                wp_get_referer()
            );

            wp_safe_redirect(
                $completion_url
            );

            exit;
        }


/*----------------*/
}

add_action(
    'template_redirect',
    'immf_handle_contact_form_submission'
);


