chore: create production-v2 branch with content only
This branch contains ONLY: - Pages (config/www/user/pages/) - Themes (config/www/user/themes/) - Plugins (config/www/user/plugins/) - PRODUCTION.md - Minimal .gitignore Clean slate for production deployment. All development files, configs, scripts removed.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
{% include 'partials/form-messages.html.twig' %}
|
||||
{% do http_response_code(form.responseCode) %}
|
||||
@@ -0,0 +1,2 @@
|
||||
{% include 'partials/form-messages.json.twig' %}
|
||||
{% do http_response_code(form.responseCode) %}
|
||||
@@ -0,0 +1 @@
|
||||
{% extends "forms/default/form.html.twig" %}
|
||||
8
config/www/user/plugins/form/templates/form.html.twig
Normal file
8
config/www/user/plugins/form/templates/form.html.twig
Normal file
@@ -0,0 +1,8 @@
|
||||
{% extends 'partials/base.html.twig' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{{ content|raw }}
|
||||
{% include "forms/form.html.twig" %}
|
||||
|
||||
{% endblock %}
|
||||
1
config/www/user/plugins/form/templates/form.json.twig
Normal file
1
config/www/user/plugins/form/templates/form.json.twig
Normal file
@@ -0,0 +1 @@
|
||||
{% extends 'forms/ajax.json.twig' %}
|
||||
20
config/www/user/plugins/form/templates/formdata.html.twig
Normal file
20
config/www/user/plugins/form/templates/formdata.html.twig
Normal file
@@ -0,0 +1,20 @@
|
||||
{% extends 'partials/base.html.twig' %}
|
||||
|
||||
{% if form is null %}
|
||||
{% set form = grav.session.getFlashObject('form') %}
|
||||
{% endif %}
|
||||
|
||||
{% block content %}
|
||||
{{ content|raw }}
|
||||
|
||||
{% if form %}
|
||||
{% include 'partials/form-messages.html.twig' %}
|
||||
|
||||
<p>{{ 'PLUGIN_FORM.DATA_SUMMARY'|t }}</p>
|
||||
|
||||
{% include "forms/data.html.twig" %}
|
||||
{% else %}
|
||||
<div class="notices warning yellow"><p>{{ 'PLUGIN_FORM.NO_FORM_DATA'|t }}</p></div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,5 @@
|
||||
{% if form_json_response %}
|
||||
{{ form_json_response|json_encode|raw }}
|
||||
{% else %}
|
||||
{}
|
||||
{% endif %}
|
||||
@@ -0,0 +1 @@
|
||||
{% extends "forms/default/data.html.twig" %}
|
||||
@@ -0,0 +1 @@
|
||||
{% extends "forms/default/data.txt.twig" %}
|
||||
@@ -0,0 +1,83 @@
|
||||
{% macro render_field(form, fields, scope) %}
|
||||
{% import _self as self %}
|
||||
|
||||
{% for index, field in fields %}
|
||||
{%- set show_field = attribute(field, "input@") ?? field.store ?? true %}
|
||||
{% if field.fields %}
|
||||
{%- set new_scope = field.nest_id ? scope ~ field.name ~ '.' : scope -%}
|
||||
{{- self.render_field(form, field.fields, new_scope) }}
|
||||
{% else %}
|
||||
{% if show_field %}
|
||||
{%- set value = form.value(scope ~ (field.name ?? index)) -%}
|
||||
{% if value %}
|
||||
{% block field %}
|
||||
<div>
|
||||
{% block field_label %}
|
||||
<strong>
|
||||
{%- if field.markdown -%}
|
||||
{{ field.data_label ? field.data_label|t|e|markdown(false) : field.label|t|emarkdown(false) }}
|
||||
{%- else -%}
|
||||
{{ field.data_label ? field.data_label|t|e : field.label|t|e }}
|
||||
{%- endif -%}
|
||||
</strong>:
|
||||
{% endblock %}
|
||||
|
||||
{% block field_value %}
|
||||
{% if field.type == 'checkboxes' %}
|
||||
<ul>
|
||||
{% set use_keys = field.use is defined and field.use == 'keys' %}
|
||||
{% for key,value in form.value(scope ~ field.name) %}
|
||||
{% set index = (use_keys ? key : value) %}
|
||||
<li>{{ field.options[index]|t|e }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% elseif field.type == 'radio' %}
|
||||
{% set value = form.value(scope ~ field.name) %}
|
||||
{{ field.options[value]|t|e }}
|
||||
{% elseif field.type == 'checkbox' %}
|
||||
{{ (form.value(scope ~ field.name) == 1) ? "GRAV.YES"|t|e : "GRAV.NO"|t|e }}
|
||||
{% elseif field.type == 'select' %}
|
||||
{% set value = form.value(scope ~ field.name) %}
|
||||
{% if value is iterable %}
|
||||
<ul>
|
||||
{% set use_keys = field.use is defined and field.use == 'keys' %}
|
||||
{% for key, val in value %}
|
||||
{% set index = (use_keys ? key : val) %}
|
||||
<li>{{ field.options[index]|t|e }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
{{ field.options[value]|t|e }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% set value = form.value(scope ~ field.name) %}
|
||||
{% if value is iterable %}
|
||||
<ul>
|
||||
{% for val in value %}
|
||||
{% if val is iterable %}
|
||||
<ul>
|
||||
{% for v in val %}
|
||||
<li>{{ string(v)|e }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<li>{{ string(val)|e }}</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
{{ string(value)|e|nl2br }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
|
||||
{% import _self as macro %}
|
||||
|
||||
{{ macro.render_field(form, form.fields, '') }}
|
||||
@@ -0,0 +1,21 @@
|
||||
{%- macro render_field(form, fields, scope) %}
|
||||
{%- import _self as self %}
|
||||
{%- for index, field in fields %}
|
||||
{%- set show_field = attribute(field, "input@") ?? field.store ?? true %}
|
||||
{%- if field.fields %}
|
||||
{%- set new_scope = field.nest_id ? scope ~ field.name ~ '.' : scope -%}
|
||||
{{- self.render_field(form, field.fields, new_scope) }}
|
||||
{%- else %}
|
||||
{%- if show_field %}
|
||||
{%- set value = form.value(scope ~ (field.name ?? index)) -%}
|
||||
{%- if value -%}
|
||||
{{- field.data_label ? field.data_label|t|e : field.label|t|e }}: {{ string(value is iterable ? value|json_encode : value) ~ "\n" }}
|
||||
{%- endif -%}
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
{%- endmacro %}
|
||||
{%- import _self as macro %}
|
||||
{%- autoescape false %}
|
||||
{{- macro.render_field(form, form.fields, '') ~ "\n" }}
|
||||
{%- endautoescape %}
|
||||
@@ -0,0 +1,158 @@
|
||||
{% if not field.validate.ignore %}
|
||||
|
||||
{% use 'forms/layouts/field-variables.html.twig' %}
|
||||
{% block field_override_variables_before %}{% endblock %}
|
||||
|
||||
{% set field_name = (scope ~ field.name)|fieldName %}
|
||||
{% set vertical = field.style == 'vertical' %}
|
||||
|
||||
{% if not blueprints or (blueprints.schema.type(field.type)['input@'] ?? true) is same as(true) %}
|
||||
{% set default = field.default %}
|
||||
{% set toggleable = field.toggleable ?? false %}
|
||||
{% if toggleable %}
|
||||
{% set originalValue = originalValue ?? value %}
|
||||
{% set toggleableChecked = originalValue is not null %}
|
||||
{% elseif field.overridable %}
|
||||
{% set toggleable = true %}
|
||||
{% set default = form.getDefaultValue(field.name) ?? default %}
|
||||
{% set toggleableChecked = value is not null and value != default %}
|
||||
{% endif %}
|
||||
|
||||
{% set cookie_name = 'forms-' ~ form.name ~ '-' ~ field.name %}
|
||||
{% set value = value ?? get_cookie(cookie_name) %}
|
||||
{% set has_value = value is not same as(null) %}
|
||||
{% if not has_value %}
|
||||
{% set value = default %}
|
||||
{% endif %}
|
||||
|
||||
{% if (field.yaml or field.validate.type == 'yaml') and value is iterable %}
|
||||
{% set value = value|yaml %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% set toggleable = false %}
|
||||
{% endif %}
|
||||
|
||||
{# DEPRECATED: Needed by old form fields; remove when backwards compatibility breaks are allowed #}
|
||||
{% set isDisabledToggleable = toggleable and not toggleableChecked %}
|
||||
|
||||
{% if toggleable %}
|
||||
{% set form_field_toggleable %}
|
||||
{% include 'forms/default/toggleable.html.twig' with {checked: toggleableChecked} %}
|
||||
{% endset %}
|
||||
{% endif %}
|
||||
|
||||
{% set errors = attribute(form.messages, field.name) %}
|
||||
{% set required = client_side_validation and field.validate.required in ['on', 'true', 1] %}
|
||||
{% set autofocus = (inline_errors == false) and field.autofocus in ['on', 'true', 1] %}
|
||||
|
||||
{% if inline_errors and errors %}
|
||||
{% set autofocus = true %}
|
||||
{% endif %}
|
||||
|
||||
{% set embed_outer_field_classes %}
|
||||
{% block outer_field_classes %}{% endblock %}
|
||||
{% endset %}
|
||||
|
||||
{# Field Classes #}
|
||||
{%- if errors %}{% set form_field_outer_core = form_field_outer_core ~ ' has-errors' %}{% endif -%}
|
||||
{%- if toggleable %}{% set form_field_outer_core = form_field_outer_core ~ ' form-field-toggleable' %}{% endif -%}
|
||||
|
||||
{% set layout_form_field_outer_classes = field.outerclasses %}
|
||||
{% set layout_form_field_outer_classes = layout_form_field_outer_classes|trim ~ ' ' ~ form_field_outer_classes %}
|
||||
{% set layout_form_field_outer_classes = layout_form_field_outer_classes|trim ~ ' ' ~ embed_outer_field_classes %}
|
||||
|
||||
{# Show Label logic #}
|
||||
{% set show_label = field.label is not same as(false) and field.display_label is not same as(false )%}
|
||||
|
||||
{# Label Classes #}
|
||||
{% set layout_form_field_outer_label_classes = ((form_field_outer_label_classes ?: 'form-label') ~ ' ' ~ field.labelclasses)|trim %}
|
||||
{% set layout_form_field_label_classes = (form_field_label_classes ?: 'inline')|trim %}
|
||||
{% set form_field_label_trim = toggleable ? 'toggleable' %}
|
||||
|
||||
{# Field Outer Data classes #}
|
||||
{% set layout_form_field_outer_data_classes = ((form_field_outer_data_classes ?: ' form-data') ~ ' ' ~ field.dataclasses)|trim %}
|
||||
|
||||
{# Field Wrapper classes #}
|
||||
{% set layout_form_field_wrapper_classes = ((form_field_wrapper_classes ?: ' form-input-wrapper') ~ ' ' ~ field.wrapper_classes)|trim %}
|
||||
|
||||
{# Field input classes #}
|
||||
{% if field|of_type('array') %}
|
||||
{% if field.classes %}
|
||||
{% set field = field|merge({'classes': field.classes ~ ' ' ~ block('field_input_classes')|trim }) %}
|
||||
{% else %}
|
||||
{% set field = field|merge({'classes': block('field_input_classes') }) %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% set layout_form_field_input_classes = (form_field_input_classes ~ ' ' ~ field.classes)|trim %}
|
||||
|
||||
{# Inline error classes #}
|
||||
{% set form_field_inline_error_classes = form_field_inline_error_classes ?: ' form-errors' %}
|
||||
|
||||
{# Field extra classes #}
|
||||
{% set form_field_extra_wrapper_classes = 'form-extra-wrapper ' ~ field.wrapper_classes %}
|
||||
|
||||
{# Field For #}
|
||||
{% set form_field_for = toggleable ? 'toggleable_' ~ field.name : field.id|e %}
|
||||
|
||||
{# Field Label #}
|
||||
{% set form_field_label = field.markdown ? field.label|markdown(false) : field.label %}
|
||||
{% set form_field_label = form_field_label|default(field.name|capitalize)|t %}
|
||||
|
||||
{# Field Help #}
|
||||
{% if field.help %}
|
||||
{% set form_field_help = field.markdown ? field.help|t|markdown(false)|e : field.help|t|e %}
|
||||
{% endif %}
|
||||
|
||||
{# Field Requied #}
|
||||
{% set form_field_required = field.validate.required in ['on', 'true', 1] ? true : false %}
|
||||
|
||||
{# Field Description #}
|
||||
{% set form_field_description = field.markdown ? field.description|t|markdown(false)|raw : field.description|t|raw %}
|
||||
|
||||
{% extends 'forms/layouts/field.html.twig' %}
|
||||
|
||||
{% block global_attributes %}
|
||||
data-grav-field="{{ field.type }}"
|
||||
data-grav-disabled="{{ toggleable and toggleableChecked }}"
|
||||
data-grav-default="{{ default|json_encode()|e('html_attr') }}"
|
||||
{% endblock %}
|
||||
|
||||
{% block input_attributes %}
|
||||
class="{{ layout_form_field_input_classes|trim }} {{ field.size }}"
|
||||
{% if field.id is defined %}id="{{ field.id|e }}" {% endif %}
|
||||
{% if field.style is defined %}style="{{ field.style|e }}" {% endif %}
|
||||
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
|
||||
{% if field.placeholder %}placeholder="{{ field.placeholder|t|e('html_attr') }}"{% endif %}
|
||||
{% if autofocus %}autofocus="autofocus"{% endif %}
|
||||
{% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %}
|
||||
{% if field.readonly in ['on', 'true', 1] %}readonly="readonly"{% endif %}
|
||||
{% if field.autocomplete is defined %}autocomplete="{{ field.autocomplete }}"{% endif %}
|
||||
{% if field.autocapitalize in ['off', 'characters', 'words', 'sentences'] %}autocapitalize="{{ field.autocapitalize }}"{% endif %}
|
||||
{% if field.inputmode in ['none', 'text', 'decimal', 'numeric', 'tel', 'search', 'email', 'url'] %}inputmode="{{ field.inputmode }}"{% endif %}
|
||||
{% if field.tabindex %}tabindex="{{ field.tabindex }}"{% endif %}
|
||||
{% if field.spellcheck in ['true', 'false'] %}spellcheck="{{ field.spellcheck }}"{% endif %}
|
||||
{% if required %}required="required"{% endif %}
|
||||
{% if field.validate.pattern %}pattern="{{ field.validate.pattern|e }}"{% endif %}
|
||||
{% if field.validate.message %}title="{{ field.validate.message|t|e }}"
|
||||
{% elseif field.title is defined %}title="{{ field.title|t|e }}" {% endif %}
|
||||
|
||||
{# Support key/value and .name/.value styles #}
|
||||
{% if field.attributes is defined %}
|
||||
{% for key,attribute in field.attributes %}
|
||||
{% if attribute|of_type('array') %}
|
||||
{{ attribute.name }}="{{ attribute.value|e('html_attr') }}"
|
||||
{% else %}
|
||||
{{ key }}="{{ attribute|e('html_attr') }}"
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{# Support for Custom data attributes#}
|
||||
{% if field.datasets %}
|
||||
{% for key, attribute in field.datasets %}
|
||||
data-{{ key }}="{{ attribute|e('html_attr') }}"
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% endif %}
|
||||
@@ -0,0 +1,17 @@
|
||||
{% set fields = prepare_form_fields(fields, name) %}
|
||||
{% set originalValue = null %}
|
||||
{% if fields|length %}
|
||||
{% block outer_markup_field_open %}{% endblock %}
|
||||
{% for field_name, field in fields %}
|
||||
{% set value = form ? form.value(field.name) : data.value(field.name) %}
|
||||
{% set field_templates = include_form_field(field.type, field_layout, fallback_field ?? 'text') %}
|
||||
{% block inner_markup_field_open %}{% endblock %}
|
||||
{% block field %}
|
||||
{% include field_templates %}
|
||||
{% endblock %}
|
||||
{% block inner_markup_field_close %}{% endblock %}
|
||||
{% endfor %}
|
||||
{% block outer_markup_field_close %}{% endblock %}
|
||||
{% else %}
|
||||
{% block empty_fields_markup %}{% endblock %}
|
||||
{% endif %}
|
||||
@@ -0,0 +1,211 @@
|
||||
{% block xhr %}{% endblock %}
|
||||
{% set form = form ?? grav.session.getFlashObject('form') %}
|
||||
{% set layout = layout ?? form.layout ?? 'default' %}
|
||||
{% set field_layout = field_layout ?? layout %}
|
||||
|
||||
<div id="{{ form.id }}-wrapper" class="form-wrapper">
|
||||
{# Keep here for Backwards Compatibility #}
|
||||
{% include 'partials/form-messages.html.twig' %}
|
||||
|
||||
{% set scope = scope ?: form.scope is defined ? form.scope : 'data.' %}
|
||||
{% set multipart = '' %}
|
||||
{% set blueprints = blueprints ?? form.blueprint() %}
|
||||
{% set method = form.method|upper|default('POST') %}
|
||||
{% set client_side_validation = form.client_side_validation is not null ? form.client_side_validation : config.plugins.form.client_side_validation|defined(true) %}
|
||||
{% set inline_errors = form.inline_errors is not null ? form.inline_errors : config.plugins.form.inline_errors(false) %}
|
||||
|
||||
{% set data = data ?? form.data %}
|
||||
{% set context = context ?? data %}
|
||||
|
||||
{% for field in form.fields %}
|
||||
{% if (method == 'POST' and field.type == 'file') %}
|
||||
{% set multipart = ' enctype="multipart/form-data"' %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% set action = action ?? (form.action ?: page.route ~ uri.params) %}
|
||||
{% set action = (action starts with 'http') or (action starts with '#') ? action : base_url ~ action %}
|
||||
{% set action = action|trim('/', 'right') %}
|
||||
|
||||
{% if (action == base_url_relative) %}
|
||||
{% set action = base_url_relative ~ '/' %}
|
||||
{% endif %}
|
||||
|
||||
{% if form.keep_alive %}
|
||||
{% if grav.browser.browser == 'msie' and grav.browser.version < 12 %}
|
||||
{% do assets.addJs('plugin://form/assets/object.assign.polyfill.js') %}
|
||||
{% endif %}
|
||||
{% do assets.addJs('plugin://form/assets/form.vendor.js', { 'group': 'bottom', 'loading': 'defer' }) %}
|
||||
{% do assets.addJs('plugin://form/assets/form.min.js', { 'group': 'bottom', 'loading': 'defer' }) %}
|
||||
{% endif %}
|
||||
|
||||
{% do assets.addInlineJs("
|
||||
window.GravForm = window.GravForm || {};
|
||||
window.GravForm.config = {
|
||||
current_url: '" ~ grav.route.withoutParams().toString(true) ~"',
|
||||
current_params: " ~ grav.route.params|json_encode ~ ",
|
||||
param_sep: '" ~ config.system.param_sep ~ "',
|
||||
base_url_relative: '" ~ base_url_relative ~ "',
|
||||
form_nonce: '" ~ form.getNonce() ~ "',
|
||||
session_timeout: " ~ config.system.session.timeout ~ "
|
||||
};
|
||||
window.GravForm.translations = Object.assign({}, window.GravForm.translations || {}, { PLUGIN_FORM: {} });
|
||||
", {'group': 'bottom', 'position': 'before', 'priority': 100}) %}
|
||||
|
||||
{# Backwards Compatibility for block overrides #}
|
||||
{% set override_form_classes %}
|
||||
{% block form_classes -%}
|
||||
{{ form_outer_classes }} {{ form.classes }}
|
||||
{%- endblock %}
|
||||
{% endset %}
|
||||
|
||||
{% set override_inner_markup_fields_start %}
|
||||
{% block inner_markup_fields_start %}{% endblock %}
|
||||
{% endset %}
|
||||
|
||||
{% set override_inner_markup_fields_end %}
|
||||
{% block inner_markup_fields_end %}{% endblock %}
|
||||
{% endset %}
|
||||
|
||||
{% set override_inner_markup_fields %}
|
||||
{% block inner_markup_fields %}
|
||||
{% for field_name, field in form.fields %}
|
||||
{% set field = prepare_form_field(field, field_name) %}
|
||||
{% if field %}
|
||||
{% set value = form ? form.value(field.name) : data.value(field.name) %}
|
||||
{% set field_templates = include_form_field(field.type, field_layout) %}
|
||||
|
||||
{% block inner_markup_field_open %}{% endblock %}
|
||||
{% block field %}
|
||||
{% include field_templates ignore missing %}
|
||||
{% endblock %}
|
||||
{% block inner_markup_field_close %}{% endblock %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
{% endset %}
|
||||
|
||||
{% set override_inner_markup_buttons_start %}
|
||||
{% block inner_markup_buttons_start %}
|
||||
<div class="{{ form_button_outer_classes ?: 'buttons'}}">
|
||||
{% endblock %}
|
||||
{% endset %}
|
||||
|
||||
{% set override_inner_markup_buttons_end %}
|
||||
{% block inner_markup_buttons_end %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endset %}
|
||||
|
||||
{# Embed for HTML layout #}
|
||||
{% embed 'forms/layouts/form.html.twig' %}
|
||||
|
||||
{% block embed_form_core %}
|
||||
name="{{ form.name }}"
|
||||
action="{{ action }}"
|
||||
method="{{ method }}"{{ multipart|raw }}
|
||||
id="{{ form.id|default(form.name|hyphenize) }}"
|
||||
{% if form.novalidate %}novalidate{% endif %}
|
||||
{% if form.xhr_submit %}data-xhr-enabled="true"{% endif %}
|
||||
{% if form.keep_alive %}data-grav-keepalive="true"{% endif %}
|
||||
{% if form.attributes is defined %}
|
||||
{% for key,attribute in form.attributes %}
|
||||
{% if attribute|of_type('array') %}
|
||||
{{ attribute.name }}="{{ attribute.value|e('html_attr') }}"
|
||||
{% else %}
|
||||
{{ key }}="{{ attribute|e('html_attr') }}"
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block embed_form_classes -%}
|
||||
class="{{ parent() }} {{ override_form_classes|trim }}"
|
||||
{%- endblock %}
|
||||
|
||||
{% block embed_form_custom_attributes %}
|
||||
{% for k, v in blueprints.form.attributes %}
|
||||
{{ k }}="{{ v|e }}"
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block embed_fields %}
|
||||
{{ override_inner_markup_fields_start|raw }}
|
||||
{{ override_inner_markup_fields|raw }}
|
||||
|
||||
{% if form.isEnabled() ?? true %}
|
||||
{% include include_form_field('formname', field_layout, 'hidden') %}
|
||||
{% include include_form_field('formtask', field_layout, 'hidden') %}
|
||||
{% include include_form_field('uniqueid', field_layout, 'hidden') %}
|
||||
{% include include_form_field('nonce', field_layout, 'hidden') %}
|
||||
{% endif %}
|
||||
|
||||
{{ override_inner_markup_fields_end|raw }}
|
||||
{% endblock %}
|
||||
|
||||
{% block embed_buttons %}
|
||||
{{ override_inner_markup_buttons_start|raw }}
|
||||
|
||||
{% if form.isEnabled() ?? true %}
|
||||
{% for button in form.buttons %}
|
||||
{% if not button.access or authorize(button.access) %}
|
||||
{% if button.outerclasses is defined %}<div class="{{ button.outerclasses }}">{% endif %}
|
||||
|
||||
{% if button.url %}
|
||||
{% set button_url = button.url starts with 'http' ? button.url : base_url ~ button.url %}
|
||||
{% endif %}
|
||||
|
||||
{% embed 'forms/layouts/button.html.twig' %}
|
||||
{% block embed_button_core %}
|
||||
{% if button.id %}id="{{ button.id }}"{% endif %}
|
||||
{% if button.disabled %}disabled="disabled"{% endif %}
|
||||
{% if button.name %}
|
||||
name="{{ button.name }}"
|
||||
{% else %}
|
||||
{% if button.task %}name="task" value="{{ button.task }}"{% endif %}
|
||||
{% endif %}
|
||||
type="{{ button.type|default('submit') }}"
|
||||
{% if button.attributes is defined %}
|
||||
{% for key,attribute in button.attributes %}
|
||||
{% if attribute|of_type('array') %}
|
||||
{{ attribute.name }}="{{ attribute.value|e('html_attr') }}"
|
||||
{% else %}
|
||||
{{ key }}="{{ attribute|e('html_attr') }}"
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block embed_button_classes %}
|
||||
{% block button_classes %}
|
||||
class="{{ form_button_classes ?: 'button' }} {{ button.classes }}"
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
{% block embed_button_content -%}
|
||||
{%- set button_value = button.value|t|default('Submit') -%}
|
||||
{%- if button.html -%}
|
||||
{{- button_value|trim|raw -}}
|
||||
{%- else -%}
|
||||
{{- button_value|trim|e -}}
|
||||
{%- endif -%}
|
||||
{%- endblock %}
|
||||
|
||||
{% endembed %}
|
||||
|
||||
{% if button.outerclasses is defined %}</div>{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{{ override_inner_markup_buttons_end }}
|
||||
{% endblock %}
|
||||
|
||||
{% endembed %}
|
||||
|
||||
{% if config.forms.dropzone.enabled %}
|
||||
<div id="dropzone-template" style="display:none;">
|
||||
{% include 'forms/dropzone/template.html.twig' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<span class="checkboxes toggleable" data-grav-field="toggleable" data-grav-field-name="{{ field_name }}">
|
||||
<input type="checkbox"
|
||||
id="toggleable_{{ field.name }}"
|
||||
name="toggleable_{{ field_name }}"
|
||||
value="1"
|
||||
{% if checked %}checked="checked"{% endif %}
|
||||
>
|
||||
<label for="toggleable_{{ field.name }}"></label>
|
||||
</span>
|
||||
@@ -0,0 +1,39 @@
|
||||
<div class="dz-preview dz-file-preview">
|
||||
<div class="dz-image"><img data-dz-thumbnail /></div>
|
||||
|
||||
<div class="dz-details">
|
||||
<div class="dz-size"><span data-dz-size></span></div>
|
||||
<div class="dz-filename"><span data-dz-name></span></div>
|
||||
</div>
|
||||
<div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div>
|
||||
<div class="dz-error-message"><span data-dz-errormessage></span></div>
|
||||
<a class="dz-remove" title="remove" href="javascript:undefined;" data-dz-remove>×</a>
|
||||
|
||||
<div class="dz-success-mark">
|
||||
|
||||
<svg width="54px" height="54px" viewBox="0 0 54 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.2.1 (9971) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Check</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<path d="M23.5,31.8431458 L17.5852419,25.9283877 C16.0248253,24.3679711 13.4910294,24.366835 11.9289322,25.9289322 C10.3700136,27.4878508 10.3665912,30.0234455 11.9283877,31.5852419 L20.4147581,40.0716123 C20.5133999,40.1702541 20.6159315,40.2626649 20.7218615,40.3488435 C22.2835669,41.8725651 24.794234,41.8626202 26.3461564,40.3106978 L43.3106978,23.3461564 C44.8771021,21.7797521 44.8758057,19.2483887 43.3137085,17.6862915 C41.7547899,16.1273729 39.2176035,16.1255422 37.6538436,17.6893022 L23.5,31.8431458 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z" id="Oval-2" stroke-opacity="0.198794158" stroke="#747474" fill-opacity="0.816519475" fill="#FFFFFF" sketch:type="MSShapeGroup"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
</div>
|
||||
<div class="dz-error-mark">
|
||||
|
||||
<svg width="54px" height="54px" viewBox="0 0 54 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.2.1 (9971) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>error</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="Check-+-Oval-2" sketch:type="MSLayerGroup" stroke="#747474" stroke-opacity="0.198794158" fill="#FFFFFF" fill-opacity="0.816519475">
|
||||
<path d="M32.6568542,29 L38.3106978,23.3461564 C39.8771021,21.7797521 39.8758057,19.2483887 38.3137085,17.6862915 C36.7547899,16.1273729 34.2176035,16.1255422 32.6538436,17.6893022 L27,23.3431458 L21.3461564,17.6893022 C19.7823965,16.1255422 17.2452101,16.1273729 15.6862915,17.6862915 C14.1241943,19.2483887 14.1228979,21.7797521 15.6893022,23.3461564 L21.3431458,29 L15.6893022,34.6538436 C14.1228979,36.2202479 14.1241943,38.7516113 15.6862915,40.3137085 C17.2452101,41.8726271 19.7823965,41.8744578 21.3461564,40.3106978 L27,34.6568542 L32.6538436,40.3106978 C34.2176035,41.8744578 36.7547899,41.8726271 38.3137085,40.3137085 C39.8758057,38.7516113 39.8771021,36.2202479 38.3106978,34.6538436 L32.6568542,29 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z" id="Oval-2" sketch:type="MSShapeGroup"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
{#
|
||||
DO NOT MODIFY!
|
||||
|
||||
Default layout can be found in form plugin or your theme:
|
||||
|
||||
templates/forms/layouts/field/default-field.html.twig
|
||||
|
||||
#}
|
||||
{% extends "forms/default/field.html.twig" %}
|
||||
@@ -0,0 +1,99 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% macro renderer(key, text, field, scope) %}
|
||||
|
||||
{% if text is not iterable %}
|
||||
<div class="form-row{% if field.value_only %} array-field-value_only{% endif %}"
|
||||
data-grav-array-type="row">
|
||||
<span data-grav-array-action="sort" class="fa fa-bars"></span>
|
||||
{% if field.value_only != true %}
|
||||
{% if key == '0' and text == '' %}
|
||||
{% set key = '' %}
|
||||
{% endif %}
|
||||
|
||||
<input
|
||||
data-grav-array-type="key"
|
||||
type="text" value="{{ key }}"
|
||||
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
|
||||
placeholder="{{ field.placeholder_key|e|t }}" />
|
||||
{% endif %}
|
||||
|
||||
{% if field.value_type == 'textarea' %}
|
||||
<textarea
|
||||
data-grav-array-type="value"
|
||||
name="{{ ((scope ~ field.name)|fieldName) ~ '[' ~ key ~ ']' }}"
|
||||
placeholder="{{ field.placeholder_value|e|t }}"
|
||||
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}>{{ text }}</textarea>
|
||||
{% else %}
|
||||
<input
|
||||
data-grav-array-type="value"
|
||||
type="text"
|
||||
name="{{ ((scope ~ field.name)|fieldName) ~ '[' ~ key ~ ']' }}"
|
||||
placeholder="{{ field.placeholder_value|e|t }}"
|
||||
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
|
||||
value={% if text == 'true' %}true{% elseif text == 'false' %}false{% else %}"{{ text|join(', ')|e }}"{% endif %} />
|
||||
{% endif %}
|
||||
|
||||
<span data-grav-array-action="rem" class="fa fa-minus"></span>
|
||||
<span data-grav-array-action="add" class="fa fa-plus"></span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% import _self as array_field %}
|
||||
|
||||
{% do assets.addJs('plugin://form/assets/form.vendor.js', { 'group': 'bottom', 'loading': 'defer' }) %}
|
||||
{% do assets.addJs('plugin://form/assets/form.min.js', { 'group': 'bottom', 'loading': 'defer' }) %}
|
||||
|
||||
{% block global_attributes %}
|
||||
data-grav-array-name="{{ (scope ~ field.name)|fieldName }}"
|
||||
data-grav-array-keyname="{{ field.placeholder_key|e|t }}"
|
||||
data-grav-array-valuename="{{ field.placeholder_value|e|t }}"
|
||||
data-grav-array-textarea="{{ field.value_type == 'textarea' }}"
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
|
||||
{% block input %}
|
||||
<div class="{{ field.size }} {{ field.classes }}" data-grav-array-type="container"{% if field.value_only %} data-grav-array-mode="value_only"{% endif %}{{ value|length <= 1 ? ' class="one-child"' : '' }}>
|
||||
{% if value|length %}
|
||||
{% for key, text in value -%}
|
||||
{% if text is not iterable %}
|
||||
{{ array_field.renderer(key, text, field, scope) }}
|
||||
{% else %}
|
||||
{# Backward compatibility for nested arrays (metas) which are not supported anymore #}
|
||||
{% for subkey, subtext in text -%}
|
||||
{{ array_field.renderer(key ~ '[' ~ subkey ~ ']', subtext, field, scope) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{%- else -%}
|
||||
{# Empty value, mock the entry field#}
|
||||
<div class="form-row" data-grav-array-type="row">
|
||||
<span data-grav-array-action="sort" class="fa fa-bars"></span>
|
||||
{% if field.value_only != true %}
|
||||
<input
|
||||
data-grav-array-type="key"
|
||||
type="text"
|
||||
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
|
||||
placeholder="{{ field.placeholder_key|e|t }}" />
|
||||
{% endif %}
|
||||
{% if field.value_type == 'textarea' %}
|
||||
<textarea
|
||||
data-grav-array-type="value"
|
||||
name="{{ (scope ~ field.name)|fieldName }}"
|
||||
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
|
||||
placeholder="{{ field.placeholder_value|e|t }}"></textarea>
|
||||
{% else %}
|
||||
<input
|
||||
data-grav-array-type="value"
|
||||
type="text"
|
||||
name="{{ (scope ~ field.name)|fieldName }}"
|
||||
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
|
||||
placeholder="{{ field.placeholder_value|e|t }}" />
|
||||
{% endif %}
|
||||
<span data-grav-array-action="rem" class="fa fa-minus"></span>
|
||||
<span data-grav-array-action="add" class="fa fa-plus"></span>
|
||||
</div>
|
||||
{%- endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,10 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block field %}
|
||||
{% set avatar = form.value('avatar') %}
|
||||
{% if avatar %}
|
||||
<label class="{{ field.classes }}"><img class="{{ field.img_classes }}" style="max-width:200px;" src="{{ base_url_simple ~ '/' ~ (avatar|first).path }}" /></label>
|
||||
{% else %}
|
||||
<label class="{{ field.classes }}"><img class="{{ field.img_classes }}" src="https://www.gravatar.com/avatar/{{ form.value('email')|md5 }}?s=200" /></label>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,35 @@
|
||||
{% set form_field_outer_data_classes = 'form-data basic-captcha' %}
|
||||
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block prepend %}
|
||||
{% set field_id = field.name|default('default') %}
|
||||
{% set config_hash = (form.id ~ '_basic_captcha_' ~ field_id)|md5 %}
|
||||
{% set image_url = url('/forms-basic-captcha-image.jpg') ~ '?field=' ~ config_hash %}
|
||||
|
||||
{# Store field configuration in session for image generation #}
|
||||
{% set global_config = grav.config.get('plugins.form.basic_captcha', {}) %}
|
||||
{% set merged_config = global_config|merge(field) %}
|
||||
{% do store_basic_captcha_config(config_hash, merged_config) %}
|
||||
|
||||
<div class="form-input-addon form-input-prepend"
|
||||
data-captcha-provider="basic-captcha"
|
||||
data-field-id="{{ config_hash }}">
|
||||
<img id="basic-captcha-reload-{{ form.id }}"
|
||||
src="{{ image_url }}"
|
||||
alt="human test"
|
||||
data-base-url="{{ url('/forms-basic-captcha-image.jpg') }}"
|
||||
data-field-id="{{ config_hash }}" />
|
||||
<button type="button" id="reload-captcha-{{ form.id }}" class="reload-captcha-button"><svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g fill="currentColor"><path d="M14.74 22.39c4.68-1.24 8-5.49 8-10.4 0-5.95-4.79-10.75-10.75-10.75 -3.11 0-5.78 1.11-7.99 2.95 -.77.64-1.43 1.32-1.98 2.01 -.34.41-.57.75-.69.95 -.22.35-.1.81.25 1.02 .35.21.81.09 1.02-.26 .08-.15.27-.43.56-.79 .49-.62 1.08-1.23 1.76-1.81C6.87 3.67 9.21 2.7 11.94 2.7c5.13 0 9.25 4.12 9.25 9.25 0 4.22-2.86 7.88-6.9 8.94 -.41.1-.64.51-.54.91 .1.4.51.63.91.53Zm-12-14.84V2.99c-.001-.42-.34-.75-.75-.75 -.42 0-.75.33-.75.75v4.56c0 .41.33.75.75.75 .41 0 .75-.34.75-.75Zm-.75.75H4h2.43c.41 0 .75-.34.75-.75 0-.42-.34-.75-.75-.75H4 1.99c-.42 0-.75.33-.75.75 0 .41.33.75.75.75Z"/><path d="M1.25 12c0 1.09.16 2.16.48 3.18 .12.39.54.61.93.49 .39-.13.61-.55.49-.94 -.28-.89-.42-1.81-.42-2.75 0-.42-.34-.75-.75-.75 -.42 0-.75.33-.75.75Zm1.93 6.15c.61.88 1.36 1.67 2.22 2.33 .32.25.79.19 1.05-.14 .25-.33.19-.8-.14-1.06 -.74-.58-1.38-1.25-1.92-2.02 -.24-.34-.71-.43-1.05-.19 -.34.23-.43.7-.19 1.04Zm5.02 3.91c1 .37 2.06.6 3.15.66 .41.02.76-.3.79-.71 .02-.42-.30-.77-.71-.80 -.94-.06-1.85-.25-2.72-.58 -.39-.15-.83.04-.97.43 -.15.38.04.82.43.96Z"/></g></svg></button>
|
||||
</div>
|
||||
|
||||
{% do assets.addJs('plugin://form/assets/captcha/basic-captcha-refresh.js') %}
|
||||
{% endblock %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="text"
|
||||
{% if field.size %}size="{{ field.size }}"{% endif %}
|
||||
{% if field.minlength is defined or field.validate.min is defined %}minlength="{{ field.minlength | default(field.validate.min) }}"{% endif %}
|
||||
{% if field.maxlength is defined or field.validate.max is defined %}maxlength="{{ field.maxlength | default(field.validate.max) }}"{% endif %}
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,18 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block field %}
|
||||
{# This main captcha field serves as a router to the appropriate provider template #}
|
||||
{% set provider = field.provider %}
|
||||
|
||||
{% if provider is not defined or provider == null %}
|
||||
{% set provider = 'recaptcha' %}
|
||||
{% endif %}
|
||||
|
||||
{% set template = 'forms/fields/' ~ provider ~ '/' ~ provider ~ '.html.twig' %}
|
||||
|
||||
{% if captcha_template_exists(template) %}
|
||||
{% include template with {'field': field} %}
|
||||
{% else %}
|
||||
<div class="form-error" style="color:#c00000;">ERROR - unknown captcha provider: <strong>{{ provider }}</strong></div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,46 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block label %}
|
||||
{% endblock %}
|
||||
|
||||
{% block input %}
|
||||
{% set id = field.id|default(field.name)|hyphenize %}
|
||||
<div class="{{ form_field_wrapper_classes ?: 'form-input-wrapper' }} {{ field.size }} {{ field.wrapper_classes }}">
|
||||
<input
|
||||
{# required attribute structures #}
|
||||
name="{{ (scope ~ field.name)|fieldName }}"
|
||||
value="{{ field.value ?? '1' }}"
|
||||
type="checkbox"
|
||||
{% if value == (field.value ?? '1') %} checked="checked" {% endif %}
|
||||
|
||||
{# input attribute structures #}
|
||||
{% block input_attributes %}
|
||||
id="{{ id|e }}"
|
||||
class="{{ form_field_checkbox_classes }} {{ field.classes }}"
|
||||
{% if field.style is defined %}style="{{ field.style|e }}" {% endif %}
|
||||
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
|
||||
{% if field.autofocus in ['on', 'true', 1] %}autofocus="autofocus"{% endif %}
|
||||
{% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %}
|
||||
{% if required %}required="required"{% endif %}
|
||||
{% if field.tabindex %}tabindex="{{ field.tabindex }}"{% endif %}
|
||||
{% if field.attributes is defined %}
|
||||
{% for key,attribute in field.attributes %}
|
||||
{% if attribute|of_type('array') %}
|
||||
{{ attribute.name }}="{{ attribute.value|e('html_attr') }}"
|
||||
{% else %}
|
||||
{{ key }}="{{ attribute|e('html_attr') }}"
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
/>
|
||||
<label style="display:inline;" class="inline" for="{{ id|e }}">
|
||||
{% if field.markdown %}
|
||||
{{ field.label|t|markdown(false) }}
|
||||
{% else %}
|
||||
{{ field.label|t|e }}
|
||||
{% endif %}
|
||||
{{ field.validate.required in ['on', 'true', 1] ? '<span class="required">*</span>' }}
|
||||
</label>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,41 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block global_attributes %}
|
||||
{{ parent() }}
|
||||
data-grav-keys="{{ field.use == 'keys' ? 'true' : 'false' }}"
|
||||
data-grav-field-name="{{ (scope ~ field.name)|fieldName }}"
|
||||
{% endblock %}
|
||||
|
||||
{% block input %}
|
||||
{% set value = (value is null ? field.default : value) %}
|
||||
{% if field.use == 'keys' and field.default %}
|
||||
{% set value = field.default|merge(value) %}
|
||||
{% endif %}
|
||||
|
||||
<div class="checkboxes {{ form_field_wrapper_classes }} {{ field.wrapper_classes }}">
|
||||
{% for key, text in field.options %}
|
||||
{% set id = field.id|default(field.name)|hyphenize ~ '-' ~ key %}
|
||||
{% set name = field.use == 'keys' ? key : id %}
|
||||
{% set val = field.use == 'keys' ? '1' : key %}
|
||||
{% set checked = (field.use == 'keys' ? value[key] : key in value) %}
|
||||
{% set help = (key in field.help_options|keys ? field.help_options[key] : false) %}
|
||||
{% set disabled = key in field.disabled_options %}
|
||||
<input type="checkbox"
|
||||
id="{{ id|e }}"
|
||||
value="{{ val|e }}"
|
||||
name="{{ (scope ~ field.name)|fieldName ~ '[' ~ name ~ ']' }}"
|
||||
class="{{ form_field_checkbox_classes }} {{ field.classes }}"
|
||||
{% if checked %}checked="checked"{% endif %}
|
||||
{% if disabled %}disabled="disabled"{% endif %}
|
||||
>
|
||||
<label style="display: inline; {% if disabled %}opacity: 0.6; cursor: no-drop;{% endif %}" for="{{ id|e }}">
|
||||
{% if help %}
|
||||
<span class="hint--bottom" data-hint="{{ help|t|e('html_attr') }}">{{ text|t|e }}</span>
|
||||
{% else %}
|
||||
{{ text|t|e }}
|
||||
{% endif %}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,6 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="color"
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,8 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block field %}
|
||||
{% embed 'forms/default/fields.html.twig' with {name: name, fields: field.fields} %}
|
||||
{% block outer_markup_field_open %}<div class="form-column {{ field.classes }}">{% endblock %}
|
||||
{% block outer_markup_field_close %}</div>{% endblock %}
|
||||
{% endembed %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,7 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block field %}
|
||||
<div class="form-columns {{ field.classes }}">
|
||||
{% include 'forms/default/fields.html.twig' with {name: field.name|parent_field, fields: field.fields} %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,19 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block field %}
|
||||
{% set value = evaluate(field.condition) %}
|
||||
{% set value = value == 'true' ? 1 : value %}
|
||||
{% set value = value == 'false' ? 0 : value %}
|
||||
|
||||
{% if value %}
|
||||
{% if field.classes %}
|
||||
<div class="{{ field.classes }}">
|
||||
{% endif %}
|
||||
|
||||
{% include 'forms/default/fields.html.twig' with {name: field.name, fields: field.fields} %}
|
||||
|
||||
{% if field.classes %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,8 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="date"
|
||||
{% if field.validate.min %}min="{{ field.validate.min }}"{% endif %}
|
||||
{% if field.validate.max %}max="{{ field.validate.max }}"{% endif %}
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1 @@
|
||||
{{ value ? value|date('m/d/Y')|e }}
|
||||
@@ -0,0 +1,2 @@
|
||||
{# DEPRECATED. Switched to Text field until implemented properly #}
|
||||
{% extends "forms/fields/text/text.html.twig" %}
|
||||
@@ -0,0 +1 @@
|
||||
{{ value ? value|date('m/d/Y \\a\\t g:i A')|e }}
|
||||
@@ -0,0 +1,21 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% if field.file %}
|
||||
{% set content = read_file(field.file) %}
|
||||
{% else %}
|
||||
{% set content = field.content %}
|
||||
{% endif %}
|
||||
|
||||
{% block input %}
|
||||
<div class="form-display-wrapper {{ field.size }} {{ field.classes }}" {% if field.id is defined %}id="{{ field.id|e }}" {% endif %}>
|
||||
{% if field.markdown %}
|
||||
{{ content|t|markdown|raw }}
|
||||
{% else %}
|
||||
{% if field.evaluate %}
|
||||
{{ evaluate_twig(content)|raw }}
|
||||
{% else %}
|
||||
{{ content|t|raw }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,10 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="email"
|
||||
{% if field.multiple in ['on', 'true', 1] %}multiple="multiple"{% endif %}
|
||||
{% if field.size %}size="{{ field.size }}"{% endif %}
|
||||
{% if field.minlength is defined or field.validate.min is defined %}minlength="{{ field.minlength | default(field.validate.min) }}"{% endif %}
|
||||
{% if field.maxlength is defined or field.validate.max is defined %}maxlength="{{ field.maxlength | default(field.validate.max) }}"{% endif %}
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,12 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
{% set scope = field.nest_id ? scope ~ field.name ~ '.' : scope %}
|
||||
|
||||
{% block field %}
|
||||
<fieldset {% if field.id is defined %}id="{{ field.id }}"{% endif %} {% if field.classes is defined %}class="{{ field.classes }}" {% endif %}>
|
||||
{% if field.legend %}
|
||||
<legend>{{ field.legend|t }}</legend>
|
||||
{% endif %}
|
||||
|
||||
{% include 'forms/default/fields.html.twig' with {name: field.name, fields: field.fields} %}
|
||||
</fieldset>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,136 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% macro bytesToSize(bytes) -%}
|
||||
{% apply spaceless %}
|
||||
{% set kilobyte = 1024 %}
|
||||
{% set megabyte = kilobyte * 1024 %}
|
||||
{% set gigabyte = megabyte * 1024 %}
|
||||
{% set terabyte = gigabyte * 1024 %}
|
||||
|
||||
{% if bytes < kilobyte %}
|
||||
{{ bytes ~ ' B' }}
|
||||
{% elseif bytes < megabyte %}
|
||||
{{ (bytes / kilobyte)|number_format(2, '.') ~ ' KB' }}
|
||||
{% elseif bytes < gigabyte %}
|
||||
{{ (bytes / megabyte)|number_format(2, '.') ~ ' MB' }}
|
||||
{% elseif bytes < terabyte %}
|
||||
{{ (bytes / gigabyte)|number_format(2, '.') ~ ' GB' }}
|
||||
{% else %}
|
||||
{{ (bytes / terabyte)|number_format(2, '.') ~ ' TB' }}
|
||||
{% endif %}
|
||||
{% endapply %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro preview(path, value, global) %}
|
||||
{% if value %}
|
||||
{% set uri = global.grav.uri %}
|
||||
{% set files = global.files %}
|
||||
{% set config = global.grav.config %}
|
||||
{% set route = global.context.route().toString(true) %}
|
||||
{% set type = global.context.content() is not null ? 'pages' : global.plugin ? 'plugins' : global.theme ? 'themes' : 'config' %}
|
||||
{% set blueprint_name = global.blueprints.getFilename %}
|
||||
{% if type == 'pages' %}
|
||||
{% set blueprint_name = type ~ '/' ~ blueprint_name %}
|
||||
{% endif %}
|
||||
{% set blueprint = blueprint_name|base64_encode %}
|
||||
{% set real_path = value.thumb ?? global.context.media[path].relativePath ?? global.form.getPagePathFromToken(path) %}
|
||||
{% set remove = global.form.getFileDeleteAjaxRoute(files.name, path).toString(true) ?: uri.addNonce(
|
||||
global.base_url_relative ~
|
||||
'/media.json' ~
|
||||
'/task' ~ config.system.param_sep ~ 'removeFileFromBlueprint' ~
|
||||
'/proute' ~ config.system.param_sep ~ route|base64_encode ~
|
||||
'/blueprint' ~ config.system.param_sep ~ blueprint ~
|
||||
'/type' ~ config.system.param_sep ~ type ~
|
||||
'/field' ~ config.system.param_sep ~ files.name ~
|
||||
'/path' ~ config.system.param_sep ~ value.path|base64_encode, 'admin-form', 'admin-nonce') %}
|
||||
|
||||
{% set file = value|merge({remove: remove, path: value.thumb_url ?? (uri.rootUrl == '/' ? '/' : uri.rootUrl ~ '/' ~ real_path) }) %}
|
||||
<div class="hidden" data-file="{{ file|json_encode|e('html_attr') }}"></div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% import _self as macro %}
|
||||
|
||||
{% set defaults = config.plugins.form %}
|
||||
{% set files = defaults.files|merge(field|default([])) %}
|
||||
{% set limit = not field.multiple ? 1 : files.limit %}
|
||||
|
||||
{% do config.set('forms.dropzone.enabled', true) %}
|
||||
|
||||
{% block input %}
|
||||
{% set page_can_upload = exists or (type == 'page' and not exists and not (field.destination starts with '@self' or field.destination starts with 'self@')) %}
|
||||
{% set max_filesize = (field.filesize > form_max_filesize or field.filesize == 0) ? form_max_filesize : field.filesize %}
|
||||
|
||||
{% block prepend %}{% endblock %}
|
||||
{% set settings = {name: field.name, paramName: (scope ~ field.name)|fieldName ~ (files.multiple ? '[]' : ''), limit: limit, filesize: max_filesize, accept: files.accept, resolution: files.resolution, resizeWidth: files.resizeWidth, resizeHeight: files.resizeHeight, resizeQuality: files.resizeQuality } %}
|
||||
{% set dropzoneSettings = field.dropzone %}
|
||||
{% set file_url_add = form.getFileUploadAjaxRoute().getUri() %}
|
||||
{% set file_url_remove = form.getFileDeleteAjaxRoute(null, null).getUri() %}
|
||||
<div class="{{ form_field_wrapper_classes ?: 'form-input-wrapper' }} {{ field.classes }} dropzone files-upload form-input-file {{ field.size }}"
|
||||
data-grav-file-settings="{{ settings|json_encode|e('html_attr') }}"
|
||||
data-dropzone-options="{{ dropzoneSettings|json_encode|e('html_attr') }}"
|
||||
data-file-field-name="{{ field.name }}"
|
||||
{% if file_url_add %}data-file-url-add="{{ file_url_add|e('html_attr') }}"{% endif %}
|
||||
{% if file_url_remove %}data-file-url-remove="{{ file_url_remove|e('html_attr') }}"{% endif %}>
|
||||
{% block file_extras %}{% endblock %}
|
||||
<input
|
||||
{# required attribute structures #}
|
||||
{% block input_attributes %}
|
||||
type="file"
|
||||
{% if files.multiple %}multiple="multiple"{% endif %}
|
||||
{% if files.accept %}accept="{{ files.accept|join(',') }}"{% endif %}
|
||||
{% if field.disabled %}disabled="disabled"{% endif %}
|
||||
{% if field.random_name %}random="true"{% endif %}
|
||||
{% if required %}required="required"{% endif %}
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
/>
|
||||
|
||||
{% for path, file in value %}
|
||||
{{ macro.preview(path, file, _context) }}
|
||||
{% endfor %}
|
||||
{% include 'forms/fields/hidden/hidden.html.twig' with {field: {name: '_json.' ~ field.name}, value: (value ?? [])|json_encode } %}
|
||||
|
||||
|
||||
</div>
|
||||
{% if inline_errors and errors %}
|
||||
<div class="{{ form_field_inline_error_classes }}">
|
||||
<p class="form-message"><i class="fa fa-exclamation-circle"></i> {{ errors|first|raw }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if form.xhr_submit %}
|
||||
{% do assets.addJs('plugin://form/assets/dropzone-reinit.js', { 'group': 'bottom', 'loading': 'defer', 'priority': 90 }) %}
|
||||
{% endif %}
|
||||
|
||||
{% if grav.browser.browser == 'msie' and grav.browser.version < 12 %}
|
||||
{% do assets.addJs('plugin://form/assets/object.assign.polyfill.js') %}
|
||||
{% endif %}
|
||||
{% do assets.addJs('jquery', 101) %}
|
||||
{% do assets.addJs('plugin://form/assets/form.vendor.js', { 'group': 'bottom', 'loading': 'defer', 'priority': 100 }) %}
|
||||
{% do assets.addJs('plugin://form/assets/form.min.js', { 'group': 'bottom', 'loading': 'defer', 'priority': 99 }) %}
|
||||
{% do assets.addCss('plugin://form/assets/dropzone.min.css', { 'group': 'form'}) %}
|
||||
{{ assets.css('form')|raw }}
|
||||
{% do assets.addInlineJs("
|
||||
window.GravForm = window.GravForm || {};
|
||||
window.GravForm = Object.assign({}, window.GravForm, {
|
||||
translations: {
|
||||
PLUGIN_FORM: {
|
||||
'DROPZONE_CANCEL_UPLOAD': " ~ 'PLUGIN_FORM.DROPZONE_CANCEL_UPLOAD'|t|json_encode ~ ",
|
||||
'DROPZONE_CANCEL_UPLOAD_CONFIRMATION': " ~ 'PLUGIN_FORM.DROPZONE_CANCEL_UPLOAD_CONFIRMATION'|t|json_encode ~ ",
|
||||
'DROPZONE_DEFAULT_MESSAGE': " ~ 'PLUGIN_FORM.DROPZONE_DEFAULT_MESSAGE'|t|json_encode ~ ",
|
||||
'DROPZONE_FALLBACK_MESSAGE': " ~ 'PLUGIN_FORM.DROPZONE_FALLBACK_MESSAGE'|t|json_encode ~ ",
|
||||
'DROPZONE_FALLBACK_TEXT': " ~ 'PLUGIN_FORM.DROPZONE_FALLBACK_TEXT'|t|json_encode ~ ",
|
||||
'DROPZONE_FILE_TOO_BIG': " ~ 'PLUGIN_FORM.DROPZONE_FILE_TOO_BIG'|t|json_encode ~ ",
|
||||
'DROPZONE_INVALID_FILE_TYPE': " ~ 'PLUGIN_FORM.DROPZONE_INVALID_FILE_TYPE'|t|json_encode ~ ",
|
||||
'DROPZONE_MAX_FILES_EXCEEDED': " ~ 'PLUGIN_FORM.DROPZONE_MAX_FILES_EXCEEDED'|t|json_encode ~ ",
|
||||
'DROPZONE_REMOVE_FILE': " ~ 'PLUGIN_FORM.DROPZONE_REMOVE_FILE'|t|json_encode ~ ",
|
||||
'DROPZONE_REMOVE_FILE_CONFIRMATION': " ~ 'PLUGIN_FORM.DROPZONE_REMOVE_FILE_CONFIRMATION'|t|json_encode ~ ",
|
||||
'DROPZONE_RESPONSE_ERROR': " ~ 'PLUGIN_FORM.DROPZONE_RESPONSE_ERROR'|t|json_encode ~ ",
|
||||
'RESOLUTION_MIN': " ~ 'PLUGIN_FORM.RESOLUTION_MIN'|t|json_encode ~ ",
|
||||
'RESOLUTION_MAX': " ~ 'PLUGIN_FORM.RESOLUTION_MAX'|t|json_encode ~ "
|
||||
}
|
||||
}
|
||||
});
|
||||
", {'group': 'bottom', 'position': 'before'}) %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,128 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% set defaults = config.plugins.form %}
|
||||
{% set files = defaults.files|merge(field|default([])) %}
|
||||
{% set limit = not field.multiple ? 1 : files.limit %}
|
||||
|
||||
{% block input %}
|
||||
{% set page_can_upload = exists or (type == 'page' and not exists and not (field.destination starts with '@self' or field.destination starts with 'self@')) %}
|
||||
{% set max_filesize = (field.filesize > form_max_filesize or field.filesize == 0) ? form_max_filesize : field.filesize %}
|
||||
|
||||
{% block prepend %}{% endblock %}
|
||||
{% set settings = {name: field.name, paramName: (scope ~ field.name)|fieldName ~ (files.multiple ? '[]' : ''), limit: limit, filesize: max_filesize, accept: files.accept, resolution: files.resolution, resizeWidth: field.filepond.resize_width, resizeHeight: field.filepond.resize_height, resizeQuality: field.filepond.resize_quality } %}
|
||||
{% set filepond_settings = field.filepond|default({}) %}
|
||||
{% set file_url_add = form.getFileUploadAjaxRoute().getUri() %}
|
||||
{% set file_url_remove = form.getFileDeleteAjaxRoute(null, null).getUri() %}
|
||||
<div class="{{ form_field_wrapper_classes ?: 'form-input-wrapper' }} {{ field.classes }} filepond-root form-input-file {{ field.size }}"
|
||||
data-grav-file-settings="{{ settings|json_encode|e('html_attr') }}"
|
||||
data-filepond-options="{{ filepond_settings|json_encode|e('html_attr') }}"
|
||||
data-file-field-name="{{ field.name }}"
|
||||
{% if file_url_add %}data-file-url-add="{{ file_url_add|e('html_attr') }}"{% endif %}
|
||||
{% if file_url_remove %}data-file-url-remove="{{ file_url_remove|e('html_attr') }}"{% endif %}>
|
||||
{% block file_extras %}{% endblock %}
|
||||
<input
|
||||
{# required attribute structures #}
|
||||
{% block input_attributes %}
|
||||
type="file"
|
||||
{% if files.multiple %}multiple="multiple"{% endif %}
|
||||
{% if files.accept %}accept="{{ files.accept|join(',') }}"{% endif %}
|
||||
{% if field.disabled %}disabled="disabled"{% endif %}
|
||||
{% if field.random_name %}random="true"{% endif %}
|
||||
{% if required %}required="required"{% endif %}
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
/>
|
||||
|
||||
{% for path, file in value %}
|
||||
<div class="hidden" data-file="{{ file|merge({remove: file.remove|default(''), path: file.path|default('')})|json_encode|e('html_attr') }}"></div>
|
||||
{% endfor %}
|
||||
{% include 'forms/fields/hidden/hidden.html.twig' with {field: {name: '_json.' ~ field.name}, value: (value ?? [])|json_encode } %}
|
||||
|
||||
</div>
|
||||
{% if inline_errors and errors %}
|
||||
<div class="{{ form_field_inline_error_classes }}">
|
||||
<p class="form-message"><i class="fa fa-exclamation-circle"></i> {{ errors|first|raw }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if grav.browser.browser == 'msie' and grav.browser.version < 12 %}
|
||||
{% do assets.addJs('plugin://form/assets/object.assign.polyfill.js') %}
|
||||
{% endif %}
|
||||
|
||||
{% do assets.addJs('jquery', 101) %}
|
||||
|
||||
{# FilePond core and plugins #}
|
||||
{% do assets.addJs('plugin://form/assets/filepond/filepond.min.js', { 'group': 'bottom', 'loading': 'defer', 'priority': 98 }) %}
|
||||
{% do assets.addJs('plugin://form/assets/filepond/filepond-plugin-file-validate-size.min.js', { 'group': 'bottom', 'loading': 'defer', 'priority': 97 }) %}
|
||||
{% do assets.addJs('plugin://form/assets/filepond/filepond-plugin-file-validate-type.min.js', { 'group': 'bottom', 'loading': 'defer', 'priority': 97 }) %}
|
||||
{% do assets.addJs('plugin://form/assets/filepond/filepond-plugin-image-preview.min.js', { 'group': 'bottom', 'loading': 'defer', 'priority': 97 }) %}
|
||||
{% do assets.addJs('plugin://form/assets/filepond/filepond-plugin-image-resize.min.js', { 'group': 'bottom', 'loading': 'defer', 'priority': 97 }) %}
|
||||
{% do assets.addJs('plugin://form/assets/filepond/filepond-plugin-image-transform.min.js', { 'group': 'bottom', 'loading': 'defer', 'priority': 97 }) %}
|
||||
|
||||
{# FilePond CSS #}
|
||||
{% do assets.addCss('plugin://form/assets/filepond/filepond.min.css') %}
|
||||
{% do assets.addCss('plugin://form/assets/filepond/filepond-plugin-image-preview.min.css') %}
|
||||
|
||||
{# Custom handlers - note: load this AFTER the libraries #}
|
||||
{% do assets.addJs('plugin://form/assets/filepond-handler.js', { 'group': 'bottom', 'loading': 'defer', 'priority': 96 }) %}
|
||||
|
||||
{# {% if form.xhr_submit %}#}
|
||||
{# {% do assets.addJs('plugin://form/assets/filepond-reinit.js', { 'group': 'bottom', 'loading': 'defer', 'priority': 90 }) %}#}
|
||||
{# {% endif %}#}
|
||||
|
||||
{% do assets.addInlineJs("
|
||||
window.GravForm = window.GravForm || {};
|
||||
window.GravForm = Object.assign({}, window.GravForm, {
|
||||
translations: {
|
||||
PLUGIN_FORM: {
|
||||
'FILEPOND_REMOVE_FILE': " ~ 'PLUGIN_FORM.FILEPOND_REMOVE_FILE'|t|json_encode ~ ",
|
||||
'FILEPOND_REMOVE_FILE_CONFIRMATION': " ~ 'PLUGIN_FORM.FILEPOND_REMOVE_FILE_CONFIRMATION'|t|json_encode ~ ",
|
||||
'FILEPOND_CANCEL_UPLOAD': " ~ 'PLUGIN_FORM.FILEPOND_CANCEL_UPLOAD'|t|json_encode ~ ",
|
||||
'FILEPOND_ERROR_FILESIZE': " ~ 'PLUGIN_FORM.FILEPOND_ERROR_FILESIZE'|t|json_encode ~ ",
|
||||
'FILEPOND_ERROR_FILETYPE': " ~ 'PLUGIN_FORM.FILEPOND_ERROR_FILETYPE'|t|json_encode ~ "
|
||||
}
|
||||
}
|
||||
});
|
||||
", {'group': 'bottom', 'position': 'before'}) %}
|
||||
|
||||
{% do assets.addInlineJs("
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (typeof GravFormXHR !== 'undefined') {
|
||||
// First check if DOM property exists
|
||||
if (GravFormXHR.DOM && typeof GravFormXHR.DOM.updateFormContent === 'function') {
|
||||
var originalUpdateFormContent = GravFormXHR.DOM.updateFormContent;
|
||||
GravFormXHR.DOM.updateFormContent = function() {
|
||||
var result = originalUpdateFormContent.apply(this, arguments);
|
||||
|
||||
// Dispatch event after form content is updated
|
||||
setTimeout(function() {
|
||||
document.dispatchEvent(new Event('grav-form-updated'));
|
||||
if (window.reinitializeFilePonds) {
|
||||
window.reinitializeFilePonds();
|
||||
}
|
||||
}, 50);
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
// If DOM property doesn't exist, try to hook into submit directly
|
||||
else if (typeof GravFormXHR.submit === 'function') {
|
||||
var originalSubmit = GravFormXHR.submit;
|
||||
GravFormXHR.submit = function(form) {
|
||||
var result = originalSubmit.apply(this, arguments);
|
||||
|
||||
// Reinitialize FilePond after form submission
|
||||
setTimeout(function() {
|
||||
document.dispatchEvent(new Event('grav-form-updated'));
|
||||
if (window.reinitializeFilePonds) {
|
||||
window.reinitializeFilePonds();
|
||||
}
|
||||
}, 500);
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
", {'group': 'bottom'}) %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,5 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block field %}
|
||||
<input type="hidden" name="__form-name__" value="{{ form.name }}" />
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,8 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block field %}
|
||||
{% if form.task %}
|
||||
<input type="hidden" name="task" value="{{ form.task }}" />
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block field %}
|
||||
|
||||
{# Used if the field is being used directly outside of form #}
|
||||
{% set value = value ?? field.value ?? (field.evaluate ? evaluate(field.default) : field.default) %}
|
||||
|
||||
{# Evaluate support for the form #}
|
||||
{% if not has_value and value and field.evaluate %}
|
||||
{% set value = evaluate(value) %}
|
||||
{% endif %}
|
||||
{% set input_value = value is iterable ? value|join(',') : value|string %}
|
||||
|
||||
<input data-grav-field="hidden" data-grav-disabled="false" {% if field.id is defined %}id="{{ field.id|e }}" {% endif %}type="hidden" class="input" name="{{ (scope ~ field.name)|fieldName }}" value="{{ input_value|e('html_attr') }}" />
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,15 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block field %}
|
||||
{% set input_value = value is iterable ? value|join(',') : value|string %}
|
||||
<input aria-hidden="true"
|
||||
type="text"
|
||||
{% if config.plugins.form.inline_css == true %}
|
||||
style="visibility:hidden;position:absolute!important;height:1px;width:1px;overflow:hidden;clip:rect(1px,1px,1px,1px);"
|
||||
{% endif %}
|
||||
class="form-honeybear"
|
||||
tabindex="-1"
|
||||
autocomplete="off"
|
||||
name="{{ (scope ~ field.name)|fieldName }}"
|
||||
value="{{ input_value|e }}" />
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,28 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input %}
|
||||
<div class="form-input-wrapper {{ field.size }}">
|
||||
{% set input_value = value is iterable ? value|join(',') : value|string %}
|
||||
<input
|
||||
type="text"
|
||||
value="{{ input_value|e }}"
|
||||
data-key-observe="{{ (scope ~ field.name)|fieldName }}"
|
||||
{% block input_attributes %}
|
||||
{% if field.classes is defined %}class="{{ field.classes }}" {% endif %}
|
||||
{% if field.id is defined %}id="{{ field.id|e }}" {% endif %}
|
||||
{% if field.style is defined %}style="{{ field.style|e }}" {% endif %}
|
||||
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
|
||||
{% if field.placeholder %}placeholder="{{ field.placeholder }}"{% endif %}
|
||||
{% if field.autofocus in ['on', 'true', 1] %}autofocus="autofocus"{% endif %}
|
||||
{% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %}
|
||||
{% if field.readonly in ['on', 'true', 1] %}readonly="readonly"{% endif %}
|
||||
{% if field.autocomplete in ['on', 'off'] %}autocomplete="{{ field.autocomplete }}"{% endif %}
|
||||
{% if field.tabindex %}tabindex="{{ field.tabindex }}"{% endif %}
|
||||
{% if field.validate.required in ['on', 'true', 1] %}required="required"{% endif %}
|
||||
{% if field.validate.pattern %}pattern="{{ field.validate.pattern }}"{% endif %}
|
||||
{% if field.validate.message %}title="{{ field.validate.message|e|t }}"
|
||||
{% elseif field.title is defined %}title="{{ field.title|e|t }}" {% endif %}
|
||||
{% endblock %}
|
||||
/>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,6 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="month"
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1 @@
|
||||
{{ nonce_field(form.getNonceAction() ?? 'form', form.getNonceName() ?? 'form-nonce')|raw }}
|
||||
@@ -0,0 +1,9 @@
|
||||
{% extends "forms/fields/text/text.html.twig" %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="number"
|
||||
{% if field.validate.min is defined %}min="{{ field.validate.min }}"{% endif %}
|
||||
{% if field.validate.max is defined %}max="{{ field.validate.max }}"{% endif %}
|
||||
{% if field.validate.step is defined %}step="{{ field.validate.step }}"{% endif %}
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,8 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% set value = null %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="password"
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,21 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input %}
|
||||
{% for key, text in field.options %}
|
||||
{% set id = field.id|default(field.name) ~ '-' ~ key %}
|
||||
|
||||
<div class="radio {{ form_field_wrapper_classes }} {{ field.wrapper_classes }}">
|
||||
<input type="radio"
|
||||
value="{{ key|e }}"
|
||||
id="{{ id|e }}"
|
||||
name="{{ (scope ~ field.name)|fieldName }}"
|
||||
class="{{ form_field_radio_classes }} {{ field.classes }}"
|
||||
{% if key == value %}checked="checked" {% endif %}
|
||||
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
|
||||
{% if required %}required="required"{% endif %}
|
||||
{% if field.tabindex %}tabindex="{{ field.tabindex }}"{% endif %}
|
||||
/>
|
||||
<label style="display: inline" class="inline" for="{{ id|e }}">{{ text|t|raw }}</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,9 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="range"
|
||||
{% if field.validate.min %}min="{{ field.validate.min }}"{% endif %}
|
||||
{% if field.validate.max %}max="{{ field.validate.max }}"{% endif %}
|
||||
{% if field.validate.step %}step="{{ field.validate.step }}"{% endif %}
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,393 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block label %}{% endblock %}
|
||||
|
||||
{% block input %}
|
||||
{% set config = grav.config %}
|
||||
{% set formId = form.id ?: form.name %}
|
||||
{% set callbackId = formId|underscorize %}
|
||||
{% set lang = grav.language.language %}
|
||||
|
||||
{# Get configuration values with fallbacks #}
|
||||
{% set version = field.recaptcha_version ?? config.plugins.form.recaptcha.version ?? '2-checkbox' %}
|
||||
{% set site_key = field.recaptcha_site_key ?? config.plugins.form.recaptcha.site_key %}
|
||||
{% set theme = field.recaptcha_theme ?? config.plugins.form.recaptcha.theme ?? 'light' %}
|
||||
|
||||
{% if not site_key %}
|
||||
<div class="form-error">reCAPTCHA site key is not set. Please set it in the form field or plugin configuration.</div>
|
||||
{% else %}
|
||||
{% if version == 3 or version == '3' %}
|
||||
{# --- reCAPTCHA v3 Handling --- #}
|
||||
{% set action = (page.route|trim('/') ~ '-' ~ form.name)|underscorize|md5 %}
|
||||
|
||||
<div class="g-recaptcha-container"
|
||||
data-form-id="{{ formId }}"
|
||||
data-recaptcha-version="3"
|
||||
data-captcha-provider="recaptcha"
|
||||
data-intercepts-submit="true"
|
||||
data-sitekey="{{ site_key }}"
|
||||
data-version="3"
|
||||
data-action="{{ action }}"
|
||||
data-theme="{{ theme }}"
|
||||
data-lang="{{ lang }}"
|
||||
data-callback-id="{{ callbackId }}">
|
||||
{# Container for v3 - will be managed by JS #}
|
||||
<input type="hidden" name="data[token]" value="">
|
||||
<input type="hidden" name="data[action]" value="{{ action }}">
|
||||
</div>
|
||||
|
||||
{% do assets.addJs('https://www.google.com/recaptcha/api.js?render=' ~ site_key, { group: 'bottom' }) %}
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
window.GravRecaptchaInitializers = window.GravRecaptchaInitializers || {};
|
||||
|
||||
function addHiddenInput(form, name, value) {
|
||||
const existing = form.querySelector('input[type="hidden"][name="' + name + '"]');
|
||||
if (existing) {
|
||||
existing.value = value;
|
||||
} else {
|
||||
const input = document.createElement('input');
|
||||
input.setAttribute('type', 'hidden');
|
||||
input.setAttribute('name', name);
|
||||
input.setAttribute('value', value);
|
||||
form.insertBefore(input, form.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
function initRecaptchaV3(container) {
|
||||
const formId = container.dataset.formId;
|
||||
const siteKey = container.dataset.sitekey;
|
||||
const action = container.dataset.action;
|
||||
const form = document.getElementById(formId);
|
||||
|
||||
if (!form) return;
|
||||
|
||||
console.log(`Initializing reCAPTCHA v3 for form ${formId}`);
|
||||
|
||||
const submitHandler = function(event) {
|
||||
event.preventDefault();
|
||||
console.log(`reCAPTCHA v3 intercepting submit for form ${formId}`);
|
||||
|
||||
grecaptcha.ready(function() {
|
||||
grecaptcha.execute(siteKey, { action: action })
|
||||
.then(function(token) {
|
||||
console.log(`reCAPTCHA v3 token received for form ${formId}`);
|
||||
addHiddenInput(form, 'data[token]', token);
|
||||
addHiddenInput(form, 'data[action]', action);
|
||||
form.removeEventListener('submit', submitHandler);
|
||||
|
||||
if (form.dataset.xhrEnabled === 'true' && window.GravFormXHR && typeof window.GravFormXHR.submit === 'function') {
|
||||
window.GravFormXHR.submit(form);
|
||||
} else {
|
||||
if (typeof form.requestSubmit === 'function') {
|
||||
form.requestSubmit();
|
||||
} else {
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const currentForm = document.getElementById(formId);
|
||||
if (currentForm && !currentForm.dataset.recaptchaListenerAttached) {
|
||||
currentForm.addEventListener('submit', submitHandler);
|
||||
currentForm.dataset.recaptchaListenerAttached = 'true';
|
||||
} else if (currentForm) {
|
||||
delete currentForm.dataset.recaptchaListenerAttached;
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
delete form.dataset.recaptchaListenerAttached;
|
||||
if (!form.dataset.recaptchaListenerAttached) {
|
||||
form.addEventListener('submit', submitHandler);
|
||||
form.dataset.recaptchaListenerAttached = 'true';
|
||||
}
|
||||
}
|
||||
|
||||
// Register the initializer function
|
||||
const initializerFunctionName = 'initRecaptcha_{{ formId }}';
|
||||
window.GravRecaptchaInitializers[initializerFunctionName] = function() {
|
||||
const container = document.querySelector('[data-form-id="{{ formId }}"][data-captcha-provider="recaptcha"]');
|
||||
if (!container) return;
|
||||
|
||||
initRecaptchaV3(container);
|
||||
};
|
||||
|
||||
// Initial call
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', window.GravRecaptchaInitializers[initializerFunctionName]);
|
||||
} else {
|
||||
setTimeout(window.GravRecaptchaInitializers[initializerFunctionName], 0);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
{% elseif version == '2-invisible' %}
|
||||
{# --- reCAPTCHA v2 Invisible Handling --- #}
|
||||
<div class="g-recaptcha-container"
|
||||
data-form-id="{{ formId }}"
|
||||
data-recaptcha-version="2-invisible"
|
||||
data-captcha-provider="recaptcha"
|
||||
data-intercepts-submit="true"
|
||||
data-sitekey="{{ site_key }}"
|
||||
data-version="2-invisible"
|
||||
data-theme="{{ theme }}"
|
||||
data-lang="{{ lang }}"
|
||||
data-callback-id="{{ callbackId }}">
|
||||
{# Container for v2 invisible - will be managed by JS #}
|
||||
<div id="g-recaptcha-{{ formId }}" class="g-recaptcha"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
window.GravRecaptchaInitializers = window.GravRecaptchaInitializers || {};
|
||||
|
||||
function addHiddenInput(form, name, value) {
|
||||
const existing = form.querySelector('input[type="hidden"][name="' + name + '"]');
|
||||
if (existing) {
|
||||
existing.value = value;
|
||||
} else {
|
||||
const input = document.createElement('input');
|
||||
input.setAttribute('type', 'hidden');
|
||||
input.setAttribute('name', name);
|
||||
input.setAttribute('value', value);
|
||||
form.insertBefore(input, form.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
function initRecaptchaV2Invisible(container) {
|
||||
const formId = container.dataset.formId;
|
||||
const siteKey = container.dataset.sitekey;
|
||||
const lang = container.dataset.lang;
|
||||
const theme = container.dataset.theme;
|
||||
const callbackId = container.dataset.callbackId || formId;
|
||||
const form = document.getElementById(formId);
|
||||
let widgetId = null;
|
||||
|
||||
if (!form) return;
|
||||
|
||||
console.log(`Initializing reCAPTCHA v2 Invisible for form ${formId}`);
|
||||
|
||||
const callbackName = 'captchaInvisibleOnloadCallback_' + callbackId;
|
||||
|
||||
if (typeof window[callbackName] !== 'function') {
|
||||
window[callbackName] = function() {
|
||||
console.log('reCAPTCHA Invisible API ready for form ' + formId);
|
||||
};
|
||||
|
||||
if (!document.querySelector('script[src*="recaptcha/api.js?onload=' + callbackName + '"]')) {
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://www.google.com/recaptcha/api.js?onload=' + callbackName + '&hl=' + lang + '&theme=' + theme;
|
||||
script.async = true;
|
||||
script.defer = true;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
}
|
||||
|
||||
const submitHandler = function(event) {
|
||||
event.preventDefault();
|
||||
console.log(`reCAPTCHA v2 Invisible intercepting submit for form ${formId}`);
|
||||
|
||||
if (typeof grecaptcha === 'undefined' || typeof grecaptcha.render === 'undefined') {
|
||||
console.error('grecaptcha not ready for invisible captcha');
|
||||
return;
|
||||
}
|
||||
|
||||
const recaptchaId = 'g-recaptcha-' + formId;
|
||||
let captchaElement = document.getElementById(recaptchaId);
|
||||
|
||||
if (!captchaElement) {
|
||||
captchaElement = document.createElement('div');
|
||||
captchaElement.setAttribute('id', recaptchaId);
|
||||
captchaElement.className = 'g-recaptcha';
|
||||
form.appendChild(captchaElement);
|
||||
}
|
||||
|
||||
const renderCaptcha = () => {
|
||||
if (widgetId !== null) {
|
||||
try {
|
||||
grecaptcha.reset(widgetId);
|
||||
} catch (e) {
|
||||
console.warn("Error resetting captcha", e);
|
||||
}
|
||||
}
|
||||
|
||||
widgetId = grecaptcha.render(recaptchaId, {
|
||||
sitekey: siteKey,
|
||||
size: 'invisible',
|
||||
callback: function(token) {
|
||||
console.log(`reCAPTCHA v2 Invisible token received for form ${formId}`);
|
||||
addHiddenInput(form, 'g-recaptcha-response', token);
|
||||
form.removeEventListener('submit', submitHandler);
|
||||
|
||||
if (form.dataset.xhrEnabled === 'true' && window.GravFormXHR && typeof window.GravFormXHR.submit === 'function') {
|
||||
window.GravFormXHR.submit(form);
|
||||
} else {
|
||||
if (typeof form.requestSubmit === 'function') {
|
||||
form.requestSubmit();
|
||||
} else {
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const currentForm = document.getElementById(formId);
|
||||
if (currentForm && !currentForm.dataset.recaptchaListenerAttached) {
|
||||
currentForm.addEventListener('submit', submitHandler);
|
||||
currentForm.dataset.recaptchaListenerAttached = 'true';
|
||||
} else if (currentForm) {
|
||||
delete currentForm.dataset.recaptchaListenerAttached;
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
|
||||
grecaptcha.execute(widgetId);
|
||||
};
|
||||
|
||||
if (typeof grecaptcha !== 'undefined' && grecaptcha.render) {
|
||||
renderCaptcha();
|
||||
} else {
|
||||
const originalOnload = window[callbackName];
|
||||
window[callbackName] = function() {
|
||||
if(originalOnload) originalOnload();
|
||||
renderCaptcha();
|
||||
};
|
||||
console.warn("grecaptcha object not found immediately, waiting for onload callback: " + callbackName);
|
||||
}
|
||||
};
|
||||
|
||||
delete form.dataset.recaptchaListenerAttached;
|
||||
if (!form.dataset.recaptchaListenerAttached) {
|
||||
form.addEventListener('submit', submitHandler);
|
||||
form.dataset.recaptchaListenerAttached = 'true';
|
||||
}
|
||||
}
|
||||
|
||||
// Register the initializer function
|
||||
const initializerFunctionName = 'initRecaptcha_{{ formId }}';
|
||||
window.GravRecaptchaInitializers[initializerFunctionName] = function() {
|
||||
const container = document.querySelector('[data-form-id="{{ formId }}"][data-captcha-provider="recaptcha"]');
|
||||
if (!container) return;
|
||||
|
||||
initRecaptchaV2Invisible(container);
|
||||
};
|
||||
|
||||
// Initial call
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', window.GravRecaptchaInitializers[initializerFunctionName]);
|
||||
} else {
|
||||
setTimeout(window.GravRecaptchaInitializers[initializerFunctionName], 0);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
{% else %}
|
||||
{# --- reCAPTCHA v2 Checkbox Handling --- #}
|
||||
{# Add script and container #}
|
||||
{% set container_id = 'g-recaptcha-' ~ formId %}
|
||||
{% set onloadCallback = 'captchaCheckboxOnloadCallback_' ~ callbackId %}
|
||||
|
||||
<div class="g-recaptcha-container"
|
||||
data-form-id="{{ formId }}"
|
||||
data-captcha-provider="recaptcha"
|
||||
data-sitekey="{{ site_key }}"
|
||||
data-version="2-checkbox"
|
||||
data-theme="{{ theme }}"
|
||||
data-lang="{{ lang }}"
|
||||
data-callback-id="{{ callbackId }}">
|
||||
<div id="{{ container_id }}" class="g-recaptcha"></div>
|
||||
</div>
|
||||
|
||||
{% do assets.addJs('https://www.google.com/recaptcha/api.js?onload=' ~ onloadCallback ~ '&render=explicit', { 'group': 'bottom', 'loading': 'defer' }) %}
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
// Explicit rendering for reCAPTCHA v2 Checkbox
|
||||
window.GravExplicitCaptchaInitializers = window.GravExplicitCaptchaInitializers || {};
|
||||
const initializerFunctionName = 'initExplicitCaptcha_{{ formId }}';
|
||||
|
||||
// Define the initializer function
|
||||
window.GravExplicitCaptchaInitializers[initializerFunctionName] = function() {
|
||||
const containerId = '{{ container_id }}';
|
||||
const container = document.getElementById(containerId);
|
||||
|
||||
if (!container) {
|
||||
console.warn('reCAPTCHA container #' + containerId + ' not found.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent re-rendering if widget already exists
|
||||
if (container.innerHTML.trim() !== '' && container.querySelector('iframe')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get configuration from parent container
|
||||
const parentContainer = container.closest('.g-recaptcha-container');
|
||||
if (!parentContainer) {
|
||||
console.error('Cannot find parent container for #' + containerId);
|
||||
return;
|
||||
}
|
||||
|
||||
const sitekey = parentContainer.dataset.sitekey;
|
||||
const theme = parentContainer.dataset.theme;
|
||||
|
||||
if (!sitekey) {
|
||||
console.error('reCAPTCHA sitekey missing for #' + containerId);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Attempting to render reCAPTCHA in #' + containerId);
|
||||
|
||||
if (typeof grecaptcha !== 'undefined' && typeof grecaptcha.render === 'function') {
|
||||
try {
|
||||
grecaptcha.render(containerId, {
|
||||
sitekey: sitekey,
|
||||
theme: theme,
|
||||
callback: function(token) {
|
||||
console.log('reCAPTCHA challenge successful for #' + containerId);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Error calling grecaptcha.render for #' + containerId, e);
|
||||
container.innerHTML = '<p style="color:red;">Error initializing reCAPTCHA.</p>';
|
||||
}
|
||||
} else {
|
||||
console.warn('grecaptcha API not available yet for #' + containerId + '. Waiting for onload.');
|
||||
}
|
||||
};
|
||||
|
||||
// Define the global onload callback
|
||||
window['{{ onloadCallback }}'] = function() {
|
||||
console.log('reCAPTCHA API loaded, triggering init for #{{ container_id }}');
|
||||
if (window.GravExplicitCaptchaInitializers[initializerFunctionName]) {
|
||||
window.GravExplicitCaptchaInitializers[initializerFunctionName]();
|
||||
} else {
|
||||
console.error("Initializer " + initializerFunctionName + " not found!");
|
||||
}
|
||||
};
|
||||
|
||||
// Form submit handler to check if captcha is completed
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const form = document.getElementById('{{ formId }}');
|
||||
if (form) {
|
||||
form.addEventListener('submit', function(event) {
|
||||
const response = grecaptcha.getResponse();
|
||||
if (!response) {
|
||||
event.preventDefault();
|
||||
alert("{{ field.captcha_not_validated|t|default('Please complete the captcha')|e('js') }}");
|
||||
} else if (form.dataset.xhrEnabled === 'true' && window.GravFormXHR && typeof window.GravFormXHR.submit === 'function') {
|
||||
event.preventDefault();
|
||||
window.GravFormXHR.submit(form);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,21 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
{% set title_level = grav['admin'] is defined ? 'h1' : field.title_level|default('h3') %}
|
||||
|
||||
{% block field %}
|
||||
{% if field.security is empty or authorize(array(field.security)) %}
|
||||
<div class="{{ field.classes }}">
|
||||
{% if field.title or field.underline %}
|
||||
<{{ title_level }} class="{% if not field.underline %}no_underline{% endif %}">{{ field.title|t }}</{{ title_level }}>
|
||||
{% endif %}
|
||||
|
||||
{% if field.text %}
|
||||
<p>{{ field.text|t|raw }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% embed 'forms/default/fields.html.twig' with {name: field.name, fields: field.fields} %}
|
||||
{% block outer_markup_field_open %}<div class="form-section">{% endblock %}
|
||||
{% block outer_markup_field_close %}</div>{% endblock %}
|
||||
{% endembed %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,78 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block global_attributes %}
|
||||
data-grav-selectize="{{ (field.selectize is defined ? field.selectize : {})|json_encode()|e('html_attr') }}"
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
|
||||
{% block input %}
|
||||
<div class="{{ form_field_wrapper_classes ?: 'form-select-wrapper' }} {{ field.size }} {{ field.wrapper_classes }}">
|
||||
<select name="{{ (scope ~ field.name)|fieldName ~ (field.multiple ? '[]' : '') }}"
|
||||
class="{{ form_field_select_classes }} {{ field.classes }} {{ field.size }}"
|
||||
{% if field.id is defined %}id="{{ field.id|e }}" {% endif %}
|
||||
{% if field.style is defined %}style="{{ field.style|e }}" {% endif %}
|
||||
{% if field.disabled %}disabled="disabled"{% endif %}
|
||||
{% if field.autofocus in ['on', 'true', 1] %}autofocus="autofocus"{% endif %}
|
||||
{% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %}
|
||||
{% if required %}required="required"{% endif %}
|
||||
{% if field.multiple in ['on', 'true', 1] %}multiple="multiple"{% endif %}
|
||||
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
|
||||
{% if field.tabindex %}tabindex="{{ field.tabindex }}"{% endif %}
|
||||
{% if field.form %}form="{{ field.form }}"{% endif %}
|
||||
{% if field.autocomplete is defined %}autocomplete="{{ field.autocomplete }}"{% endif %}
|
||||
{% if field.key %}
|
||||
data-key-observe="{{ (scope ~ field.name)|fieldName }}"
|
||||
{% endif %}
|
||||
{% if field.datasets %}
|
||||
{% for datakey, datavalue in field.datasets %}
|
||||
data-{{ datakey }}="{{ datavalue|e('html_attr') }}"
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if field.attributes %}
|
||||
{% for key, value in field.attributes %}
|
||||
{{ key }}="{{ value|e('html_attr') }}"
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
>
|
||||
{% if field.placeholder %}<option value="" disabled selected>{{ field.placeholder|t|raw }}</option>{% endif %}
|
||||
|
||||
{% set options = field.options %}
|
||||
{% if field.selectize.create and value %}
|
||||
{% set custom_value = field.multiple ? value : { (value): value } %}
|
||||
{% set options = options|merge(custom_value|default([]))|array_unique %}
|
||||
{% endif %}
|
||||
|
||||
{% set value = value is iterable ? value : value|string %}
|
||||
{% for key, item_value in options %}
|
||||
{% if item_value is iterable and item_value.value %}
|
||||
{% set akey = field.selectize and field.multiple ? item_value : key %}
|
||||
{% set avalue = item_value.value|t %}
|
||||
<option {{ item_value.disabled ? 'disabled="disabled"' : '' }}
|
||||
{{ item_value.selected or key == value ? 'selected="selected"' : '' }}
|
||||
{{ item_value.label ? 'label=' ~ item_value.label : '' }}
|
||||
value="{{ akey }}"
|
||||
>
|
||||
{{ avalue|raw }}
|
||||
</option>
|
||||
{% elseif item_value is iterable %}
|
||||
{% set optgroup_label = item_value|keys|first %}
|
||||
<optgroup label="{{ optgroup_label|t|e('html_attr') }}">
|
||||
{% for subkey, suboption in field.options[key][optgroup_label] %}
|
||||
{% set subkey = subkey|string %}
|
||||
{% set item_value = (field.selectize and field.multiple ? suboption : subkey)|string %}
|
||||
{% set selected = (field.selectize ? suboption : subkey)|string %}
|
||||
<option {% if subkey is same as (value) or (field.multiple and selected in value) %}selected="selected"{% endif %} value="{{ subkey }}">
|
||||
{{ suboption|t|raw }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% else %}
|
||||
{% set val = (field.selectize and field.multiple ? item_value : key)|string %}
|
||||
{% set selected = (field.selectize ? item_value : key)|string %}
|
||||
<option {% if val is same as (value) or (field.multiple and selected in value) %}selected="selected"{% endif %} value="{{ val }}">{{ item_value|t|raw }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
</select>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,2 @@
|
||||
{# Deprecated Form 4.0: Just use `select` field #}
|
||||
{% extends "forms/fields/select/select.html.twig" %}
|
||||
@@ -0,0 +1,105 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block prepend %}
|
||||
<div id="signature-pad" class="signature-pad">
|
||||
<div class="signature-pad--body">
|
||||
<canvas></canvas>
|
||||
</div>
|
||||
<div class="signature-pad--footer">
|
||||
<div class="description">Sign above</div>
|
||||
|
||||
<div class="signature-pad--actions">
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary-border" data-action="clear">Clear Signature</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{{ url('plugin://form/assets/signature_pad.js') }}"></script>
|
||||
<script>
|
||||
|
||||
var wrapper = document.getElementById("signature-pad");
|
||||
var clearButton = wrapper.querySelector("[data-action=clear]");
|
||||
|
||||
var canvas = wrapper.querySelector("canvas");
|
||||
var signaturePad = new SignaturePad(canvas, {
|
||||
// It's Necessary to use an opaque color when saving image as JPEG;
|
||||
// this option can be omitted if only saving as PNG or SVG
|
||||
backgroundColor: 'rgb(255, 255, 255)',
|
||||
onEnd: function() {
|
||||
var input = document.querySelector('[name="data[{{ field.name }}]"]');
|
||||
input.value = this.toDataURL();
|
||||
}
|
||||
});
|
||||
|
||||
// Adjust canvas coordinate space taking into account pixel ratio,
|
||||
// to make it look crisp on mobile devices.
|
||||
// This also causes canvas to be cleared.
|
||||
function resizeCanvas() {
|
||||
// When zoomed out to less than 100%, for some very strange reason,
|
||||
// some browsers report devicePixelRatio as less than 1
|
||||
// and only part of the canvas is cleared then.
|
||||
var ratio = Math.max(window.devicePixelRatio || 1, 1);
|
||||
|
||||
// This part causes the canvas to be cleared
|
||||
canvas.width = canvas.offsetWidth * ratio;
|
||||
canvas.height = canvas.offsetHeight * ratio;
|
||||
canvas.getContext("2d").scale(ratio, ratio);
|
||||
|
||||
// This library does not listen for canvas changes, so after the canvas is automatically
|
||||
// cleared by the browser, SignaturePad#isEmpty might still return false, even though the
|
||||
// canvas looks empty, because the internal data of this library wasn't cleared. To make sure
|
||||
// that the state of this library is consistent with visual state of the canvas, you
|
||||
// have to clear it manually.
|
||||
signaturePad.clear();
|
||||
}
|
||||
|
||||
// On mobile devices it might make more sense to listen to orientation change,
|
||||
// rather than window resize events.
|
||||
window.onresize = resizeCanvas;
|
||||
resizeCanvas();
|
||||
|
||||
function download(dataURL, filename) {
|
||||
var blob = dataURLToBlob(dataURL);
|
||||
var url = window.URL.createObjectURL(blob);
|
||||
|
||||
var a = document.createElement("a");
|
||||
a.style = "display: none";
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// One could simply use Canvas#toBlob method instead, but it's just to show
|
||||
// that it can be done using result of SignaturePad#toDataURL.
|
||||
function dataURLToBlob(dataURL) {
|
||||
// Code taken from https://github.com/ebidel/filer.js
|
||||
var parts = dataURL.split(';base64,');
|
||||
var contentType = parts[0].split(":")[1];
|
||||
var raw = window.atob(parts[1]);
|
||||
var rawLength = raw.length;
|
||||
var uInt8Array = new Uint8Array(rawLength);
|
||||
|
||||
for (var i = 0; i < rawLength; ++i) {
|
||||
uInt8Array[i] = raw.charCodeAt(i);
|
||||
}
|
||||
|
||||
return new Blob([uInt8Array], { type: contentType });
|
||||
}
|
||||
|
||||
clearButton.addEventListener("click", function (event) {
|
||||
signaturePad.clear();
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock prepend %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="hidden"
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,19 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block field %}
|
||||
<div class="form-field form-spacer {{ field.classes }}">
|
||||
{% if field.title %}
|
||||
<{{ title_type|default('h3') }}>{{- field.title|t|raw -}}</ {{ title_type|default('h3') }}>
|
||||
{% endif %}
|
||||
|
||||
{% if field.markdown %}
|
||||
<p>{{- field.text|t|markdown|raw -}}</p>
|
||||
{% else %}
|
||||
<p>{{- field.text|t|raw -}}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if field.underline %}
|
||||
<hr />
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1 @@
|
||||
{% extends 'forms/fields/checkbox/checkbox.html.twig' %}
|
||||
@@ -0,0 +1,8 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block field %}
|
||||
{% embed 'forms/default/fields.html.twig' with {name: field.name, fields: field.fields} %}
|
||||
{% block outer_markup_field_open %}<div class="form-tab">{% endblock %}
|
||||
{% block outer_markup_field_close %}</div>{% endblock %}
|
||||
{% endembed %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,60 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% if not grav.admin %}
|
||||
{% do assets.addJs('plugin://form/assets/form.vendor.js', { 'group': 'bottom', 'loading': 'defer' }) %}
|
||||
{% do assets.addJs('plugin://form/assets/form.min.js', { 'group': 'bottom', 'loading': 'defer' }) %}
|
||||
{% endif %}
|
||||
|
||||
{% block field %}
|
||||
<div class="form-tabs {{ field.class }} {{ field.classes }}">
|
||||
|
||||
{% set fields = prepare_form_fields(field.fields, field.name) %}
|
||||
{% if fields|length %}
|
||||
{% set tabs = {} %}
|
||||
{% for tab in fields %}
|
||||
{% if tab.type == 'tab' and not tab.validate.ignore and (tab.security is empty or authorize(array(tab.security))) %}
|
||||
{% set tabs = tabs|merge([tab]) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% set count = tabs|length %}
|
||||
|
||||
{% if count == 0 %}
|
||||
{# Nothing to display #}
|
||||
{% elseif count == 1 and not admin %}
|
||||
{% set fields = (tabs|first).fields %}
|
||||
{% for field in fields %}
|
||||
{% set value = field.name ? (form ? form.value(field.name) : data.value(field.name)) : data.toArray %}
|
||||
{% set field_templates = include_form_field(field.type, field_layout, 'text') %}
|
||||
{% include field_templates %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% set tabsKey = form.name ~ '-' ~ fields|keys|join(':')|md5 %}
|
||||
{% set storedValue = grav.admin ? get_cookie('grav-tabs-state')|default('{}')|json_decode : [] %}
|
||||
{% set storedTab = attribute(storedValue, 'tab-' ~ tabsKey) %}
|
||||
{% if storedTab is empty %}
|
||||
{% set active = uri.params.tab ?? field.active ?? 1 %}
|
||||
{% endif %}
|
||||
|
||||
<div class="tabs-nav">
|
||||
{% for tab in tabs %}
|
||||
{% if tab.type == 'tab' and (tab.condition is null or tab.condition == true) %}
|
||||
<a class="tab__link {{ (storedTab == scope ~ tab.name) or active == loop.index ? 'active' : '' }}" data-tabid="tab-{{ tabsKey ~ '-' ~ tab.name }}" data-tabkey="tab-{{ tabsKey }}" data-scope="{{ scope ~ tab.name }}">
|
||||
<span>{{ tab.title|t }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="tabs-content">
|
||||
{% embed 'forms/default/fields.html.twig' with {name: field.name, fields: fields} %}
|
||||
{% block inner_markup_field_open %}
|
||||
<div id="tab-{{ tabsKey ~ '-' ~ field.name }}" class="tab__content {{ (storedTab == scope ~ field.name) or active == loop.index ? 'active' : '' }}">
|
||||
{% endblock %}
|
||||
{% block inner_markup_field_close %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,9 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="tel"
|
||||
{% if field.size %}size="{{ field.size }}"{% endif %}
|
||||
{% if field.minlength is defined or field.validate.min is defined %}minlength="{{ field.minlength | default(field.validate.min) }}"{% endif %}
|
||||
{% if field.maxlength is defined or field.validate.max is defined %}maxlength="{{ field.maxlength | default(field.validate.max) }}"{% endif %}
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,7 @@
|
||||
{% set value = iterable ? value|join(', ') : value|string %}
|
||||
|
||||
{% if link -%}
|
||||
<a href="{{ link|e }}">{{ value|e }}</a>
|
||||
{%- else -%}
|
||||
{{ value|e }}
|
||||
{%- endif %}
|
||||
@@ -0,0 +1,39 @@
|
||||
{% if field.prepend or field.append or field.copy_to_clipboard %}
|
||||
{% set field = field|merge({'wrapper_classes': 'form-input-addon-wrapper'}) %}
|
||||
{% endif %}
|
||||
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block prepend %}
|
||||
{% if field.prepend %}
|
||||
<div class="form-input-addon form-input-prepend">
|
||||
{{- field.prepend|t|raw -}}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="text"
|
||||
{% if field.size %}size="{{ field.size }}"{% endif %}
|
||||
{% if field.minlength is defined or field.validate.min is defined %}minlength="{{ field.minlength | default(field.validate.min) }}"{% endif %}
|
||||
{% if field.maxlength is defined or field.validate.max is defined %}maxlength="{{ field.maxlength | default(field.validate.max) }}"{% endif %}
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
|
||||
{% block append %}
|
||||
{% if field.copy_to_clipboard %}
|
||||
<div class="form-input-addon form-input-append copy-to-clipboard">
|
||||
{% if field.copy_to_clipboard in ['0', '1'] %}
|
||||
<i class="fa fa-clipboard"></i>
|
||||
{% else %}
|
||||
{{- field.copy_to_clipboard|t|raw -}}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% elseif field.append %}
|
||||
<div class="form-input-addon form-input-append">
|
||||
{{- field.append|t|raw -}}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input %}
|
||||
<div class="{{ form_field_wrapper_classes ?: 'form-textarea-wrapper' }} {{ field.size }} {{ field.wrapper_classes }}">
|
||||
{% block prepend %}{% endblock prepend %}
|
||||
<textarea
|
||||
{# required attribute structures #}
|
||||
name="{{ (scope ~ field.name)|fieldName }}"
|
||||
{# input attribute structures #}
|
||||
{% block input_attributes %}
|
||||
class="{{ form_field_textarea_classes }} {{ field.classes }} {{ field.size }}"
|
||||
{% if field.id is defined %}id="{{ field.id|e }}" {% endif %}
|
||||
{% if field.style is defined %}style="{{ field.style|e }}" {% endif %}
|
||||
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
|
||||
{% if field.placeholder %}placeholder="{{ field.placeholder|t }}"{% endif %}
|
||||
{% if field.autofocus in ['on', 'true', 1] %}autofocus="autofocus"{% endif %}
|
||||
{% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %}
|
||||
{% if field.readonly in ['on', 'true', 1] %}readonly="readonly"{% endif %}
|
||||
{% if field.autocomplete in ['on', 'off'] %}autocomplete="{{ field.autocomplete }}"{% endif %}
|
||||
{% if field.tabindex %}tabindex="{{ field.tabindex }}"{% endif %}
|
||||
{% if required %}required="required"{% endif %}
|
||||
{% if field.validate.pattern %}pattern="{{ field.validate.pattern }}"{% endif %}
|
||||
{% if field.validate.message %}title="{{ field.validate.message|t|e }}"{% endif %}
|
||||
{% if field.rows is defined %}rows="{{ field.rows }}"{% endif %}
|
||||
{% if field.cols is defined %}cols="{{ field.cols }}"{% endif %}
|
||||
{% if field.minlength is defined or field.validate.min is defined %}minlength="{{ field.minlength | default(field.validate.min) }}"{% endif %}
|
||||
{% if field.maxlength is defined or field.validate.max is defined %}maxlength="{{ field.maxlength | default(field.validate.max) }}"{% endif %}
|
||||
{% if field.datasets %}
|
||||
{% for datakey, datavalue in field.datasets %}
|
||||
data-{{ datakey }}="{{ datavalue|e('html_attr') }}"
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if field.attributes is defined %}
|
||||
{% for key,attribute in field.attributes %}
|
||||
{% if attribute|of_type('array') %}
|
||||
{{ attribute.name }}="{{ attribute.value|e('html_attr') }}"
|
||||
{% else %}
|
||||
{{ key }}="{{ attribute|e('html_attr') }}"
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
>{{ value|trim|e('html') }}</textarea>
|
||||
{% block append %}{% endblock append %}
|
||||
{% if inline_errors and errors %}
|
||||
<div class="{{ form_errors_classes ?: 'form-errors' }}">
|
||||
<p class="form-message"><i class="fa fa-exclamation-circle"></i> {{ errors|first }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1 @@
|
||||
{{ value ? value|date('g:i A')|e }}
|
||||
@@ -0,0 +1,6 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="time"
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,5 @@
|
||||
{%- if value -%}
|
||||
<span><i class="published fa fa-check-circle"></i></span>
|
||||
{%- else -%}
|
||||
<span><i class="unpublished fa fa-times-circle"></i></span>
|
||||
{%- endif -%}
|
||||
@@ -0,0 +1,52 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% macro spanToggle(input, length) %}
|
||||
{% set space = repeat(' ', (length - input|length) / 2) %}
|
||||
{{ (space ~ input ~ space)|raw }}
|
||||
{% endmacro %}
|
||||
|
||||
{% import _self as macro %}
|
||||
|
||||
{% set has_hidden = false %}
|
||||
{% for key, text in field.options %}
|
||||
{% if key is empty %}
|
||||
{% set has_hidden = true %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% block global_attributes %}
|
||||
{{ parent() }}
|
||||
data-grav-field-name="{{ (scope ~ field.name)|fieldName }}"
|
||||
{% endblock %}
|
||||
|
||||
{% block input %}
|
||||
<div class="switch-toggle switch-grav {{ field.size }} switch-{{ field.options|length }} {{ field.classes }}">
|
||||
{% set maxLen = 0 %}
|
||||
{% for text in field.options %}
|
||||
{% set translation = text|t|trim %}
|
||||
{% set maxLen = max(translation|length, maxLen) %}
|
||||
{% endfor %}
|
||||
|
||||
{# Value falls back to highlight instead of default #}
|
||||
{% set highlight = field.highlight|string %}
|
||||
{% set value = (value ?? default ?? highlight)|string %}
|
||||
|
||||
{% for key, text in field.options %}
|
||||
{% set key = key|string %}
|
||||
{% set id = (field.id ?? ("toggle_" ~ field.name)) ~ key %}
|
||||
{% set translation = text|t|trim %}
|
||||
|
||||
<input type="radio"
|
||||
value="{{ key }}"
|
||||
id="{{ id }}"
|
||||
name="{{ (scope ~ field.name)|fieldName }}"
|
||||
{% if highlight is same as(key) %}class="highlight"{% endif %}
|
||||
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
|
||||
{% if key is same as(value) %}checked="checked"{% endif %}
|
||||
{% if required %}required="required"{% endif %}
|
||||
{% if field.tabindex %}tabindex="{{ field.tabindex }}"{% endif %}
|
||||
/>
|
||||
<label for="{{ id }}">{{ (macro.spanToggle(translation, maxLen)|trim)|raw }}</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,107 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block label %}{% endblock %}
|
||||
|
||||
{% block input %}
|
||||
{% set config = grav.config %}
|
||||
{% set formId = form.id ?: form.name %}
|
||||
|
||||
{# Get configuration values with fallbacks #}
|
||||
{% set site_key = field.turnstile_site_key ?? config.plugins.form.turnstile.site_key %}
|
||||
{% set theme = field.turnstile_theme ?? config.plugins.form.turnstile.theme ?? 'light' %}
|
||||
{% set container_id = 'cf-turnstile-' ~ formId %}
|
||||
{% set init_var = 'turnstile_initialized_' ~ formId %}
|
||||
|
||||
{% if not site_key %}
|
||||
<div class="form-error">Turnstile site key is not set. Please set it in the form field or plugin configuration.</div>
|
||||
{% else %}
|
||||
{# Add a hidden field for the token directly in the INPUT block to ensure it's part of the field #}
|
||||
<input type="hidden" name="cf-turnstile-response" value="" class="turnstile-token" />
|
||||
|
||||
<div class="turnstile-container"
|
||||
data-form-id="{{ formId }}"
|
||||
data-captcha-provider="turnstile"
|
||||
data-sitekey="{{ site_key }}"
|
||||
data-theme="{{ theme }}">
|
||||
<div id="{{ container_id }}" class="cf-turnstile"></div>
|
||||
</div>
|
||||
|
||||
{% do assets.addJs('https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback_' ~ formId ~ '&render=explicit', { 'loading': 'async', 'defer': '' }) %}
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
// Prevent multiple initialization
|
||||
if (window['{{ init_var }}']) {
|
||||
console.log('Turnstile already initialized for form {{ formId }}');
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark as initialized
|
||||
window['{{ init_var }}'] = true;
|
||||
|
||||
// Use unique callback name to avoid conflicts with multiple forms
|
||||
window['onloadTurnstileCallback_{{ formId }}'] = function() {
|
||||
console.log('Turnstile API loaded - initializing widget for {{ formId }}');
|
||||
|
||||
const container = document.getElementById('{{ container_id }}');
|
||||
if (!container) {
|
||||
console.error('Turnstile container #{{ container_id }} not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const form = document.getElementById('{{ formId }}');
|
||||
if (!form) {
|
||||
console.error('Cannot find form #{{ formId }}');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the token input field
|
||||
const tokenField = form.querySelector('input[name="cf-turnstile-response"]');
|
||||
if (!tokenField) {
|
||||
console.error('Token field not found in form #{{ formId }}');
|
||||
}
|
||||
|
||||
// Check if this container already has a widget (avoid duplicates)
|
||||
if (container.querySelector('iframe')) {
|
||||
console.log('Turnstile widget already initialized in this container');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
turnstile.render('#{{ container_id }}', {
|
||||
sitekey: '{{ site_key }}',
|
||||
theme: '{{ theme }}',
|
||||
callback: function(token) {
|
||||
console.log('Turnstile callback fired with token', token.substring(0, 10) + '...');
|
||||
|
||||
// Update the token field
|
||||
if (tokenField) {
|
||||
tokenField.value = token;
|
||||
console.log('Updated token field with value');
|
||||
}
|
||||
},
|
||||
'expired-callback': function() {
|
||||
console.warn('Turnstile token expired');
|
||||
if (tokenField) {
|
||||
tokenField.value = '';
|
||||
}
|
||||
},
|
||||
'error-callback': function(error) {
|
||||
console.error('Turnstile error:', error);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Turnstile render call completed');
|
||||
} catch (e) {
|
||||
console.error('Error initializing Turnstile:', e);
|
||||
}
|
||||
};
|
||||
|
||||
// Check if we can initialize immediately
|
||||
if (document.getElementById('{{ container_id }}') && typeof turnstile !== 'undefined') {
|
||||
window['onloadTurnstileCallback_{{ formId }}']();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,5 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block field %}
|
||||
<input type="hidden" name="__unique_form_id__" value="{{ form.uniqueid() ?? random_string(20) }}" />
|
||||
{% endblock %}
|
||||
@@ -0,0 +1 @@
|
||||
<a href="{{ url(value) }}" target="_blank"><i class="fa fa-link"></i> {{ value|e }}</a>
|
||||
@@ -0,0 +1,9 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="url"
|
||||
{% if field.size %}size="{{ field.size }}"{% endif %}
|
||||
{% if field.minlength is defined or field.validate.min is defined %}minlength="{{ field.minlength | default(field.validate.min) }}"{% endif %}
|
||||
{% if field.maxlength is defined or field.validate.max is defined %}maxlength="{{ field.maxlength | default(field.validate.max) }}"{% endif %}
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,18 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% if field.options %}
|
||||
{% set value = field.options[value] ?: value %}
|
||||
{% endif %}
|
||||
|
||||
{% block input %}
|
||||
<span class="field-value">
|
||||
{% switch field.filter %}
|
||||
{% case 'date' %}
|
||||
{{ value|date }}
|
||||
{% case 'raw' %}
|
||||
{{ value|raw }}
|
||||
{% default %}
|
||||
{{ value }}
|
||||
{% endswitch %}
|
||||
</span>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,6 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="week"
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
34
config/www/user/plugins/form/templates/forms/form.html.twig
Normal file
34
config/www/user/plugins/form/templates/forms/form.html.twig
Normal file
@@ -0,0 +1,34 @@
|
||||
{#
|
||||
DO NOT MODIFY!
|
||||
|
||||
Default layout can be found in form plugin or your theme:
|
||||
|
||||
templates/forms/layouts/form/default-form.html.twig
|
||||
templates/forms/layouts/field/default-field.html.twig
|
||||
templates/forms/layouts/button/default-button.html.twig
|
||||
|
||||
If you want to use multiple different layouts in your site, you can do:
|
||||
|
||||
```
|
||||
form:
|
||||
name: my-form
|
||||
layout: tailwind
|
||||
```
|
||||
|
||||
and copy the above files to:
|
||||
|
||||
templates/forms/layouts/form/tailwind-form.html.twig
|
||||
templates/forms/layouts/field/tailwind-field.html.twig
|
||||
templates/forms/layouts/button/tailwind-button.html.twig
|
||||
|
||||
You can also override individual fields by copying (using text field as an example):
|
||||
|
||||
templates/forms/fields/text/text.html.twig -> templates/forms/fields/text/tailwind-text.html.twig
|
||||
|
||||
#}
|
||||
{% extends "forms/default/form.html.twig" %}
|
||||
|
||||
{% block xhr %}
|
||||
{% include 'forms/layouts/xhr.html.twig' %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{# DEPRECATED: Do not override this file! Use forms/layouts/button/[FORM_LAYOUT]-button.html.twig instead #}
|
||||
|
||||
{% set layout = layout ?? form.layout ?? 'default' %}
|
||||
{% extends ["forms/layouts/button/#{layout}-button.html.twig", 'forms/layouts/button/default-button.html.twig'] %}
|
||||
@@ -0,0 +1,12 @@
|
||||
{% set button_tag %}
|
||||
<button
|
||||
{% block embed_button_core %}{% endblock %}
|
||||
{% block embed_button_classes %}{% endblock %}
|
||||
>{%- block embed_button_content -%}{%- endblock -%}</button>
|
||||
{% endset %}
|
||||
|
||||
{% if button_url %}
|
||||
<a href="{{ button_url|e }}">{{ button_tag|trim|raw }}</a>
|
||||
{% else %}
|
||||
{{ button_tag|trim|raw }}
|
||||
{% endif %}
|
||||
@@ -0,0 +1 @@
|
||||
{% block field_input_classes %}{% endblock %}
|
||||
@@ -0,0 +1,4 @@
|
||||
{# DEPRECATED: Do not override this file! Use forms/layouts/field/[FORM_LAYOUT]-field.html.twig instead #}
|
||||
|
||||
{% set layout = layout ?? form.layout ?? 'default' %}
|
||||
{% extends ["forms/layouts/field/#{layout}-field.html.twig", 'forms/layouts/field/default-field.html.twig'] %}
|
||||
@@ -0,0 +1,53 @@
|
||||
{% block field %}
|
||||
<div class="form-field {{ layout_form_field_outer_classes|trim }} {{ form_field_outer_core|trim}}">
|
||||
{% block contents %}
|
||||
{% if show_label %}
|
||||
<div class="{{- layout_form_field_outer_label_classes -}}">
|
||||
{{- form_field_toggleable -}}
|
||||
<label class="{{ layout_form_field_label_classes }}{{ form_field_label_trim }}" {% if field.id %}for="{{ form_field_for }}"{% endif %}>
|
||||
{%- block label -%}
|
||||
{%- if form_field_help -%}
|
||||
<span class="tooltip" data-tooltip="{{ form_field_help|e }}">{{ form_field_label|raw }}</span>
|
||||
{%- else -%}
|
||||
{{ form_field_label|raw }}
|
||||
{%- endif -%}
|
||||
{%- if form_field_required %}
|
||||
<span class="required">*</span>
|
||||
{%- endif -%}
|
||||
{%- endblock -%}
|
||||
</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="{{ layout_form_field_outer_data_classes }}"
|
||||
{% block global_attributes %}{% endblock %}
|
||||
>
|
||||
{% block group %}
|
||||
{% block input %}
|
||||
<div class="{{ layout_form_field_wrapper_classes }} {{ field.size }}">
|
||||
{% block prepend %}{% endblock prepend %}
|
||||
{% set input_value = value is iterable ? value|join(',') : value|string %}
|
||||
<input
|
||||
name="{{ (scope ~ field.name)|fieldName }}"
|
||||
value="{{ input_value|e }}"
|
||||
{% block input_attributes %}{% endblock %}
|
||||
/>
|
||||
{% block append %}{% endblock append %}
|
||||
{% if inline_errors and errors %}
|
||||
<div class="{{ form_field_inline_error_classes }}">
|
||||
<p class="form-message"><i class="fa fa-exclamation-circle"></i> {{ errors|first|raw }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
{% if field.description is defined %}
|
||||
<div class="{{ form_field_extra_wrapper_classes }}">
|
||||
<span class="form-description">
|
||||
{{ form_field_description|raw }}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,4 @@
|
||||
{# DEPRECATED: Do not override this file! Use forms/layouts/form/[FORM_LAYOUT]-form.html.twig instead #}
|
||||
|
||||
{% set layout = layout ?? form.layout ?? 'default' %}
|
||||
{% extends ["forms/layouts/form/#{layout}-form.html.twig", 'forms/layouts/form/default-form.html.twig'] %}
|
||||
@@ -0,0 +1,9 @@
|
||||
<form
|
||||
{% block embed_form_core %}{% endblock %}
|
||||
{% block embed_form_classes %}{% endblock %}
|
||||
{% block embed_form_custom_attributes %}{% endblock %}
|
||||
>
|
||||
{% block embed_fields %}{% endblock %}
|
||||
{% block embed_buttons %}{% endblock %}
|
||||
</form>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
{% if form.xhr_submit == true %}
|
||||
{# Ensure xhr-submitter.js is loaded BEFORE the inline JS that uses it #}
|
||||
{% do assets.addJs('plugin://form/assets/xhr-submitter.js', {'group': 'bottom', 'priority': 101, 'position': 'before'}) %}
|
||||
{% do assets.addInlineJs("
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// This now primarily sets up the *potential* for XHR submission
|
||||
// It might not attach the listener directly if recaptcha is present
|
||||
attachFormSubmitListener('" ~ form.id ~ "');
|
||||
|
||||
// Re-run captcha initializers *if* the form was loaded via XHR initially
|
||||
// This covers edge cases, might not be strictly needed if captcha script handles DOMContentLoaded
|
||||
const formElement = document.getElementById('" ~ form.id ~ "');
|
||||
if (formElement && window.GravRecaptchaInitializers) {
|
||||
const initializerFuncName = 'initRecaptcha_" ~ form.id ~ "';
|
||||
if (typeof window.GravRecaptchaInitializers[initializerFuncName] === 'function') {
|
||||
// Check if it needs init (e.g., if container exists but no widget/listener)
|
||||
// For simplicity, just call it again; the init function should be idempotent
|
||||
// window.GravRecaptchaInitializers[initializerFuncName]();
|
||||
}
|
||||
}
|
||||
});",
|
||||
{'group': 'bottom', 'priority': 100, 'position': 'before'}) %}
|
||||
{% do assets.addJs('plugin://form/assets/captcha/recaptcha-handler.js', {'group': 'bottom', 'priority': 99, 'position': 'before'}) %}
|
||||
{% do assets.addJs('plugin://form/assets/captcha/turnstile-handler.js', {'group': 'bottom', 'priority': 98, 'position': 'before'}) %}
|
||||
{% endif %}
|
||||
@@ -0,0 +1,8 @@
|
||||
<div class="modular-row form {{ page.header.class }}">
|
||||
{{ content|raw }}
|
||||
{% if config.plugins.form.modular_form_fix %}
|
||||
{% include "forms/form.html.twig" with {form: forms({route: page.route})} %}
|
||||
{% else %}
|
||||
{% include "forms/form.html.twig" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
{% if form.message %}
|
||||
{% set inline_errors = form.inline_errors is not null ? form.inline_errors : config.plugins.form.inline_errors(false) %}
|
||||
{% set status_mapping = {'success':'green', 'error': 'red', 'warning': 'yellow'} %}
|
||||
{% set message = inline_errors and form.messages ? "GRAV.FORM.VALIDATION_FAIL"|t : form.message %}
|
||||
<div class="notices {{ form.status }} {{ status_mapping[form.status] ?: 'green' }}"><p>{{ message|raw }}</p></div>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,3 @@
|
||||
{% set inline_errors = form.inline_errors is not null ? form.inline_errors : config.plugins.form.inline_errors(false) %}
|
||||
{% set message = inline_errors and form.messages ? "GRAV.FORM.VALIDATION_FAIL"|t : form.message %}
|
||||
{{ {'status':form.status, 'message':message}|json_encode|raw }}
|
||||
Reference in New Issue
Block a user