xpertbot/craft-wheelform

Craft CMS 5 表单管理员,数据库集成

安装次数: 46,432

依赖项: 3

建议者: 0

安全: 0

星标: 66

关注者: 5

分支: 32

开放问题: 22

类型:craft-plugin


README

具有数据库集成的免费表单构建工具,是 Straight Up Craft 上免费联系表单插件的后继产品

Screenshot

Craft CMS 4.x

使用 composer require xpertbot/craft-wheelform "^3.2"

Craft CMS 3.x

使用 composer require xpertbot/craft-wheelform "^2.7"

要求

此插件需要 Craft CMS 5.0.0 或更高版本

安装

要安装此插件,请按照以下说明操作。

  1. 打开您的终端并转到您的 Craft 项目

     cd /path/to/project
    
  2. 然后告诉 Composer 加载插件

     composer require xpertbot/craft-wheelform
    
  3. 在控制面板中,转到设置 → 插件,并为 Wheel Form 点击“安装”按钮。

功能

  • reCaptcha 验证
  • 导出 CSV 文件
  • 自定义电子邮件 HTML 模板
  • 模板变量,便于开发
  • 基于所选字段类型的电子邮件验证
  • 表单字段类型用于部分
  • 必填字段
  • 复选框选项
  • 文件扩展名限制
  • 蜜罐字段
  • Ajax 和重定向友好
  • 将表单提交发送到多个电子邮件地址
  • 每个表单的高级权限
  • 字段重新排序
  • 将上传的文件保存到资产管理器
  • 多语言
  • 在不同网站之间导出/导入字段
  • 动态电子邮件通知

用法

安装成功后,转到插件设置并添加您希望表单发送的电子邮件 FROM。还可以设置其他有用的设置。

表单在表单面板的主设置中进行管理。设置此表单应提交到 TO 以及表单的名称。

字段设置可以设置为必填或非必填,用于验证目的。

当前支持的字段类型有

  • 文本
  • 多行文本
  • 电子邮件
  • 数字
  • 复选框
  • 单选按钮
  • 下拉列表
  • 隐藏
  • 文件
  • 列表
  • HTML

模板变量

  • wheelform

    • 设置
    • 表单
      • recaptcha
      • open()
      • close()
      • 字段
        • 类型
        • 名称
        • 标签
        • 字段类
        • 容器类
        • 必填
        • 排序
        • 选项
        • 显示标签
      • 条目
        • id
        • 表单id
        • 字段
          • 名称
          • 标签
          • 类型
        • 日期
      • getErrors()
    • 最后提交
      • id
      • 表单id
      • 字段
        • 名称
        • 标签
        • 类型
  • wheelformErrors (基于字段名、表单、reCaptcha、蜜罐的错误数组)

  • values (基于字段名的用户提交值数组)

表单配置选项

这些是可以传递给 wheelform.form 以配置您的表单的配置选项。

  • id: 必需 正在使用的表单的 ID。
  • redirect: 表单提交成功后将重定向到的 URL。
  • registerScripts: 布尔值,用于在 wheelform.open 调用之前加载脚本(这对于缓存表单和模板很有用)。默认为 False。
  • refreshCsrf:布尔值,用于加载JavaScript以刷新当前页面的表单的CSRF令牌(这对于缓存表单和模板很有用)。为了使此功能正常工作,需要将form.open()放在{% cache %}块之外。
  • submitForm:提交按钮的配置选项。示例
{# SubmitButton options:
    "type" can be button, or input
    "html" attribute takes precedence over the other properties,
    "attributes" is an easy way to add attributes to the button, all attributes are optional #}

submitButton: {
    "type": "button",
    "label": "Send", // Text displayed for the button
    "attributes": { // Array of attributes for the Button. Same as Form
        "class": "btn btn-success",
        "id": "submit-btn",
        "data-submit": "Foo",
    },
    "html": "<span><button>Custom Button</button></span>", // Custom HTML Overwrittes any other options and will render it as final.
}

Form.open()参数选项

  • action:字符串,用于覆盖表单提交的位置。如果需要使用JavaScript覆盖它,这很有用。默认为空字符串。
  • attributes:{键:值}数组,表示当前表单的属性。默认为空数组。注意:表单属性会覆盖默认值。示例
{#
    - Special Attribute: `csrf`: {boolean}, enables/disables csrf token field, disable if you would like to implement your own csrf token generation. Example:
    {{ form.open('', {
        'csrf': false,
    }) }}
        {{ craft.blitz.csrfInput() }}
        ... Rest of form template ...
#}
  • method:字符串,用于覆盖表单HTML的method属性。默认为"POST"。
{{ form.open("", {
        'novalidate': 'novalidate',
        'id':'custom-form',
        'class': 'custom-form',
    }, "POST") }}

字段服务选项

  • getFileExtensions:如果当前字段类型为"文件",可以检索在该字段上设置的当前"文件扩展名限制"。

动态电子邮件通知

  • “电子邮件主题”、“提交消息”、“用户通知主题”和“用户通知消息”表单设置可以使用动态字段值,通过在方括号中引用字段“名称”。例如,“您收到来自[email]的表单提交”其中email是您表单中字段的name

模板结构

您的表单模板可能看起来像这样

{% macro errorList(errors) %}
    {% if errors %}
        <ul class="errors">
            {% for error in errors %}
                <li>{{ error }}</li>
            {% endfor %}
        </ul>
    {% endif %}
{% endmacro %}
{% from _self import errorList %}

{% set form = wheelform.form({
    id: 1,
    redirect: 'contact/thanks'
}) %}

{{ form.open() }}
    {{ wheelformErrors['form'] is defined ? errorList(wheelformErrors['form']) }}
    {{ wheelformErrors['recaptcha'] is defined ? errorList(wheelformErrors['recaptcha']) }}
    {{ wheelformErrors['honeypot'] is defined ? errorList(wheelformErrors['honeypot']) }}

    {% for field in form.fields %}
        {{ field.render() }}
        {{ wheelformErrors[field.name] is defined ? errorList(wheelformErrors[field.name]) }}
    {% endfor %}
{{ form.close() }}

高级模板

{% macro errorList(errors) %}
    {% if errors %}
        <ul class="errors">
            {% for error in errors %}
                <li>{{ error }}</li>
            {% endfor %}
        </ul>
    {% endif %}
{% endmacro %}
{% from _self import errorList %}

{% set form = wheelform.form({
    id: 1,
    redirect: 'contact/thanks',
}) %}

{{ form.open() }}
    {{ wheelformErrors['form'] is defined ? errorList(wheelformErrors['form']) }}
    {{ wheelformErrors['recaptcha'] is defined ? errorList(wheelformErrors['recaptcha']) }}
    {{ wheelformErrors['honeypot'] is defined ? errorList(wheelformErrors['honeypot']) }}

    {% for field in form.fields %}
        {% switch field.type %}
            {% case "checkbox" %}
                <div class="form-checkbox">
                {% for item in field.items %}
                <label><input class="checkbox" type="checkbox" value="{{ item }}" {{values[field.name] is defined and item in values[field.name] ? ' checked="checked"' : '' }} name="{{field.name}}[]" id=""/>{{item}}</label>
                {% endfor %}
                </div>
            {% case "radio" %}
                <div class="form-radio">
                {% for item in field.items %}
                <input class="radio" type="radio" value="{{ item }}" {{values[field.name] is defined and item == values[field.name] ? ' checked="checked"' : '' }} name="{{field.name}}" id=""/>
                <label>{{item}}</label>
                {% endfor %}
                </div>
            {% case "select" %}
                <div class="form-select">
                <select id="wf-select" name="{{field.name}}" class="wf-field {{field.fieldClass}}">
                {% for item in field.items %}
                    <option value="{{ item }}" {{values[field.name] is defined and item == values[field.name] ? 'selected="selected"' : '' }}>{{item}}</option>
                {% endfor %}
                </select>
                </div>
            {% case "file" %}
                <div class="form-group">
                    <label>{{field.label}}</label>
                    <input type="file" name="{{field.name}}" id=""/>
                </div>
            {% case "textarea" %}
                <div class="form-group">
                    <label>{{field.label}}</label>
                    <textarea class="form-control" name="{{field.name}}" id="">{{ values[field.name] ?? '' }}</textarea>
                </div>
            {% case "list" %}
                <div class="form-group">
                    <label>{{field.label}}</label>
                    <input type="text" name="{{field.name}}[]" id=""/>
                    <script>//Javascript to handle adding fields</script>
                </div>
            {% default %}
                <div class="form-group">
                <label>{{field.label}}</label>
                <input class="form-control" type="{{field.type}}" value="{{ values[field.name] ?? '' }}" name="{{field.name}}" id=""/>
                </div>
        {% endswitch %}
        {{ wheelformErrors[field.name] is defined ? errorList(wheelformErrors[field.name]) }}
    {% endfor %}
    {% if form.recaptcha %}
        <div>
            <script src="https://www.google.com/recaptcha/api.js"></script>
            <!-- Production captcha -->
            <div class="g-recaptcha" data-sitekey="{{wheelform.getSettings('recaptcha_public')}}"></div>
        </div>
    {% endif %}

    <button class="btn btn-success" id="submit">Send</button>

</form>

如果您想坚持使用HTML而不使用变量

{% macro errorList(errors) %}
    {% if errors %}
        <ul class="errors">
            {% for error in errors %}
                <li>{{ error }}</li>
            {% endfor %}
        </ul>
    {% endif %}
{% endmacro %}

{% from _self import errorList %}

    {{ wheelformErrors['form'] is defined ? errorList(wheelformErrors['form']) }}

    <form method="post" action="" accept-charset="UTF-8" novalidate="" enctype="multipart/form-data">
    {{ csrfInput() }}
    <input type="hidden" name="action" value="wheelform/message/send">
    <input type="hidden" name="form_id" value="1">
    <input type="hidden" name="redirect" value="{{ 'contact/thanks'|hash }}">

    <h3><label for="from-name">Your Name</label></h3>
    <input id="from-name" type="text" name="name" value="{{ values['name'] ?? '' }}">
    {{ wheelformErrors['name'] is defined ? errorList(wheelformErrors['name']) }}

    <h3><label for="from-email">Your Email</label></h3>
    <input id="from-email" type="email" name="email" value="{{ values['email'] ?? '' }}">
    {{ wheelformErrors['email'] is defined ? errorList(wheelformErrors['email']) }}

    <h3><label for="phone">Phone</label></h3>
    <input id="phone" type="text" name="phone" value="{{ values['phone'] ?? '' }}">
    {{ wheelformErrors['phone'] is defined ? errorList(wheelformErrors['phone']) }}

    <label><input type="checkbox" name="favorite_topping[]" value="Chocolate">Chocolate</label>
    <label><input type="checkbox" name="favorite_topping[]" value="Vanilla">Vanilla</label>
    <label><input type="checkbox" name="favorite_topping[]" value="Strawberry">Strawberry</label>
    {{ wheelformErrors['favorite_topping'] is defined ? errorList(wheelformErrors['favorite_topping']) }}

    <h3><label for="message">Message</label></h3>
    <textarea rows="10" cols="40" id="message" name="message">{{ values['message'] ?? '' }}</textarea>
    {{ wheelformErrors['message'] is defined ? errorList(wheelformErrors['message']) }}

    <input type="file" name="user_file" id="user_filer">

    {# if using recaptcha settings #}
    <div class="recaptcha-container">
        <script src="https://www.google.com/recaptcha/api.js"></script>
        <div class="g-recaptcha" data-sitekey="{{ site_key }}">
        </div>
    </div>

    <input type="submit" value="Send">
</form>

提交后重定向

如果您有一个隐藏的'重定向'输入,则在成功发送电子邮件后,用户将被重定向到该输入。

请注意,如果您没有包含redirect输入,当前页面将被重新加载。

显示闪存消息

当提交联系表单时,插件将在用户会话上设置一个success闪存消息。这样做是为了在表单提交后显示成功消息,并且可以在重定向页面上显示。可用的闪存变量是:wheelformSubmittedForm - 提交表单的ID wheelformSuccess - 要显示的消息。您可以在模板中这样显示它

{% if craft.app.session.hasFlash('wheelformSuccess') %}
    <p class="message success">{{ craft.app.session.getFlash('wheelformSuccess') }}</p>
{% endif %}

表单字段类型

您可以将字段类型分配给用户可以从中选择要显示的表单的分区。此字段将返回一个wheelform.form模板服务(与其他示例相同),它属于在管理员面板上选择的表单。如果您需要自定义它,可以使用setConfig变量来修改默认行为。

{% set form = entry.formField.setConfig({
    redirect: 'contact/thanks',
    attributes: {
        'novalidate':"novalidate",
        'id':'field-form',
        'class': 'field-form',
    },
}) %}

显示最后/当前提交

类似于闪存消息(仅在提交后可用),当提交联系表单时,插件会将提交表单的值存储在会话中,并通过wheelform.lastSubmission变量提供一次。您可以在模板中使用它(通常在重定向页面上)如下所示

{% set submission = wheelform.lastSubmission %}
{% if submission %}
    <dl>
        {% for field in submission.fields %}
            <dt>{{ field.label }}</dt>
            <dd>{{ field.value }}</dd>
        {% endfor %}
    </dl>
{% endif %}

您还可以使用submission.idsubmission.formIdsubmission.date(注意:submission.date是DateTime对象,通过date()过滤器运行)。

Recaptcha V3

{# You need to add the Recaptcha Init before the form.open() #}
{# action is optional, action defaults to form URL #}
{{ wheelform.recaptchaV3({'action': 'contact-form'})}}

显示现有表单提交

您可以通过form.entries属性访问表单上的现有提交表单条目。

{% set form = wheelform.form({ id: 1 }) %}

{# form.entries(start, limit) can be used for pagination purposes #}
{% set entries = form.entries %}

{# form.fields returns active form fields #}
{% set fields = form.fields %}

<table>
<thead>
    {% for field in fields %}
        <th>{{ field.label }}</th>
    {% endfor %}
    <th>Date</th>
</thead>
<tbody>
    {% for entry in entries %}
        <tr data-id="{{ entry.id }}">
            {% for field in fields %}
                {% set current = entry.fields[field.name] %}
                <td data-id="{{ current.name }}">{{ current.value }}</td>
            {% endfor %}
            <td data-id="date">{{ entry.date|date("m/d/Y") }}</td>
        </tr>
    {% endfor %}
</tbody>

显示特定表单提交

您可以通过加载表单并按ID请求它来访问特定表单条目。

{% set form = wheelform.form({ id: 1 }) %}
{% set entry = form.entry(id) %}
{% for field in entry.fields %}
    <p>{{ field.value }}</p>
{% endfor %}

文件附件

如果您希望您的表单接受文件附件,请按照以下步骤操作

  1. 确保您的打开HTML <form>标签包含enctype="multipart/form-data"
  2. 在您的表单中添加一个<input type="file" name="{field_name}">

将文件保存到资产文件夹

在插件设置中选择您希望保存文件的文件夹。确保已打开“允许公共URL”选项。

Ajax表单提交

您可以选择通过Ajax发布联系表单提交。只需向您的站点发送包含所有常规数据的POST请求即可

JQuery

$('#my-form').submit(function(ev) {
    // Prevent the form from actually submitting
    ev.preventDefault();

    var data = $(this).serialize();

    // Send it to the server
    $.post('/wheelform/message/send',
        data,
        function(response) {
            if (response.success) {
                //reponse.message is the message saved in the Form Settings
                $('#thanks').fadeIn();
            } else {
                // response.values will contain user submitted values
                // response.errors will be an array containing any validation errors that occurred, indexed by field name
                // e.g. response.error['email'] => ['Email is required', 'Email is invalid']
                alert('An error occurred. Please try again.');
            }
        }
    );
});

使用 AxiosBabel 以及 Webpack 进行基本的 JavaScript 编程

import axios from 'axios';

const contactForm = document.getElementById('contact-form');

if (contactForm) {
  contactForm.addEventListener('submit', function(event) {
    /**
     * This prevents the default behaviour of the browser submitting
     * the form so that we can handle things instead.
     */
    event.preventDefault();

    /**
     * This gets the element which the event handler was attached to.
     *
     * @see https://mdn.org.cn/en-US/docs/Web/API/Event/currentTarget
     */
    const form = event.currentTarget;

    /**
      * Hidden field named action
      */
    const url = form.action.value;

    /**
     * This takes all the fields in the form and makes their values
     * available through a `FormData` instance.
     *
     * @see https://mdn.org.cn/en-US/docs/Web/API/FormData
     */
    const formData = new FormData(form);

    axios({
      method: 'post',
      url: url,
      responseType: 'json',
      headers: {'X-Requested-With': 'XMLHttpRequest'},
      data: formData,
    })
    .then(function (response) {
      console.log(response);
    })
    .catch(function (error) {
      console.log(error);
    });
  });
}

如果使用 getCrsfInput(),请确保您将其与表单的其余部分一起提交。

使用 Ajax 的 Recaptcha

如果您使用 Ajax 实现Recaptcha,请注意,在每次 Ajax 请求之后都需要刷新 recaptcha 令牌,以避免无效的 recaptcha 验证错误(请参阅问题 242)。

使用 Fetch API 的示例

const myRequest = new Request('endpoint.json', {
    method: 'POST', // any allowed Method
    param: value
});
fetch(myRequest)
.then((response) => response.json())
.then((data) => {
    // Use respose `data` here

    // Refresh Recaptcha token
    wheelformProcessRecaptchaCallback()
});

CSRF 元标签

如果您提交 Ajax 请求且需要在 head 元素中包含元标签,可以使用 {{ wheelform.metaTags() }} 生成适当的 CSRF 值。

权限

有 4 种不同的权限类型({表单名称} 权限在每个表单上重复)

  • 创建新表单 - 允许用户/组看到“新建表单”按钮并创建新表单。
  • 编辑 {表单名称} - 允许用户/组在表单列表中看到表单。
    • {表单名称} 条目 - 允许用户/组查看条目列表。
    • {表单名称} 设置 - 允许用户/组编辑表单设置。

表单工具

  • CSV 导出器可以根据条目日期进行,在 Admin > 工具 > 表单工具下。
  • 可以将表单字段导出为 JSON 文件。
  • 可以从有效的 JSON 文件导入表单字段。

删除消息的 Cron 任务

您可以在服务器上安排一个 Cron 任务,每天运行一次,检查并删除数据库中保存的任何旧消息值。Cron 任务命令是:php var/www/yourwebsite/craft wheelform/message/purge 其中 var\www\yourwebsite\craft 是 craft 可执行包的路径。

config\wheelform.php 中需要进行的唯一配置是

return [
    'purgeMessages' => true, //True / False value to allow Cron Job to go
    'purgeMessagesDays' => 30, //Number of days messages should live in your database
];

注意:此操作不可撤销。

每个表单的“来自”地址自定义

根据每个表单更改“来自”地址,这将覆盖表单设置中设置的一般电子邮件。

  1. 在 Craft 的配置文件夹内创建 wheelform.php 文件。
  2. 添加一个带有键 formsarray,该 ID 是您想要覆盖的表单的 ID 作为键,然后是一个带有键 from 的数组。以下是一个示例
return [
    'forms' => [
        // ID of form
        3 => [
            // This is the email to use for this specific form
            'from' => 'form3@example.com',
        ],
        5 => [
            // This can also be an array with a custom name
            'from' => [
                'form5@example.com' => "Form 5 Custom Name",
            ]
        ]
    ]
];

自定义电子邮件模板

电子邮件模板是可选的。可以使用以下步骤使用自定义 Twig 模板

  1. 在 Craft 的配置文件夹内创建 wheelform.php 文件。
  2. wheelform.php 预期返回一个配置设置数组。选项包括
    • template:用于所有电子邮件的默认模板。
    • notification:默认通知模板覆盖。
    • forms:是一个数组,覆盖特定于表单的任何设置。数组的键是要修改的表单的 ID。(这些设置优先于其他任何内容)(注意:此 forms 键与上面自定义“来自”地址的相同)
return [
    'template' => '_emails/custom',
    'notification' => [
        'template' => '_emails/notification',
        'subject' => 'Default Notification Subject',
    ],
    'forms' => [
        1 => [
            'template' => '_emails/form1_template',
            'notification' => [
                'template' => '_emails/notification2',
                'subject' => 'Form specific Subject',
            ],
        ],
        3 => [
            'template' => '_emails/form3_template',
        ],
    ],
];
  1. 在您的模板中,您将能够访问一个 fields 数组和 notification_message,其中包含在表单管理面板上设置的消息。以下是一个示例
<html>
<body>
    <h1>Custom Template</h1>

    <ul>
    {% for field in fields %}
        <li>
        <strong>{{ field.label }}:</strong>
        {% switch field.type %}

            {% case "file" %}
                {# This is an object with file attributes #}
                {{ field.value.name }}

            {% case "checkbox" %}
                {# Array of all choices selected #}
                {{ field.value | join(',')}}
            {% case "list" %}
                {% if field.value %}
                    <ul>
                    {% for item in field.value %}
                        <li>{{ item }}</li>
                    {% endfor %}
                    </ul>
                {% endif %}

            {% default %}

                {# Text based items #}
                {{ field. value }}

        {% endswitch %}
        </li>
    {% endfor %}
    </ul>
</body>
</html>

跳过将文件附加到电子邮件中

wheelform.php 中,每个表单都可以有一个标志来跳过附加电子邮件

return [
    'forms' => [
        1 => [
            'skip_attachments' => true, // True/False value, leave empty if not needed.
        ],
    ],
];

蜜罐字段

Honeypot 字段是一个旨在由人类留空的字段。通常通过 CSS 隐藏。有关 Honeypot 字段的更多信息请参阅 此处

如果您不使用 {{ form.close() }} 辅助标签,请确保您添加一个与创建表单时使用的名称相同名称的文本字段。然后,使用 CSS 或 JavaScript 隐藏它。

渲染 Honeypot(可选)

如果您需要完全控制蜜罐字段的行为。您可以通过调用 {{ form.honeypot($type, $attributes = [], $returnString = false) }} 来实现,其中

  • $type 可以是三种类型之一: textpasswordhidden
  • $attributes:(可选)字段的选项数组。例如:{'autocomplete': 'none', 'class': 'comments-field'}
  • $returnString:(可选)这将返回字段的 HTML 字符串以进行进一步操作,而不是模板准备实体。

事件

(注意:这主要面向了解基本 PHP 和 Composer 包的开发者)

beforeValidate 事件,允许开发者在基本插件验证之前验证值并添加自定义错误。跳过插件验证,这个事件发生在基本插件验证之前。

afterValidate 事件,允许开发者在基本插件验证之后验证消息并调整值和错误,这些更改将级联到其他事件和邮件发送者。

beforeSave 事件,允许开发者在将活动记录对象保存到数据库之前修改其值,这些更改将级联到其他事件和邮件发送者。

beforeSend 事件,允许开发者在电子邮件中修改要发送的字段,此事件不会修改在数据库中输入的值。仅修改发送给客户端的字段。

afterSend 事件,最终发送给用户电子邮件的值,非常适合第三方集成和库。

beforeResponse 事件,修改提交的响应,是添加自定义标题、动态成功消息或前端自定义数据的好地方。

您还可以触发其他自定义功能,例如收集自定义字段值以添加到第三方服务,例如邮件列表。

beforeSendafterSend 对象如下所示: Event 类属性

  • form_id - 正在提交的当前表单的 ID。这允许开发者以某种方式检查正在发送的字段。
  • subject - 当前表单的主题。这可以修改为使其可定制。
  • message - 关联数组,包含提交的不同字段及其值。
  • from - 发送消息的电子邮件地址。
  • to - 发送消息的电子邮件地址(这可以是一个多个电子邮件的数组)。
  • reply_to - 消息可以回复的电子邮件地址。
  • email_html - 将在电子邮件中发送的完整 HTML 字符串。这将覆盖其他电子邮件模板。
  • saveMessage - 允许消息在数据库中保存(默认:True)。
  • sendMessage - 允许发送消息(默认:True)。

处理这些事件的示例插件。 wheelformhelper

翻译

可以使用翻译文件夹内的格式提交新的翻译。 (我将尽可能保持“es”翻译的最新状态,这可以成为您翻译的良好起点)