28 | * @copyright Copyright (c) 2014-2016, Gawain Lynch
29 | * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0
30 | * @license http://opensource.org/licenses/LGPL-3.0 GNU Lesser General Public License 3.0
31 | */
32 | class BoltFormsExtension extends Extension
33 | {
34 | /**
35 | * {@inheritdoc}
36 | */
37 | public function getFunctions()
38 | {
39 | $safe = ['is_safe' => ['html', 'is_safe_callback' => true]];
40 | $env = ['needs_environment' => true];
41 |
42 | return [
43 | new Twig_SimpleFunction('boltforms', [BoltFormsRuntime::class, 'twigBoltForms'], $safe + $env),
44 | new Twig_SimpleFunction('boltforms_uploads', [BoltFormsRuntime::class, 'twigBoltFormsUploads']),
45 | ];
46 | }
47 |
48 | /**
49 | * {@inheritdoc}
50 | */
51 | public function getTests()
52 | {
53 | return array(
54 | new Twig_SimpleTest('rootform', [BoltFormsRuntime::class, 'twigIsRootForm']),
55 | );
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/Twig/RuntimeLoader.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class RuntimeLoader implements RuntimeLoaderInterface
16 | {
17 | /** @var Container */
18 | private $container;
19 | /** @var array */
20 | private $mapping;
21 |
22 | /**
23 | * Constructor.
24 | *
25 | * @param Container $container
26 | * @param array $mapping
27 | */
28 | public function __construct(Container $container, array $mapping)
29 | {
30 | $this->container = $container;
31 | $this->mapping = $mapping;
32 | }
33 |
34 | /**
35 | * {@inheritdoc}
36 | */
37 | public function load($class)
38 | {
39 | if (!isset($this->mapping[$class])) {
40 | return null;
41 | }
42 |
43 | return $this->container[$this->mapping[$class]];
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/templates/README.md:
--------------------------------------------------------------------------------
1 | BoltForms Templates
2 | ===================
3 |
4 | In the subdirectories here, you will find the following groups of template
5 | defaults:
6 |
7 | * `asset` — Web assets such as the AJAX handler and default CSS
8 | * `email` — Email notification body and subject line templates
9 | * `feedback` — General user messages, and debug feedback
10 | * `file` — Uploaded file browser
11 | * `form` — Form specific rendering and macro templates
12 |
--------------------------------------------------------------------------------
/templates/_macros.twig:
--------------------------------------------------------------------------------
1 | {# Render a single line label #}
2 | {% macro label(label) %}
3 | {{ label }}:
4 | {% endmacro %}
5 |
6 | {# Render a list item value #}
7 | {% macro value(value) %}
8 | {{ value }}
9 | {% endmacro %}
10 |
11 | {# Render a label/value pair #}
12 | {% macro label_value(label, value) %}
13 | {{ label }}: {{ value }}
14 | {% endmacro %}
15 |
16 | {% macro label_array(label, value) %}
17 | {{ label }}:
18 |
19 | {% for val in value %}
20 | {{ _self.value(val) }}
21 | {% endfor %}
22 |
23 | {% endmacro %}
24 |
--------------------------------------------------------------------------------
/templates/asset/_ajax.twig:
--------------------------------------------------------------------------------
1 |
40 |
--------------------------------------------------------------------------------
/templates/asset/_css.twig:
--------------------------------------------------------------------------------
1 | {#
2 | # Passed in variables:
3 | # * webpath — BoltForms web asset path
4 | #}
5 |
6 |
--------------------------------------------------------------------------------
/templates/asset/_js.twig:
--------------------------------------------------------------------------------
1 | {#
2 | # Passed in variables:
3 | # * webpath — BoltForms web asset path
4 | #}
5 |
6 |
--------------------------------------------------------------------------------
/templates/email/_blocks.twig:
--------------------------------------------------------------------------------
1 | {% block submission_summary %}
2 | {%- for fieldname, values in data %}
3 | {%- set field = attribute(fields, fieldname) %}
4 | {%- set label = field.options.label|default(fieldname) %}
5 |
6 |
7 |
8 | {%- if values is iterable %}
9 |
10 | {{ block('field_label') }}
11 | {{ block('value_array') }}
12 |
13 | {% elseif values.timestamp is defined %}
14 | {%- set value = values %}
15 | {{ block('field_label') }}{{ block('field_date') }}
16 | {%- else %}
17 | {%- set value = values %}
18 | {{ block('field_label') }}{{ block('field_value') }}
19 |
20 | {%- endif %}
21 |
22 |
23 |
24 | {%- endfor %}
25 | {% endblock %}
26 |
27 | {% block field_label %}
28 | {%- if label is not empty %}{{ label }}: {% endif %}
29 | {% endblock %}
30 |
31 | {% block field_value %}
32 | {{- value }}
33 | {% endblock %}
34 |
35 | {% block field_date %}
36 | {{- value|date() }}
37 | {% endblock %}
38 |
39 | {% block file_field_value %}
40 | {% if value == '' %}
41 | {{ name }}
42 | {% else %}
43 | {{ name }}
44 | {% endif %}
45 | {% endblock %}
46 |
47 | {% block value_array %}
48 |
49 | {%- for name, value in values %}
50 | {%- if value is iterable %}
51 | {{ block('value_array') }}
52 | {%- else %}
53 |
54 |
55 | {%- if field.type == 'file' %}
56 | {{ block('file_field_value') }}
57 | {%- else %}
58 | {{ block('field_value') }}
59 | {%- endif %}
60 |
61 |
62 | {%- endif %}
63 | {%- endfor %}
64 |
65 |
66 | {% endblock %}
67 |
--------------------------------------------------------------------------------
/templates/email/email.twig:
--------------------------------------------------------------------------------
1 | {#
2 | # Passed in variables:
3 | # fields — Field data from the form configuration
4 | # config — Email configuration data
5 | # data — POSTed data
6 | # metadata — Form meta data
7 | #}
8 | Hi,
9 |
10 | Somebody used the form on {{ paths.currenturl }} to send you a message.
11 |
12 | The posted data is as follows:
13 |
14 | {{ block('submission_summary', '@BoltForms/email/_blocks.twig') }}
15 |
16 | Sent by the BoltForms extension for Bolt .
17 |
--------------------------------------------------------------------------------
/templates/email/subject.twig:
--------------------------------------------------------------------------------
1 | {#
2 | # Passed in variables:
3 | # subject — The configured subject
4 | # data — Posted data
5 | # metadata — Form meta data
6 | #}
7 | {% spaceless %}
8 | {{ subject }}
9 | {% endspaceless %}
10 |
--------------------------------------------------------------------------------
/templates/feedback/_exception.twig:
--------------------------------------------------------------------------------
1 | {#
2 | #}
3 | {% block boltforms_css %}
4 | {{ include(templates.css) }}
5 | {% endblock boltforms_css %}
6 |
7 |
8 | {{ include(templates.messages) }}
9 |
10 |
--------------------------------------------------------------------------------
/templates/feedback/_messages.twig:
--------------------------------------------------------------------------------
1 | {#
2 | # Informational message blocks
3 | #}
4 | {% block messages_debug %}
5 | {% if debug %}
6 | [Debug] Notification debug mode enabled!
7 |
8 | {% if messages.debug and app.request.get(formname) %}
9 | {% for debug in messages.debug %}
10 | [Debug] {{ debug|nl2br }}
11 | {% endfor %}
12 | {% endif %}
13 | {% endif %}
14 | {% endblock %}
15 |
16 | {% block messages_error %}
17 | {% if messages.error and app.request.get(formname) %}
18 | {% for error in messages.error %}
19 | {{ error }}
20 | {% endfor %}
21 | {% endif %}
22 | {% endblock %}
23 |
24 | {% block messages_info %}
25 | {% if messages.info and app.request.get(formname) %}
26 | {% for info in messages.info %}
27 | {{ info }}
28 | {% endfor %}
29 | {% endif %}
30 | {% endblock %}
31 |
--------------------------------------------------------------------------------
/templates/file/browser.twig:
--------------------------------------------------------------------------------
1 | {#
2 | # Passed in variables:
3 | # webpath - URI for the default web assets
4 | # base_uri - Relative base URI for BoltForms download controller
5 | # directories - Symfony SplFileInfo object of found directories
6 | # files - Symfony SplFileInfo object of found files
7 | #}
8 |
9 | {% block boltforms_css %}
10 |
11 | {% endblock boltforms_css %}
12 |
13 |
33 |
--------------------------------------------------------------------------------
/templates/form/_recaptcha.twig:
--------------------------------------------------------------------------------
1 |
2 | {% macro recaptcha(recaptcha) %}
3 | {% if recaptcha.enabled %}
4 | {% if not recaptcha.valid %}
5 |
8 | {% endif %}
9 |
10 | {% if recaptcha.type == 'v2' %}
11 |
17 | {% endif %}
18 | {% endif %}
19 | {% endmacro %}
20 |
--------------------------------------------------------------------------------
/tests/data/bolt-logo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bolt/boltforms/de80a4f5549fe642309149b83e819d5796d683ba/tests/data/bolt-logo
--------------------------------------------------------------------------------
/tests/data/bolt-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bolt/boltforms/de80a4f5549fe642309149b83e819d5796d683ba/tests/data/bolt-logo.png
--------------------------------------------------------------------------------
/web/boltforms.css:
--------------------------------------------------------------------------------
1 | div.boltforms-row {
2 | margin: 0.75em 0;
3 | }
4 |
5 | .boltform label.required:after {
6 | content: " *";
7 | color: #F00;
8 | font-weight: bold;
9 | }
10 |
11 | p.boltform-debug, p.boltform-error, li.boltform-error, p.boltform-info, p.boltform-message {
12 | padding: 8px 35px 8px 14px;
13 | margin-bottom: 20px;
14 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
15 | -webkit-border-radius: 4px;
16 | -moz-border-radius: 4px;
17 | border-radius: 4px;
18 | }
19 |
20 | p.boltform-debug {
21 | border: 1px solid #EED3D7;
22 | color: #B94A48;
23 | background-color: #F2DEDE;
24 | }
25 |
26 | p.boltform-error {
27 | border: 1px solid #EED3D7;
28 | color: #B94A48;
29 | background-color: #F2DEDE;
30 | }
31 |
32 | ul.boltform-error {
33 | margin-left: 0;
34 | padding-left: 0;
35 | }
36 |
37 | li.boltform-error {
38 | list-style: none;
39 | border: 1px solid #EED3D7;
40 | color: #B94A48;
41 | background-color: #F2DEDE;
42 | }
43 |
44 | p.boltform-message {
45 | border: 1px solid #D6E9C6;
46 | color: #468847;
47 | background-color: #DFF0D8;
48 | }
49 |
50 | .boltforms-preview-image {
51 | display: inline-block;
52 | }
53 |
--------------------------------------------------------------------------------
/web/boltforms.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Closure to handled image specific uploads
3 | * @param file
4 | * @param preview
5 | */
6 | var $handleImage = function (file, preview) {
7 | var reader = new FileReader();
8 | reader.onload = function (e) {
9 | var img = new Image();
10 | img.src = e.target.result;
11 | img.onload = function () {
12 | var canvas = document.createElement('canvas');
13 | var ctx = canvas.getContext('2d');
14 |
15 | ctx.clearRect(0, 0, canvas.width, canvas.height);
16 | img.width = 128;
17 | img.height = 128;
18 | canvas.width = img.width;
19 | canvas.height = img.height;
20 | ctx.drawImage(img, 0, 0, img.width, img.height);
21 |
22 | var upload = document.createElement('div');
23 | upload.className = 'boltforms-preview-image';
24 | upload.appendChild(img);
25 |
26 | preview.appendChild(upload);
27 | }
28 | };
29 | reader.readAsDataURL(file);
30 | };
31 |
32 | /**
33 | * Handler for file uploads.
34 | *
35 | * @param files
36 | * @param preview
37 | */
38 | function handleFiles(files, preview) {
39 | preview = document.getElementById(preview);
40 | preview.innerHTML = '';
41 |
42 | for (var i = 0; i < files.length; i++) {
43 | var file = files[i];
44 | var imageType = /^image\//;
45 |
46 | if (imageType.test(file.type)) {
47 | $handleImage(file, preview);
48 | } else {
49 | console.debug(file.type);
50 | }
51 | }
52 | }
53 |
54 |
55 | /**
56 | * Polyfill for browsers that don't support element.closest()
57 | * We need to use this to find the relevant form for a recaptcha-protected submit button.
58 | *
59 | */
60 |
61 | if (window.Element && !Element.prototype.closest) {
62 | Element.prototype.closest =
63 | function(s) {
64 | var matches = (this.document || this.ownerDocument).querySelectorAll(s),
65 | i,
66 | el = this;
67 | do {
68 | i = matches.length;
69 | while (--i >= 0 && matches.item(i) !== el) {};
70 | } while ((i < 0) && (el = el.parentElement));
71 | return el;
72 | };
73 | }
74 |
75 | function invisibleRecaptchaOnLoad() {
76 |
77 |
78 | function createHiddenElement(name, value) {
79 | var input = document.createElement("input");
80 | input.setAttribute("type", "hidden");
81 | input.setAttribute("name", name);
82 | input.setAttribute("value", value);
83 | return input;
84 | }
85 |
86 | var els = document.getElementsByClassName('g-recaptcha-button');
87 | for (var i = 0; i < els.length; ++i) {
88 | var buttonElement = els[i];
89 | grecaptcha.render(buttonElement, {
90 | sitekey: buttonElement.getAttribute('data-sitekey'),
91 | size: 'invisible',
92 | callback: function(token) {
93 | if (token) {
94 | buttonElement.closest('form').appendChild(createHiddenElement('g-recaptcha-response', token));
95 | buttonElement.closest('form').submit();
96 | }
97 | }
98 | });
99 | }
100 | }
101 |
102 |
103 |
--------------------------------------------------------------------------------