first commit
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
|
||||
"rules": {
|
||||
"accessor-pairs": 2,
|
||||
"array-bracket-spacing": 0,
|
||||
"block-scoped-var": 0,
|
||||
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
|
||||
"camelcase": 0,
|
||||
"comma-dangle": [2, "never"],
|
||||
"comma-spacing": [2, { "before": false, "after": true }],
|
||||
"comma-style": [2, "last"],
|
||||
"complexity": 0,
|
||||
"computed-property-spacing": 0,
|
||||
"consistent-return": 0,
|
||||
"consistent-this": 0,
|
||||
"constructor-super": 2,
|
||||
"curly": [2, "multi-line"],
|
||||
"default-case": 0,
|
||||
"dot-location": [2, "property"],
|
||||
"dot-notation": 0,
|
||||
"eol-last": 2,
|
||||
"eqeqeq": [2, "allow-null"],
|
||||
"func-names": 0,
|
||||
"func-style": 0,
|
||||
"generator-star-spacing": [2, { "before": true, "after": true }],
|
||||
"guard-for-in": 0,
|
||||
"handle-callback-err": [2, "^(err|error)$" ],
|
||||
"indent": [2, 4, { "SwitchCase": 1 }],
|
||||
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
|
||||
"linebreak-style": 0,
|
||||
"lines-around-comment": 0,
|
||||
"max-nested-callbacks": 0,
|
||||
"new-cap": [2, { "newIsCap": true, "capIsNew": false }],
|
||||
"new-parens": 2,
|
||||
"newline-after-var": 0,
|
||||
"no-alert": 0,
|
||||
"no-array-constructor": 2,
|
||||
"no-caller": 2,
|
||||
"no-catch-shadow": 0,
|
||||
"no-cond-assign": 2,
|
||||
"no-console": 0,
|
||||
"no-constant-condition": 0,
|
||||
"no-continue": 0,
|
||||
"no-control-regex": 2,
|
||||
"no-debugger": 2,
|
||||
"no-delete-var": 2,
|
||||
"no-div-regex": 0,
|
||||
"no-dupe-args": 2,
|
||||
"no-dupe-keys": 2,
|
||||
"no-duplicate-case": 2,
|
||||
"no-else-return": 0,
|
||||
"no-empty": 0,
|
||||
"no-empty-character-class": 2,
|
||||
"no-eq-null": 0,
|
||||
"no-eval": 2,
|
||||
"no-ex-assign": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-extra-bind": 2,
|
||||
"no-extra-boolean-cast": 2,
|
||||
"no-extra-parens": 0,
|
||||
"no-extra-semi": 0,
|
||||
"no-fallthrough": 2,
|
||||
"no-floating-decimal": 2,
|
||||
"no-func-assign": 2,
|
||||
"no-implied-eval": 2,
|
||||
"no-inline-comments": 0,
|
||||
"no-inner-declarations": [2, "functions"],
|
||||
"no-invalid-regexp": 2,
|
||||
"no-irregular-whitespace": 2,
|
||||
"no-iterator": 2,
|
||||
"no-label-var": 2,
|
||||
"no-labels": 2,
|
||||
"no-lone-blocks": 2,
|
||||
"no-lonely-if": 0,
|
||||
"no-loop-func": 0,
|
||||
"no-mixed-requires": 0,
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
"no-multi-spaces": 2,
|
||||
"no-multi-str": 2,
|
||||
"no-multiple-empty-lines": [2, { "max": 1 }],
|
||||
"no-native-reassign": 2,
|
||||
"no-negated-in-lhs": 2,
|
||||
"no-nested-ternary": 0,
|
||||
"no-new": 2,
|
||||
"no-new-func": 0,
|
||||
"no-new-object": 2,
|
||||
"no-new-require": 2,
|
||||
"no-new-wrappers": 2,
|
||||
"no-obj-calls": 2,
|
||||
"no-octal": 2,
|
||||
"no-octal-escape": 2,
|
||||
"no-param-reassign": 0,
|
||||
"no-path-concat": 0,
|
||||
"no-process-env": 0,
|
||||
"no-process-exit": 0,
|
||||
"no-proto": 0,
|
||||
"no-redeclare": 2,
|
||||
"no-regex-spaces": 2,
|
||||
"no-restricted-modules": 0,
|
||||
"no-return-assign": 2,
|
||||
"no-script-url": 0,
|
||||
"no-self-compare": 2,
|
||||
"no-sequences": 2,
|
||||
"no-shadow": 0,
|
||||
"no-shadow-restricted-names": 2,
|
||||
"no-spaced-func": 2,
|
||||
"no-sparse-arrays": 2,
|
||||
"no-sync": 0,
|
||||
"no-ternary": 0,
|
||||
"no-this-before-super": 2,
|
||||
"no-throw-literal": 2,
|
||||
"no-trailing-spaces": 2,
|
||||
"no-undef": 2,
|
||||
"no-undef-init": 2,
|
||||
"no-undefined": 0,
|
||||
"no-underscore-dangle": 0,
|
||||
"no-unexpected-multiline": 2,
|
||||
"no-unneeded-ternary": 2,
|
||||
"no-unreachable": 2,
|
||||
"no-unused-expressions": 0,
|
||||
"no-unused-vars": [2, { "vars": "all", "args": "none" }],
|
||||
"no-use-before-define": 0,
|
||||
"no-var": 0,
|
||||
"no-void": 0,
|
||||
"no-warning-comments": 0,
|
||||
"no-with": 2,
|
||||
"object-curly-spacing": 0,
|
||||
"object-shorthand": 0,
|
||||
"one-var": [2, { "initialized": "never" }],
|
||||
"operator-assignment": 0,
|
||||
"operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }],
|
||||
"padded-blocks": 0,
|
||||
"prefer-const": 0,
|
||||
"quote-props": 0,
|
||||
"quotes": [2, "single", "avoid-escape"],
|
||||
"radix": 2,
|
||||
"semi": [2, "always"],
|
||||
"semi-spacing": 0,
|
||||
"sort-vars": 0,
|
||||
"keyword-spacing": [2, {"after": true, "overrides": {"throw": { "after": true}, "return": { "before": true }}}],
|
||||
"space-before-blocks": [2, "always"],
|
||||
"space-before-function-paren": [2, "never"],
|
||||
"space-in-parens": [2, "never"],
|
||||
"space-infix-ops": 2,
|
||||
"space-unary-ops": [2, { "words": true, "nonwords": false }],
|
||||
"spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!"] }],
|
||||
"strict": 0,
|
||||
"use-isnan": 2,
|
||||
"valid-jsdoc": 0,
|
||||
"valid-typeof": 2,
|
||||
"vars-on-top": 0,
|
||||
"wrap-iife": [2, "any"],
|
||||
"wrap-regex": 0,
|
||||
"yoda": [2, "never"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# OS Generated
|
||||
.DS_Store*
|
||||
/.idea
|
||||
node_modules
|
||||
*.js.map
|
||||
@@ -0,0 +1,559 @@
|
||||
# v2.11.2
|
||||
## 01/22/2018
|
||||
|
||||
1. [](#new)
|
||||
* Added support for markdown in all form fields for `label`, `help`, and `description` when `markdown: true` is set on field
|
||||
|
||||
# v2.11.1
|
||||
## 12/18/2017
|
||||
|
||||
1. [](#improved)
|
||||
* Updated default fields to make them more consistent with class names
|
||||
|
||||
# v2.11.0
|
||||
## 12/05/2017
|
||||
|
||||
1. [](#new)
|
||||
* Added ability to set `novalidate: true` on form definition to turn off all HTML5 form validation
|
||||
1. [](#improved)
|
||||
* Improved logic to handle dynamically added forms to be more reliable
|
||||
* Added Dutch Translation [#207](https://github.com/getgrav/grav-plugin-form/pull/207)
|
||||
* Improved both HTML and JSON error output by utilizing `form.status`
|
||||
* Code Cleanup
|
||||
1. [](#bugfix)
|
||||
* Fix AJAX response message and wrong status [#211](https://github.com/getgrav/grav-plugin-form/pull/211)
|
||||
* Escaped YAML to form save action to prevent parsing errors [#206](https://github.com/getgrav/grav-plugin-form/pull/206)
|
||||
* Fixed RU translations [#204](https://github.com/getgrav/grav-plugin-form/pull/204)
|
||||
* Fixed nonce check fail not setting status to `error` [#213](https://github.com/getgrav/grav-plugin-form/issues/213)
|
||||
* Fixed validation fail not setting status to `error` [#209](https://github.com/getgrav/grav-plugin-form/issues/209)
|
||||
* Catch ValidationException to avoid potential fatal error
|
||||
* Fixed regression issue on reset fields
|
||||
* Removed `required` attribute in individual checkboxes as it forces all to be checked
|
||||
* Security fix to ensure file uploads are not manipulated mid-post - thnx @FLH!
|
||||
|
||||
# v2.10.0
|
||||
## 10/26/2017
|
||||
|
||||
1. [](#new)
|
||||
* Added ability to 'remember' field values in cookie between submissions [#200](https://github.com/getgrav/grav-plugin-form/pull/200)
|
||||
1. [](#improved)
|
||||
* Added back improved `filesize` option that falls back to PHP file upload limits by default [#202](https://github.com/getgrav/grav-plugin-form/issues/202)
|
||||
* Added missing file upload options into blueprints and language files
|
||||
* Added the ability for a form to have an `http_response_code` and use it for `form-messages.html.twig` (requires Grav v1.3.6+)
|
||||
|
||||
# v2.9.3
|
||||
## 10/11/2017
|
||||
|
||||
1. [](#improved)
|
||||
* Removed `filesize` plugin configuration in favor of `system.media.upload_limit`
|
||||
* Consolidated `field.classes` and `field.wrapper_classes` in radio/checkbox/checkboxes [#193](https://github.com/getgrav/grav-plugin-form/issues/)
|
||||
* Remove trailing slash from form action [#195](https://github.com/getgrav/grav-plugin-form/issues/195)
|
||||
* Improved `honeypot` validation check [#198](https://github.com/getgrav/grav-plugin-form/issues/198)
|
||||
|
||||
# v2.9.2
|
||||
## 09/30/2017
|
||||
|
||||
1. [](#improved)
|
||||
* Improved Polish translation
|
||||
1. [](#bugfix)
|
||||
* Added missing `@input: false` attributes to some non-display fields [#189](https://github.com/getgrav/grav-plugin-form/issues/189)
|
||||
|
||||
# v2.9.1
|
||||
## 09/14/2017
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed backwards compatibility issue with conditional field [#188](https://github.com/getgrav/grav-plugin-form/pull/188)
|
||||
|
||||
# v2.9.0
|
||||
## 09/07/2017
|
||||
|
||||
1. [](#new)
|
||||
* Added **Refresh Prevention** capabilities (Not enabled by default) [#184](https://github.com/getgrav/grav-plugin-form/issues/184)
|
||||
* Added support for field `attributes` [#176](https://github.com/getgrav/grav-plugin-form/pull/176)
|
||||
* Added global variables for setting form classes
|
||||
* Added support for new `select_optgroup` form field [#165](https://github.com/getgrav/grav-plugin-form/issues/165)
|
||||
1. [](#improved)
|
||||
* Moved messages output into partial to allow style overriding
|
||||
* Logic cleanup
|
||||
* Updated Italian and Russian translations
|
||||
1. [](#bugfix)
|
||||
* Fixed an issue with conditional field not always displaying properly
|
||||
* Only add Twig form variable if not already set
|
||||
* Fixed issue with multiple forms on a page failing on Captcha client-side validation [#182](https://github.com/getgrav/grav-plugin-form/issues/182)
|
||||
* Fixed issue with Ajax forms return full form HTML on error [#163](https://github.com/getgrav/grav-plugin-form/issues/163)
|
||||
|
||||
# v2.8.2
|
||||
## 08/18/2017
|
||||
|
||||
1. [](#new)
|
||||
* Added new `columns` and `column` fields for controlled form layout
|
||||
|
||||
# v2.8.1
|
||||
## 08/15/2017
|
||||
|
||||
1. [](#improved)
|
||||
* Added extra class support to the default field for more flexible styling
|
||||
|
||||
# v2.8.0
|
||||
## 07/16/2017
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed a typo in the spanish translation [#167](https://github.com/getgrav/grav-plugin-form/pull/167)
|
||||
|
||||
# v2.8.0-rc.2
|
||||
## 06/22/2017
|
||||
|
||||
1. [](#improved)
|
||||
* Add default client-side validation for captcha, with error popup [#139](https://github.com/getgrav/grav-plugin-form/issues/139)
|
||||
* Added key observe for select
|
||||
* Added Czech translation
|
||||
1. [](#bugfix)
|
||||
* Bug fix for radio type form field [#154](https://github.com/getgrav/grav-plugin-form/pull/154)
|
||||
* Remove double escaping [#155](https://github.com/getgrav/grav-plugin-form/issues/154)
|
||||
|
||||
# v2.8.0-rc.1
|
||||
## 05/22/2017
|
||||
|
||||
1. [](#new)
|
||||
* Bundled as RC release for Grav/Admin RC releases
|
||||
|
||||
# v2.7.1
|
||||
## 05/22/2017
|
||||
|
||||
1. [](#improved)
|
||||
* Force modular sub-pages with forms to set `$never_cache_twig = true` to improve form processing reliability [#153](https://github.com/getgrav/grav-plugin-form/issues/153)
|
||||
* Use new `Utils::getPagePathFromToken()` method
|
||||
|
||||
# v2.7.0
|
||||
## 05/16/2017
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fix issue with dynamically added forms (Registration, Profile, Comments, etc) not processed [#149](https://github.com/getgrav/grav-plugin-form/issues/149)
|
||||
* Fixed issue with nested values not being repopulated on form error [#140](https://github.com/getgrav/grav-plugin-form/issues/140)
|
||||
|
||||
# v2.6.0
|
||||
## 05/04/2017
|
||||
|
||||
1. [](#new)
|
||||
* Allow form item replacement in redirect location [#144](https://github.com/getgrav/grav-plugin-form/issues/144)
|
||||
1. [](#bugfix)
|
||||
* Fix regression with file uploads introduced in 2.5.0
|
||||
|
||||
# v2.5.0
|
||||
## 04/24/2017
|
||||
|
||||
1. [](#new)
|
||||
* Support proper form handling with nested fields [#141](https://github.com/getgrav/grav-plugin-form/issues/141)
|
||||
1. [](#bugfix)
|
||||
* Added check for valid Grav forms before trying to create a form object
|
||||
|
||||
# v2.4.0
|
||||
## 04/19/2017
|
||||
|
||||
1. [](#new)
|
||||
* Added the ability for front-end forms to use advanced blueprint features such as `data-*@` and `config-*@`
|
||||
* Added support for dynamically added pages to process forms properly
|
||||
* Added a new avatar field for displaying account avatar
|
||||
* Added method to get all `data` from a form
|
||||
* Support `task` in button types
|
||||
1. [](#improved)
|
||||
* Added `step` to range field [#136](https://github.com/getgrav/grav-plugin-form/issues/136)
|
||||
* Added a new default ajax handler twig template
|
||||
* Moved twig events to always process even if forms are not defined
|
||||
* Some code cleanup
|
||||
* Handle `null` with session-based form
|
||||
* Added support for append/prepend to number field
|
||||
1. [](#bugfix)
|
||||
* Always process form events as long as a `$_POST` exists [login #101](https://github.com/getgrav/grav-plugin-login/issues/101)
|
||||
* Various fixes for `file` field
|
||||
* Allow manually added pages to process forms and upload files
|
||||
* Fixed issue with nested fileds not showing up in `data.*.twig` templates
|
||||
|
||||
# v2.3.1
|
||||
## 03/23/2017
|
||||
|
||||
1. [](#bugfix)
|
||||
* Only include `outerclasses` DIV if defined [#135](https://github.com/getgrav/grav-plugin-form/issues/135)
|
||||
|
||||
# v2.3.0
|
||||
## 03/17/2017
|
||||
|
||||
1. [](#new)
|
||||
* Ability to process any form on any page via `action:`. Super useful if you want to handle form processing on some other non-form page (or Ajax)
|
||||
* Added the ability for the form to set the `template:` to use to render the form processing response.
|
||||
1. [](#bugfix)
|
||||
* Fix `number` field so it works with min value `0` [#130](https://github.com/getgrav/grav-plugin-form/issues/130)
|
||||
|
||||
# v2.2.0
|
||||
## 03/13/2017
|
||||
|
||||
1. [](#new)
|
||||
* Added new `fieldset` form field [#125](https://github.com/getgrav/grav-plugin-form/issues/125)
|
||||
* Added new `conditional form field` to show fields only if some `condition` is set
|
||||
1. [](#improved)
|
||||
* Added the option to have outer-classes on buttons [#124](https://github.com/getgrav/grav-plugin-form/issues/124)
|
||||
* Added the option to disable fields label if not defined [#126](https://github.com/getgrav/grav-plugin-form/issues/126)
|
||||
|
||||
# v2.1.1
|
||||
## 02/17/2017
|
||||
|
||||
1. [](#improved)
|
||||
* Better default output for select, checkbox and checkboxes fields in the form destination page and in the emails sent via form submit [#121](https://github.com/getgrav/grav-plugin-form/issues/121)
|
||||
|
||||
|
||||
# v2.1.0
|
||||
## 02/10/2017
|
||||
|
||||
1. [](#improved)
|
||||
* Reworked logic so form caching is based on `Pages::getPagesCacheId()`
|
||||
* Added `url` option for button field
|
||||
1. [](#bugfix)
|
||||
* Fixed issue with `honeypot` field not throwing exception properly
|
||||
|
||||
# v2.0.10
|
||||
## 02/08/2017
|
||||
|
||||
1. [](#improved)
|
||||
* Optimistically set 'status' to `success` when requesting a form via Ajax. Form processing listeners should take care of setting status to something else
|
||||
1. [](#bugfix)
|
||||
* File uploads are now adding a `__form-file-uploader__` POST field to better allow identifying them with Ajax
|
||||
* Require jQuery when using the File field, as it's needed by the form.min.js file required in the file upload functionality
|
||||
|
||||
# v2.0.9
|
||||
## 01/24/2017
|
||||
|
||||
1. [](#bugfix)
|
||||
* Translate the labels in data.html.twig [https://github.com/getgrav/grav-plugin-comments/issues/38](https://github.com/getgrav/grav-plugin-comments/issues/38)
|
||||
* Fixed file input when `System` > `Twig` > `Autoescape` is set to `Yes`
|
||||
|
||||
# v2.0.8
|
||||
## 12/13/2016
|
||||
|
||||
1. [](#new)
|
||||
* RC released as stable
|
||||
* Added a new `honeypot` field for form anti-spam protection
|
||||
|
||||
# v2.0.8-rc.1
|
||||
## 11/26/2016
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed Forms 2.0 changes for registration form [#101](https://github.com/getgrav/grav-plugin-form/issues/101)
|
||||
* Fixed errant reference to Grav DI container in Form#getPagePathFromToken [#105](https://github.com/getgrav/grav-plugin-form/issues/105)
|
||||
* Fixed issue with spacer fields being displayed first, not in order [#104](https://github.com/getgrav/grav-plugin-form/issues/104)
|
||||
|
||||
# v2.0.7
|
||||
## 11/17/2016
|
||||
|
||||
1. [](#improved)
|
||||
* Added method to set all data in a form
|
||||
* Added params to form action URL
|
||||
* Added ability to add ids to buttons and to set them disabled
|
||||
1. [](#bugfix)
|
||||
* Moved Files Upload GC logic to function in front-end only
|
||||
|
||||
# v2.0.6
|
||||
## 10/19/2016
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed translations for `display` field
|
||||
* Fixed [#95](https://github.com/getgrav/grav-plugin-form/issues/95) multilanguage forms submission
|
||||
* Fixed duplicate textarea class tag [#98](https://github.com/getgrav/grav-plugin-form/issues/98)
|
||||
|
||||
# v2.0.5
|
||||
## 09/15/2016
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fix passing updating the header through event, no need for return value
|
||||
|
||||
# v2.0.4
|
||||
## 09/15/2016
|
||||
|
||||
1. [](#improved)
|
||||
* Allow filling the page header form dynamically (e.g. use case: Comments plugin)
|
||||
|
||||
# v2.0.3
|
||||
## 09/12/2016
|
||||
|
||||
1. [](#improved)
|
||||
* Use `Page::slug()` for form name if not set in the form itself (better backwards compatibility)
|
||||
|
||||
# v2.0.2
|
||||
## 09/08/2016
|
||||
|
||||
1. [](#improved)
|
||||
* Added support for Grav's autoescape twig setting
|
||||
* Allow to add additional markup fields in form and field twig overrides
|
||||
* Updated the french language translation
|
||||
|
||||
# v2.0.1
|
||||
## 09/07/2016
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed a backwards compatibility issue with Admin forms
|
||||
|
||||
# v2.0.0
|
||||
## 09/07/2016
|
||||
|
||||
1. [](#new)
|
||||
* Forms now supports multiple forms per page!
|
||||
* Access forms from any other page within the current page
|
||||
* Instantiate forms directly in page content with Twig processing enabled
|
||||
* New Twig function to get forms data from any other page
|
||||
* Ability to use Twig in saved filename
|
||||
* Reworked the `file` field. All files get uploaded via Ajax and are stored upon Submit. Fully backward compatible, `file` field now includes also a `limit` and `filesize` option. The former determines how many files are allowed to be uploaded when in combination with `multiple: true` (default: 10), the latter determines the file size limit (in MB) allowed for each file (default: 5MB)
|
||||
1. [](#improved)
|
||||
* Added several missing HTML5 form input field types [#87](https://github.com/getgrav/grav-plugin-form/issues/87)
|
||||
* Added Support for CSS id in form definition
|
||||
|
||||
# v1.3.2
|
||||
## 08/10/2016
|
||||
|
||||
1. [](#improved)
|
||||
* Added Romanian translation
|
||||
1. [](#bugfix)
|
||||
* Fixed an issue with Recaptcha secret throwing errors [#84](https://github.com/getgrav/grav-plugin-form/pull/84)
|
||||
|
||||
# v1.3.1
|
||||
## 07/27/2016
|
||||
|
||||
1. [](#improved)
|
||||
* Added support for multiple emails in `email` field (add `multiple: true` to enable)
|
||||
1. [](#bugfix)
|
||||
* Fixed backward incompatibility with forms submission and data retrieval [getgrav/grav#933](https://github.com/getgrav/grav/issues/933)
|
||||
|
||||
# v1.3.0
|
||||
## 07/14/2016
|
||||
|
||||
1. [](#improved)
|
||||
* When uploading a file through a form, if the file is already existing prepend the current day and time to the filename instead of overwriting it.
|
||||
|
||||
# v1.3.0-rc.4
|
||||
## 06/21/2016
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed running on Grav 1.0.x
|
||||
|
||||
# v1.3.0-rc.3
|
||||
## 06/17/2016
|
||||
|
||||
1. [](#new)
|
||||
* Set hints for checkboxes options and allow field descriptions
|
||||
|
||||
# v1.3.0-rc.2
|
||||
## 06/08/2016
|
||||
|
||||
1. [](#new)
|
||||
* Allow to process Twig in a hidden field, by setting `evaluate: true`
|
||||
|
||||
# v1.3.0-rc.1
|
||||
## 06/01/2016
|
||||
|
||||
1. [](#improved)
|
||||
* French updated
|
||||
|
||||
# v1.3.0-beta.6
|
||||
## 05/23/2016
|
||||
|
||||
1. [](#new)
|
||||
* Added support for advanced blueprint functionality in forms
|
||||
* Added site-wide form options to set Google Captcha site + secret keys [#34](https://github.com/getgrav/grav-plugin-form/pull/34)
|
||||
* Session-based 'flash' storage of form for redirects [#48](https://github.com/getgrav/grav-plugin-form/issues/48)
|
||||
* Added ability to **append** to file if you include a `process: save: body:` template attribute [#65](https://github.com/getgrav/grav-plugin-form/issues/65)
|
||||
1. [](#improved)
|
||||
* Support `keyname` form format like admin forms
|
||||
* Added backwards compatibility for Captcha field
|
||||
* Added 'markdown-notices' style output for better errors
|
||||
* Added `Forms::getValue()` method to retrieve values programatically
|
||||
* Changed `datetime` form field to simply extend `text` until implemented
|
||||
* Updated french language
|
||||
1. [](#bugfix)
|
||||
* Refactored the files upload logic
|
||||
* Missing Language string
|
||||
* Fixed errors not getting output
|
||||
|
||||
# v1.3.0-beta.5
|
||||
## 05/12/2016
|
||||
|
||||
1. [](#improved)
|
||||
* Moved form/field.html.twig file to the default folder, to be more easily extended in themes
|
||||
|
||||
# v1.3.0-beta.4
|
||||
## 05/04/2016
|
||||
|
||||
1. [](#new)
|
||||
* Added support for `prepend` and `append` field attributes for Text input
|
||||
|
||||
# v1.3.0-beta.3
|
||||
## 05/03/2016
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fix for select field admin translation
|
||||
|
||||
# v1.3.0-beta.2
|
||||
## 04/27/2016
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fix for autoescape in spacer and display form fields
|
||||
* Fix issue with form reset action [#66](https://github.com/getgrav/grav-plugin-form/pull/66)
|
||||
|
||||
# v1.3.0-beta.1
|
||||
## 04/20/2016
|
||||
|
||||
1. [](#new)
|
||||
* Added the HTML5 `range` input field with `min` and `max` parameters
|
||||
1. [](#improved)
|
||||
* Allow to override classes in Form definition for the form element
|
||||
* Add more blocks in the Form twig template, so classes can be overridden more easily in themes
|
||||
* Reworked some fields to fit the new Admin
|
||||
* Use `scope` for form fields to allow fields to be excluded from the data by adding `input@: false` to their definition
|
||||
* Added german translation
|
||||
* Allow to add inline Twig to the form message definition
|
||||
1. [](#bugfix)
|
||||
* Fixed the form action URL for home page forms
|
||||
* Fix stopping form events propagation, correctly stop when one event is stopped
|
||||
* Allow to translate the fields placeholders and the form message
|
||||
* Fix captcha javascript function ordering. Also, render it in the site active language
|
||||
* Support attribute `for="id"` on label for checkbox
|
||||
* Fix select fields with the multiple option enabled
|
||||
* Fixed select options escaping with autoescape on - [#502](https://github.com/getgrav/grav-plugin-admin/issues/502)
|
||||
|
||||
# v1.2.2
|
||||
## 02/11/2016
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed case issue when including form file.
|
||||
|
||||
# v1.2.1
|
||||
## 02/11/2016
|
||||
|
||||
1. [](#new)
|
||||
* Allow placeholder for **select** field
|
||||
1. [](#improved)
|
||||
* Use common language strings in blueprints
|
||||
* Use `for` attribute in labels
|
||||
* Improved `README.md`
|
||||
* Code lint
|
||||
1. [](#bugfix)
|
||||
* Moved `nl2br` to correct place or will break for arrays
|
||||
|
||||
# v1.2.0
|
||||
## 01/06/2016
|
||||
|
||||
1. [](#bugfix)
|
||||
* Correctly merge the file field configuration
|
||||
* restore full file information save
|
||||
|
||||
# v1.1.0
|
||||
## 12/18/2015
|
||||
|
||||
1. [](#new)
|
||||
* Croatian translation
|
||||
* Added id, style, and disabled options to select fields
|
||||
1. [](#improved)
|
||||
* Allow adding form labels and help text as lang strings
|
||||
* Allow translating field content
|
||||
* Allow translating button and checkbox labels
|
||||
* Allow adding classes to the form field container with `field.outerclasses`
|
||||
* Updated French translation
|
||||
1. [](#bugfix)
|
||||
* Fixed error message on file upload
|
||||
* Fixed overriding defaults for the file type in forms
|
||||
|
||||
# v1.0.3
|
||||
## 12/11/2015
|
||||
|
||||
1. [](#improved)
|
||||
* Updated languages
|
||||
* Allow an action to stop processing
|
||||
1. [](#bugfix)
|
||||
* Fix captcha validation
|
||||
* Fix issue where Form was unsetting valid page
|
||||
|
||||
# v1.0.2
|
||||
## 12/01/2015
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed merge of defaults settings
|
||||
* Support for arrays in `data.txt.twig`
|
||||
* Fixed blueprint for admin
|
||||
|
||||
# v1.0.1
|
||||
## 12/01/2015
|
||||
|
||||
1. [](#new)
|
||||
* New **file upload** field
|
||||
* Added modular form template
|
||||
* Spanish translation
|
||||
* Hungarian translation
|
||||
* Italian translation
|
||||
|
||||
# v1.0.0
|
||||
## 11/21/2015
|
||||
|
||||
1. [](#new)
|
||||
* Server-side validation of forms #11
|
||||
* Added french translation
|
||||
* Added **nonce** form security
|
||||
1. [](#improved)
|
||||
* Show a more meaningful error when the display page is not found
|
||||
* Added links to learn site for form examples
|
||||
* Label can be omitted
|
||||
* Allow user to set the CSS class for buttons
|
||||
1. [](#bugfix)
|
||||
* Fixed multi-language forms
|
||||
* Checkbox is translatable
|
||||
* Minor fixes
|
||||
|
||||
# v0.6.0
|
||||
## 10/21/2015
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed for missing attributes in textarea field
|
||||
* Fixed checkbox inputs
|
||||
|
||||
# v0.5.0
|
||||
## 10/15/2015
|
||||
|
||||
1. [](#new)
|
||||
* New `operation` param to allow different file saving strategies
|
||||
* Ability to add new file saving strategies
|
||||
* Now calls a `process()` method during form processing
|
||||
1. [](#improved)
|
||||
* Added server-side captcha validation and removed front-end validation
|
||||
* Allow `filename` instead of `prefix`, `format` + `extension`
|
||||
1. [](#bugfix)
|
||||
* Fixed radio inputs
|
||||
|
||||
# v0.4.0
|
||||
## 9/16/2015
|
||||
|
||||
1. [](#new)
|
||||
* PHP server-side form validation
|
||||
* Added new Google Catpcha field with front-end validation
|
||||
1. [](#improved)
|
||||
* Add defaults for forms, moved from the themes to the Form plugin
|
||||
* Store multi-line fields with line endings converted to HTML
|
||||
|
||||
# v0.3.0
|
||||
## 9/11/2015
|
||||
|
||||
1. [](#improved)
|
||||
* Refactored all the forms fields
|
||||
|
||||
# v0.2.1
|
||||
## 08/24/2015
|
||||
|
||||
1. [](#improved)
|
||||
* Translated tooltips
|
||||
|
||||
# v0.2.0
|
||||
## 08/11/2015
|
||||
|
||||
1. [](#improved)
|
||||
* Disable `enable` in admin
|
||||
|
||||
# v0.1.0
|
||||
## 08/04/2015
|
||||
|
||||
1. [](#new)
|
||||
* ChangeLog started...
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Grav
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Grav Form Plugin
|
||||
|
||||
The **form plugin** for [Grav](http://github.com/getgrav/grav) adds the ability to create and use forms. This is currently used extensively by the **admin** and **login** plugins.
|
||||
|
||||
# Installation
|
||||
|
||||
The form plugin is easy to install with GPM.
|
||||
|
||||
```
|
||||
$ bin/gpm install form
|
||||
```
|
||||
|
||||
# Configuration
|
||||
|
||||
Simply copy the `user/plugins/form/form.yaml` into `user/config/plugins/form.yaml` and make your modifications.
|
||||
|
||||
```
|
||||
enabled: true
|
||||
```
|
||||
|
||||
# How to use the Form Plugin
|
||||
|
||||
The Learn site has two pages describing how to use the Form Plugin:
|
||||
- [Forms](http://learn.getgrav.org/advanced/forms)
|
||||
- [Add a contact form](http://learn.getgrav.org/forms/forms/example-form)
|
||||
|
||||
# Using email
|
||||
|
||||
Note: when using email functionality in your forms, make sure you have configured the Email plugin correctly. In particular, make sure you configured the "Email from" and "Email to" email addresses in the Email plugin with your email address
|
||||
@@ -0,0 +1,289 @@
|
||||
import $ from 'jquery';
|
||||
import Dropzone from 'dropzone';
|
||||
import { config, translations } from 'grav-form';
|
||||
|
||||
let request = {};
|
||||
|
||||
// translations
|
||||
const Dictionary = {
|
||||
dictCancelUpload: translations.PLUGIN_FORM.DROPZONE_CANCEL_UPLOAD,
|
||||
dictCancelUploadConfirmation: translations.PLUGIN_FORM.DROPZONE_CANCEL_UPLOAD_CONFIRMATION,
|
||||
dictDefaultMessage: translations.PLUGIN_FORM.DROPZONE_DEFAULT_MESSAGE,
|
||||
dictFallbackMessage: translations.PLUGIN_FORM.DROPZONE_FALLBACK_MESSAGE,
|
||||
dictFallbackText: translations.PLUGIN_FORM.DROPZONE_FALLBACK_TEXT,
|
||||
dictFileTooBig: translations.PLUGIN_FORM.DROPZONE_FILE_TOO_BIG,
|
||||
dictInvalidFileType: translations.PLUGIN_FORM.DROPZONE_INVALID_FILE_TYPE,
|
||||
dictMaxFilesExceeded: translations.PLUGIN_FORM.DROPZONE_MAX_FILES_EXCEEDED,
|
||||
dictRemoveFile: translations.PLUGIN_FORM.DROPZONE_REMOVE_FILE,
|
||||
dictRemoveFileConfirmation: translations.PLUGIN_FORM.DROPZONE_REMOVE_FILE_CONFIRMATION,
|
||||
dictResponseError: translations.PLUGIN_FORM.DROPZONE_RESPONSE_ERROR
|
||||
};
|
||||
|
||||
Dropzone.autoDiscover = false;
|
||||
|
||||
const DropzoneMediaConfig = {
|
||||
createImageThumbnails: { thumbnailWidth: 150 },
|
||||
addRemoveLinks: false,
|
||||
dictDefaultMessage: Dictionary.dictDefaultMessage,
|
||||
dictRemoveFileConfirmation: Dictionary.dictRemoveFileConfirmation,
|
||||
previewTemplate: `
|
||||
<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>
|
||||
|
||||
|
||||
|
||||
<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>`.trim()
|
||||
};
|
||||
|
||||
export default class FilesField {
|
||||
constructor({ container = '.dropzone.files-upload', options = {} } = {}) {
|
||||
this.container = $(container);
|
||||
if (!this.container.length) { return; }
|
||||
|
||||
this.urls = {};
|
||||
this.options = Object.assign({}, Dictionary, DropzoneMediaConfig, {
|
||||
klass: this,
|
||||
url: this.container.data('file-url-add') || config.current_url,
|
||||
acceptedFiles: this.container.data('media-types'),
|
||||
init: this.initDropzone
|
||||
}, this.container.data('dropzone-options'), options);
|
||||
|
||||
this.dropzone = new Dropzone(container, this.options);
|
||||
this.dropzone.on('complete', this.onDropzoneComplete.bind(this));
|
||||
this.dropzone.on('success', this.onDropzoneSuccess.bind(this));
|
||||
this.dropzone.on('removedfile', this.onDropzoneRemovedFile.bind(this));
|
||||
this.dropzone.on('sending', this.onDropzoneSending.bind(this));
|
||||
this.dropzone.on('error', this.onDropzoneError.bind(this));
|
||||
}
|
||||
|
||||
initDropzone() {
|
||||
let files = this.options.klass.container.find('[data-file]');
|
||||
let dropzone = this;
|
||||
if (!files.length) { return; }
|
||||
|
||||
files.each((index, file) => {
|
||||
file = $(file);
|
||||
let data = file.data('file');
|
||||
let mock = {
|
||||
name: data.name,
|
||||
size: data.size,
|
||||
type: data.type,
|
||||
status: Dropzone.ADDED,
|
||||
accepted: true,
|
||||
url: this.options.url,
|
||||
removeUrl: data.remove
|
||||
};
|
||||
|
||||
dropzone.files.push(mock);
|
||||
dropzone.options.addedfile.call(dropzone, mock);
|
||||
if (mock.type.match(/^image\//)) dropzone.options.thumbnail.call(dropzone, mock, data.path);
|
||||
|
||||
file.remove();
|
||||
});
|
||||
}
|
||||
|
||||
onDropzoneSending(file, xhr, formData) {
|
||||
formData.append('__form-name__', this.container.closest('form').find('[name="__form-name__"]').val());
|
||||
formData.append('__form-file-uploader__', 1);
|
||||
formData.append('name', this.options.dotNotation);
|
||||
formData.append('form-nonce', config.form_nonce);
|
||||
formData.append('task', 'filesupload');
|
||||
}
|
||||
|
||||
onDropzoneSuccess(file, response, xhr) {
|
||||
if (this.options.reloadPage) {
|
||||
global.location.reload();
|
||||
}
|
||||
|
||||
// store params for removing file from session before it gets saved
|
||||
if (response.session) {
|
||||
file.sessionParams = response.session;
|
||||
file.removeUrl = this.options.url;
|
||||
|
||||
// Touch field value to force a mutation detection
|
||||
const input = this.container.find('[name][type="hidden"]');
|
||||
const value = input.val();
|
||||
input.val(value + ' ');
|
||||
}
|
||||
|
||||
return this.handleError({
|
||||
file,
|
||||
data: response,
|
||||
mode: 'removeFile',
|
||||
msg: `<p>${translations.PLUGIN_FORM.FILE_ERROR_UPLOAD} <strong>${file.name}</strong></p>
|
||||
<pre>${response.message}</pre>`
|
||||
});
|
||||
}
|
||||
|
||||
onDropzoneComplete(file) {
|
||||
if (!file.accepted && !file.rejected) {
|
||||
let data = {
|
||||
status: 'error',
|
||||
message: `${translations.PLUGIN_FORM.FILE_UNSUPPORTED}: ${file.name.match(/\..+/).join('')}`
|
||||
};
|
||||
|
||||
return this.handleError({
|
||||
file,
|
||||
data,
|
||||
mode: 'removeFile',
|
||||
msg: `<p>${translations.PLUGIN_FORM.FILE_ERROR_ADD} <strong>${file.name}</strong></p>
|
||||
<pre>${data.message}</pre>`
|
||||
});
|
||||
}
|
||||
|
||||
if (this.options.reloadPage) {
|
||||
global.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
onDropzoneRemovedFile(file, ...extra) {
|
||||
if (!file.accepted || file.rejected) { return; }
|
||||
let url = file.removeUrl || this.urls.delete;
|
||||
let path = (url || '').match(/path:(.*)\//);
|
||||
let body = { filename: file.name };
|
||||
|
||||
if (file.sessionParams) {
|
||||
body.task = 'filessessionremove';
|
||||
body.session = file.sessionParams;
|
||||
}
|
||||
|
||||
request(url, { method: 'post', body }, () => {
|
||||
if (!path) { return; }
|
||||
|
||||
path = global.atob(path[1]);
|
||||
let input = this.container.find('[name][type="hidden"]');
|
||||
let data = JSON.parse(input.val() || '{}');
|
||||
delete data[path];
|
||||
input.val(JSON.stringify(data));
|
||||
});
|
||||
}
|
||||
|
||||
onDropzoneError(file, response, xhr) {
|
||||
let message = xhr && response.error ? response.error.message : response;
|
||||
$(file.previewElement).find('[data-dz-errormessage]').html(message);
|
||||
|
||||
return this.handleError({
|
||||
file,
|
||||
data: { status: 'error' },
|
||||
msg: `<pre>${message}</pre>`
|
||||
});
|
||||
}
|
||||
|
||||
handleError(options) {
|
||||
return true;
|
||||
/* let { file, data, mode, msg } = options;
|
||||
if (data.status !== 'error' && data.status !== 'unauthorized') { return; }
|
||||
|
||||
switch (mode) {
|
||||
case 'addBack':
|
||||
if (file instanceof File) {
|
||||
this.dropzone.addFile.call(this.dropzone, file);
|
||||
} else {
|
||||
this.dropzone.files.push(file);
|
||||
this.dropzone.options.addedfile.call(this.dropzone, file);
|
||||
this.dropzone.options.thumbnail.call(this.dropzone, file, file.extras.url);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'removeFile':
|
||||
default:
|
||||
if (~this.dropzone.files.indexOf(file)) {
|
||||
file.rejected = true;
|
||||
this.dropzone.removeFile.call(this.dropzone, file, { silent: true });
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
let modal = $('[data-remodal-id="generic"]');
|
||||
modal.find('.error-content').html(msg);
|
||||
$.remodal.lookup[modal.data('remodal')].open(); */
|
||||
}
|
||||
}
|
||||
|
||||
export function UriToMarkdown(uri) {
|
||||
uri = uri.replace(/@3x|@2x|@1x/, '');
|
||||
uri = uri.replace(/\(/g, '%28');
|
||||
uri = uri.replace(/\)/g, '%29');
|
||||
|
||||
return uri.match(/\.(jpe?g|png|gif|svg)$/i) ? `` : `[${decodeURI(uri)}](${uri})`;
|
||||
}
|
||||
|
||||
let instances = [];
|
||||
let cache = $();
|
||||
const onAddedNodes = (event, target/* , record, instance */) => {
|
||||
let files = $(target).find('.dropzone.files-upload');
|
||||
if (!files.length) { return; }
|
||||
|
||||
files.each((index, file) => {
|
||||
file = $(file);
|
||||
if (!~cache.index(file)) {
|
||||
addNode(file);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const addNode = (container) => {
|
||||
container = $(container);
|
||||
let input = container.find('input[type="file"]');
|
||||
let settings = container.data('grav-file-settings') || {};
|
||||
|
||||
if (settings.accept && ~settings.accept.indexOf('*')) {
|
||||
settings.accept = [''];
|
||||
}
|
||||
|
||||
let options = {
|
||||
url: container.data('file-url-add') || (container.closest('form').attr('action') || config.current_url) + '.json',
|
||||
paramName: settings.paramName || 'file',
|
||||
dotNotation: settings.name || 'file',
|
||||
acceptedFiles: settings.accept ? settings.accept.join(',') : input.attr('accept') || container.data('media-types'),
|
||||
maxFilesize: settings.filesize || 256,
|
||||
maxFiles: settings.limit || null
|
||||
};
|
||||
|
||||
cache = cache.add(container);
|
||||
container = container[0];
|
||||
instances.push(new FilesField({ container, options }));
|
||||
};
|
||||
|
||||
export let Instances = (() => {
|
||||
$('.dropzone.files-upload').each((i, container) => addNode(container));
|
||||
$('body').on('mutation._grav', onAddedNodes);
|
||||
|
||||
return instances;
|
||||
})();
|
||||
|
||||
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,69 @@
|
||||
.form-errors {
|
||||
background: #fdf7f7;
|
||||
color: #b52b27;
|
||||
padding: 0 5px;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.form-errors p {
|
||||
margin: 0;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.form-field.has-errors .form-errors {
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
.form-field.has-errors label {
|
||||
color: #b52b27;
|
||||
}
|
||||
|
||||
.form-field.has-errors .form-input-wrapper input,
|
||||
.form-field.has-errors .form-input-wrapper select,
|
||||
.form-field.has-errors .form-input-wrapper textarea {
|
||||
border: 1px solid #d9534f;
|
||||
}
|
||||
|
||||
.form-input-file.dropzone {
|
||||
position: relative;
|
||||
min-height: 70px;
|
||||
border-radius: 3px;
|
||||
margin-bottom: .85rem;
|
||||
border: 2px dashed #ccc;
|
||||
color: #aaa;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.form-input-file input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form-input-file .dz-default.dz-message {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.form-input-file.dropzone .dz-preview {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.form-input-file.dropzone .dz-preview:hover {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.form-input-file.dropzone .dz-preview .dz-error-message {
|
||||
min-width: 140px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.form-input-file.dropzone .dz-preview .dz-image,
|
||||
.form-input-file.dropzone .dz-preview.dz-file-preview .dz-image {
|
||||
border-radius: 3px;
|
||||
z-index: 1;
|
||||
}
|
||||
Vendored
+3
File diff suppressed because one or more lines are too long
@@ -0,0 +1,145 @@
|
||||
name: Form
|
||||
version: 2.11.2
|
||||
description: Enables the forms handling
|
||||
icon: check-square
|
||||
author:
|
||||
name: Team Grav
|
||||
email: devs@getgrav.org
|
||||
url: http://getgrav.org
|
||||
keywords: plugin, form
|
||||
homepage: https://github.com/getgrav/grav-plugin-form
|
||||
bugs: https://github.com/getgrav/grav-plugin-form/issues
|
||||
license: MIT
|
||||
|
||||
dependencies:
|
||||
- { name: grav, version: '>=1.3.9' }
|
||||
|
||||
form:
|
||||
validation: strict
|
||||
fields:
|
||||
enabled:
|
||||
type: hidden
|
||||
label: PLUGIN_ADMIN.PLUGIN_STATUS
|
||||
highlight: 1
|
||||
default: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
general:
|
||||
type: section
|
||||
title: PLUGIN_FORM.GENERAL
|
||||
|
||||
fields:
|
||||
built_in_css:
|
||||
type: toggle
|
||||
label: PLUGIN_FORM.USE_BUILT_IN_CSS
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
refresh_prevention:
|
||||
type: toggle
|
||||
label: PLUGIN_FORM.REFRESH_PREVENTION
|
||||
help: PLUGIN_FORM.REFRESH_PREVENTION_HELP
|
||||
highlight: 1
|
||||
default: 0
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
files:
|
||||
type: section
|
||||
title: PLUGIN_FORM.FILES
|
||||
|
||||
fields:
|
||||
files.multiple:
|
||||
type: toggle
|
||||
label: PLUGIN_FORM.ALLOW_MULTIPLE
|
||||
help: PLUGIN_FORM.ALLOW_MULTIPLE_HELP
|
||||
highlight: 1
|
||||
default: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
files.limit:
|
||||
type: text
|
||||
size: x-small
|
||||
label: PLUGIN_FORM.LIMIT
|
||||
help: PLUGIN_FORM.LIMIT_HELP
|
||||
default: 10
|
||||
validate:
|
||||
type: number
|
||||
min: 1
|
||||
files.destination:
|
||||
type: text
|
||||
size: large
|
||||
label: PLUGIN_FORM.DESTINATION
|
||||
help: PLUGIN_FORM.DESTINATION_HELP
|
||||
default: '@self'
|
||||
files.accept:
|
||||
type: selectize
|
||||
size: large
|
||||
label: PLUGIN_FORM.ACCEPT
|
||||
help: PLUGIN_FORM.ACCEPT_HELP
|
||||
classes: fancy
|
||||
default:
|
||||
- image/*
|
||||
validate:
|
||||
type: commalist
|
||||
files.filesize:
|
||||
type: text
|
||||
label: PLUGIN_FORM.FILESIZE
|
||||
help: PLUGIN_FORM.FILESIZE_HELP
|
||||
size: x-small
|
||||
default: 5
|
||||
validate:
|
||||
type: number
|
||||
min: 0
|
||||
files.avoid_overwriting:
|
||||
type: toggle
|
||||
label: PLUGIN_FORM.AVOID_OVERWRITING
|
||||
help: PLUGIN_FORM.AVOID_OVERWRITING_HELP
|
||||
highlight: 0
|
||||
default: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
files.random_name:
|
||||
type: toggle
|
||||
label: PLUGIN_FORM.RANDOM_NAME
|
||||
help: PLUGIN_FORM.RANDOM_NAME_HELP
|
||||
highlight: 0
|
||||
default: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
recaptcha:
|
||||
type: section
|
||||
title: PLUGIN_FORM.RECAPTCHA
|
||||
|
||||
fields:
|
||||
recaptcha.site_key:
|
||||
type: text
|
||||
label: PLUGIN_FORM.RECAPTCHA_SITE_KEY
|
||||
help: PLUGIN_FORM.RECAPTCHA_SITE_KEY_HELP
|
||||
default: ''
|
||||
recaptcha.secret_key:
|
||||
type: text
|
||||
label: PLUGIN_FORM.RECAPTCHA_SECRET_KEY
|
||||
help: PLUGIN_FORM.RECAPTCHA_SECRET_KEY_HELP
|
||||
default: ''
|
||||
@@ -0,0 +1,720 @@
|
||||
<?php
|
||||
namespace Grav\Plugin;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Data\ValidationException;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Inflector;
|
||||
use Grav\Common\Iterator;
|
||||
use Grav\Common\Page\Page;
|
||||
use Grav\Common\Utils;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
class Form extends Iterator implements \Serializable
|
||||
{
|
||||
const BYTES_TO_MB = 1048576;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $message;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $response_code;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $status = 'success';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $header_data = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $rules = [];
|
||||
|
||||
/**
|
||||
* Data values of the form (values to be stored)
|
||||
*
|
||||
* @var Data $data
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* Form header items
|
||||
*
|
||||
* @var Data $items
|
||||
*/
|
||||
protected $items = [];
|
||||
|
||||
/**
|
||||
* All the form data values, including non-data
|
||||
*
|
||||
* @var Data $values
|
||||
*/
|
||||
protected $values;
|
||||
|
||||
/**
|
||||
* The form page object
|
||||
*
|
||||
* @var Page $page
|
||||
*/
|
||||
protected $page;
|
||||
|
||||
|
||||
/**
|
||||
* Create form for the given page.
|
||||
*
|
||||
* @param Page $page
|
||||
* @param string|int|null $name
|
||||
* @param null $form
|
||||
*/
|
||||
public function __construct(Page $page, $name = null, $form = null)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->page = $page->route();
|
||||
|
||||
$header = $page->header();
|
||||
$this->rules = isset($header->rules) ? $header->rules : [];
|
||||
$this->header_data = isset($header->data) ? $header->data : [];
|
||||
|
||||
if ($form) {
|
||||
$this->items = $form;
|
||||
} else {
|
||||
if (isset($header->form)) {
|
||||
$this->items = $header->form; // for backwards compatibility
|
||||
}
|
||||
}
|
||||
|
||||
// Add form specific rules.
|
||||
if (!empty($this->items['rules']) && is_array($this->items['rules'])) {
|
||||
$this->rules += $this->items['rules'];
|
||||
}
|
||||
|
||||
// Set form name if not set.
|
||||
if ($name && !is_int($name)) {
|
||||
$this->items['name'] = $name;
|
||||
} elseif (empty($this->items['name'])) {
|
||||
$this->items['name'] = $page->slug();
|
||||
}
|
||||
|
||||
// Set form id if not set.
|
||||
if (empty($this->items['id'])) {
|
||||
$inflector = new Inflector();
|
||||
$this->items['id'] = $inflector->hyphenize($this->items['name']);
|
||||
}
|
||||
|
||||
// Reset and initialize the form
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom serializer for this complex object
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
$data = [
|
||||
'items' => $this->items,
|
||||
'message' => $this->message,
|
||||
'status' => $this->status,
|
||||
'header_data' => $this->header_data,
|
||||
'rules' => $this->rules,
|
||||
'data' => $this->data->toArray(),
|
||||
'values' => $this->values->toArray(),
|
||||
'page' => $this->page
|
||||
];
|
||||
return serialize($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom unserializer for this complex object
|
||||
*
|
||||
* @param string $data
|
||||
*/
|
||||
public function unserialize($data)
|
||||
{
|
||||
$data = unserialize($data);
|
||||
|
||||
$this->items = $data['items'];
|
||||
$this->message = $data['message'];
|
||||
$this->status = $data['status'];
|
||||
$this->header_data = $data['header_data'];
|
||||
$this->rules = $data['rules'];
|
||||
|
||||
$name = $this->items['name'];
|
||||
$items = $this->items;
|
||||
$rules = $this->rules;
|
||||
|
||||
$blueprint = function () use ($name, $items, $rules) {
|
||||
$blueprint = new Blueprint($name, ['form' => $items, 'rules' => $rules]);
|
||||
return $blueprint->load()->init();
|
||||
};
|
||||
|
||||
$this->data = new Data($data['data'], $blueprint);
|
||||
$this->values = new Data($data['values']);
|
||||
$this->page = $data['page'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow overriding of fields
|
||||
*
|
||||
* @param $fields
|
||||
*/
|
||||
public function setFields($fields)
|
||||
{
|
||||
$this->fields = $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of this form
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public function name()
|
||||
{
|
||||
return $this->items['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset data.
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$name = $this->items['name'];
|
||||
$grav = Grav::instance();
|
||||
|
||||
// Fix naming for fields (supports nested fields now!)
|
||||
if (isset($this->items['fields'])) {
|
||||
$this->items['fields'] = $this->processFields($this->items['fields']);
|
||||
}
|
||||
|
||||
$items = $this->items;
|
||||
$rules = $this->rules;
|
||||
|
||||
$blueprint = function() use ($name, $items, $rules) {
|
||||
$blueprint = new Blueprint($name, ['form' => $items, 'rules' => $rules]);
|
||||
return $blueprint->load()->init();
|
||||
};
|
||||
|
||||
$this->data = new Data($this->header_data, $blueprint);
|
||||
$this->values = new Data();
|
||||
// Reset fields to null before calling fields() -- Do not remove:
|
||||
$this->fields = null;
|
||||
$this->fields = $this->fields();
|
||||
|
||||
// Fire event
|
||||
$grav->fireEvent('onFormInitialized', new Event(['form' => $this]));
|
||||
}
|
||||
|
||||
protected function processFields($fields)
|
||||
{
|
||||
$types = Grav::instance()['plugins']->formFieldTypes;
|
||||
|
||||
$return = array();
|
||||
foreach ($fields as $key => $value) {
|
||||
// default to text if not set
|
||||
if (!isset($value['type'])) {
|
||||
$value['type'] = 'text';
|
||||
}
|
||||
|
||||
// manually merging the field types
|
||||
if ($types !== null && array_key_exists($value['type'], $types)) {
|
||||
$value += $types[$value['type']];
|
||||
}
|
||||
|
||||
// Fix numeric indexes
|
||||
if (is_numeric($key) && isset($value['name'])) {
|
||||
$key = $value['name'];
|
||||
}
|
||||
if (isset($value['fields']) && is_array($value['fields'])) {
|
||||
$value['fields'] = $this->processFields($value['fields']);
|
||||
}
|
||||
$return[$key] = $value;
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
public function fields()
|
||||
{
|
||||
|
||||
if (null === $this->fields) {
|
||||
$blueprint = $this->data->blueprints();
|
||||
|
||||
if (method_exists($blueprint, 'load')) {
|
||||
// init the form to process directives
|
||||
$blueprint->load()->init();
|
||||
|
||||
// fields set to processed blueprint fields
|
||||
$this->fields = $blueprint->fields();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return page object for the form.
|
||||
*
|
||||
* @return Page
|
||||
*/
|
||||
public function page()
|
||||
{
|
||||
return Grav::instance()['pages']->dispatch($this->page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value of given variable (or all values).
|
||||
* First look in the $data array, fallback to the $values array
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function value($name = null, $fallback = false)
|
||||
{
|
||||
if (!$name) {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
if ($this->data->get($name)) {
|
||||
return $this->data->get($name);
|
||||
}
|
||||
|
||||
if ($fallback) {
|
||||
return $this->values->get($name);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value of given variable in the values array
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
public function setValue($name = null, $value = '')
|
||||
{
|
||||
if (!$name) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->values->set($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value from the form
|
||||
*
|
||||
* @param $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValue($name)
|
||||
{
|
||||
return $this->values->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all data
|
||||
*
|
||||
* @return Data
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value of given variable in the data array
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function setData($name = null, $value = '')
|
||||
{
|
||||
if (!$name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->data->set($name, $value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setAllData($array)
|
||||
{
|
||||
$this->data = new Data($array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles ajax upload for files.
|
||||
* Stores in a flash object the temporary file and deals with potential file errors.
|
||||
*
|
||||
* @return mixed True if the action was performed.
|
||||
*/
|
||||
public function uploadFiles()
|
||||
{
|
||||
$post = $_POST;
|
||||
$grav = Grav::instance();
|
||||
$uri = $grav['uri']->url;
|
||||
$config = $grav['config'];
|
||||
$session = $grav['session'];
|
||||
|
||||
$settings = $this->data->blueprints()->schema()->getProperty($post['name']);
|
||||
$settings = (object) array_merge(
|
||||
['destination' => $config->get('plugins.form.files.destination', 'self@'),
|
||||
'avoid_overwriting' => $config->get('plugins.form.files.avoid_overwriting', false),
|
||||
'random_name' => $config->get('plugins.form.files.random_name', false),
|
||||
'accept' => $config->get('plugins.form.files.accept', ['image/*']),
|
||||
'limit' => $config->get('plugins.form.files.limit', 10),
|
||||
'filesize' => $this->getMaxFilesize(),
|
||||
],
|
||||
(array) $settings,
|
||||
['name' => $post['name']]
|
||||
);
|
||||
|
||||
$upload = $this->normalizeFiles($_FILES['data'], $settings->name);
|
||||
|
||||
// Handle errors and breaks without proceeding further
|
||||
if ($upload->file->error != UPLOAD_ERR_OK) {
|
||||
// json_response
|
||||
return [
|
||||
'status' => 'error',
|
||||
'message' => sprintf($grav['language']->translate('PLUGIN_FORM.FILEUPLOAD_UNABLE_TO_UPLOAD', null, true), $upload->file->name, $this->upload_errors[$upload->file->error])
|
||||
];
|
||||
}
|
||||
|
||||
// Handle bad filenames.
|
||||
$filename = $upload->file->name;
|
||||
if (strtr($filename, "\t\n\r\0\x0b", '_____') !== $filename || rtrim($filename, ". ") !== $filename || preg_match('|\.php|', $filename)) {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
'message' => sprintf($this->admin->translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD', null),
|
||||
$filename, 'Bad filename')
|
||||
];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove the error object to avoid storing it
|
||||
unset($upload->file->error);
|
||||
|
||||
|
||||
// Handle Accepted file types
|
||||
// Accept can only be mime types (image/png | image/*) or file extensions (.pdf|.jpg)
|
||||
$accepted = false;
|
||||
$errors = [];
|
||||
foreach ((array) $settings->accept as $type) {
|
||||
// Force acceptance of any file when star notation
|
||||
if ($type === '*') {
|
||||
$accepted = true;
|
||||
break;
|
||||
}
|
||||
|
||||
$isMime = strstr($type, '/');
|
||||
$find = str_replace('*', '.*', $type);
|
||||
|
||||
$match = preg_match('#'. $find .'$#', $isMime ? $upload->file->type : $upload->file->name);
|
||||
if (!$match) {
|
||||
$message = $isMime ? 'The MIME type "' . $upload->file->type . '"' : 'The File Extension';
|
||||
$errors[] = $message . ' for the file "' . $upload->file->name . '" is not an accepted.';
|
||||
$accepted |= false;
|
||||
} else {
|
||||
$accepted |= true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$accepted) {
|
||||
// json_response
|
||||
return [
|
||||
'status' => 'error',
|
||||
'message' => implode('<br/>', $errors)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// Handle file size limits
|
||||
$settings->filesize *= self::BYTES_TO_MB; // 1024 * 1024 [MB in Bytes]
|
||||
if ($settings->filesize > 0 && $upload->file->size > $settings->filesize) {
|
||||
// json_response
|
||||
return [
|
||||
'status' => 'error',
|
||||
'message' => $grav['language']->translate('PLUGIN_FORM.EXCEEDED_GRAV_FILESIZE_LIMIT')
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// we need to move the file at this stage or else
|
||||
// it won't be available upon save later on
|
||||
// since php removes it from the upload location
|
||||
$tmp_dir = $grav['locator']->findResource('tmp://', true, true);
|
||||
$tmp_file = $upload->file->tmp_name;
|
||||
$tmp = $tmp_dir . '/uploaded-files/' . basename($tmp_file);
|
||||
|
||||
Folder::create(dirname($tmp));
|
||||
if (!move_uploaded_file($tmp_file, $tmp)) {
|
||||
// json_response
|
||||
return [
|
||||
'status' => 'error',
|
||||
'message' => sprintf($grav['language']->translate('PLUGIN_FORM.FILEUPLOAD_UNABLE_TO_MOVE', null, true), '', $tmp)
|
||||
];
|
||||
}
|
||||
|
||||
$upload->file->tmp_name = $tmp;
|
||||
|
||||
// Retrieve the current session of the uploaded files for the field
|
||||
// and initialize it if it doesn't exist
|
||||
$sessionField = base64_encode($uri);
|
||||
$flash = $session->getFlashObject('files-upload');
|
||||
if (!$flash) {
|
||||
$flash = [];
|
||||
}
|
||||
if (!isset($flash[$sessionField])) {
|
||||
$flash[$sessionField] = [];
|
||||
}
|
||||
if (!isset($flash[$sessionField][$upload->field])) {
|
||||
$flash[$sessionField][$upload->field] = [];
|
||||
}
|
||||
|
||||
// Set destination
|
||||
$destination = Folder::getRelativePath(rtrim($settings->destination, '/'));
|
||||
$destination = $this->getPagePathFromToken($destination);
|
||||
|
||||
// Create destination if needed
|
||||
if (!is_dir($destination)) {
|
||||
Folder::mkdir($destination);
|
||||
}
|
||||
|
||||
// Generate random name if required
|
||||
if ($settings->random_name) {
|
||||
$extension = pathinfo($upload->file->name)['extension'];
|
||||
$upload->file->name = Utils::generateRandomString(15) . '.' . $extension;
|
||||
}
|
||||
|
||||
// Handle conflicting name if needed
|
||||
if ($settings->avoid_overwriting) {
|
||||
if (file_exists($destination . '/' . $upload->file->name)) {
|
||||
$upload->file->name = date('YmdHis') . '-' . $upload->file->name;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare object for later save
|
||||
$path = $destination . '/' . $upload->file->name;
|
||||
$upload->file->path = $path;
|
||||
// $upload->file->route = $page ? $path : null;
|
||||
|
||||
// Prepare data to be saved later
|
||||
$flash[$sessionField][$upload->field][$path] = (array) $upload->file;
|
||||
|
||||
// Finally store the new uploaded file in the field session
|
||||
$session->setFlashObject('files-upload', $flash);
|
||||
|
||||
|
||||
// json_response
|
||||
return [
|
||||
'status' => 'success',
|
||||
'session' => \json_encode([
|
||||
'sessionField' => base64_encode($uri),
|
||||
'path' => $upload->file->path,
|
||||
'field' => $settings->name
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle form processing on POST action.
|
||||
*/
|
||||
public function post()
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
$uri = $grav['uri']->url;
|
||||
$session = $grav['session'];
|
||||
|
||||
if (isset($_POST)) {
|
||||
$this->values = new Data(isset($_POST) ? (array)$_POST : []);
|
||||
$data = $this->values->get('data');
|
||||
|
||||
// Add post data to form dataset
|
||||
if (!$data) {
|
||||
$data = $this->values->toArray();
|
||||
}
|
||||
|
||||
|
||||
if (!$this->values->get('form-nonce') || !Utils::verifyNonce($this->values->get('form-nonce'), 'form')) {
|
||||
$this->status = 'error';
|
||||
$event = new Event(['form' => $this,
|
||||
'message' => $grav['language']->translate('PLUGIN_FORM.NONCE_NOT_VALIDATED')
|
||||
]);
|
||||
$grav->fireEvent('onFormValidationError', $event);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$i = 0;
|
||||
foreach ($this->items['fields'] as $key => $field) {
|
||||
$name = isset($field['name']) ? $field['name'] : $key;
|
||||
if (!isset($field['name'])) {
|
||||
if (isset($data[$i])) { //Handle input@ false fields
|
||||
$data[$name] = $data[$i];
|
||||
unset($data[$i]);
|
||||
}
|
||||
}
|
||||
if ($field['type'] === 'checkbox') {
|
||||
$data[$name] = isset($data[$name]) ? true : false;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
$this->data->merge($data);
|
||||
}
|
||||
|
||||
// Validate and filter data
|
||||
try {
|
||||
$this->data->validate();
|
||||
$this->data->filter();
|
||||
|
||||
$grav->fireEvent('onFormValidationProcessed', new Event(['form' => $this]));
|
||||
} catch (ValidationException $e) {
|
||||
$this->status = 'error';
|
||||
$event = new Event(['form' => $this, 'message' => $e->getMessage(), 'messages' => $e->getMessages()]);
|
||||
$grav->fireEvent('onFormValidationError', $event);
|
||||
if ($event->isPropagationStopped()) {
|
||||
return;
|
||||
}
|
||||
} catch (\RuntimeException $e) {
|
||||
$this->status = 'error';
|
||||
$event = new Event(['form' => $this, 'message' => $e->getMessage(), 'messages' => []]);
|
||||
$grav->fireEvent('onFormValidationError', $event);
|
||||
if ($event->isPropagationStopped()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Process previously uploaded files for the current URI
|
||||
// and finally store them. Everything else will get discarded
|
||||
$queue = $session->getFlashObject('files-upload');
|
||||
$queue = $queue[base64_encode($uri)];
|
||||
if (is_array($queue)) {
|
||||
foreach ($queue as $key => $files) {
|
||||
foreach ($files as $destination => $file) {
|
||||
if (!rename($file['tmp_name'], $destination)) {
|
||||
throw new \RuntimeException(sprintf($grav['language']->translate('PLUGIN_FORM.FILEUPLOAD_UNABLE_TO_MOVE', null, true), '"' . $file['tmp_name'] . '"', $destination));
|
||||
}
|
||||
|
||||
unset($files[$destination]['tmp_name']);
|
||||
}
|
||||
|
||||
$this->data->merge([$key => $files]);
|
||||
}
|
||||
}
|
||||
|
||||
$process = isset($this->items['process']) ? $this->items['process'] : [];
|
||||
if (is_array($process)) {
|
||||
$event = null;
|
||||
foreach ($process as $action => $data) {
|
||||
if (is_numeric($action)) {
|
||||
$action = \key($data);
|
||||
$data = $data[$action];
|
||||
}
|
||||
|
||||
$previousEvent = $event;
|
||||
$event = new Event(['form' => $this, 'action' => $action, 'params' => $data]);
|
||||
|
||||
if ($previousEvent) {
|
||||
if (!$previousEvent->isPropagationStopped()) {
|
||||
$grav->fireEvent('onFormProcessed', $event);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$grav->fireEvent('onFormProcessed', $event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getPagePathFromToken($path)
|
||||
{
|
||||
return Utils::getPagePathFromToken($path, $this->page());
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to normalize the $_FILES array
|
||||
*
|
||||
* @param array $data $_FILES starting point data
|
||||
* @param string $key
|
||||
* @return object a new Object with a normalized list of files
|
||||
*/
|
||||
protected function normalizeFiles($data, $key = '')
|
||||
{
|
||||
$files = new \stdClass();
|
||||
$files->field = $key;
|
||||
$files->file = new \stdClass();
|
||||
|
||||
foreach ($data as $fieldName => $fieldValue) {
|
||||
// Since Files Upload are always happening via Ajax
|
||||
// we are not interested in handling `multiple="true"`
|
||||
// because they are always handled one at a time.
|
||||
// For this reason we normalize the value to string,
|
||||
// in case it is arriving as an array.
|
||||
$value = (array) Utils::getDotNotation($fieldValue, $key);
|
||||
$files->file->{$fieldName} = array_shift($value);
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the nonce for a form
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getNonce()
|
||||
{
|
||||
$action = 'form-plugin';
|
||||
return Utils::getNonce($action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configured max file size in bytes
|
||||
*
|
||||
* @param bool $mbytes return size in MB
|
||||
* @return int
|
||||
*/
|
||||
public static function getMaxFilesize($mbytes = false)
|
||||
{
|
||||
$config = Grav::instance()['config'];
|
||||
|
||||
$filesize_mb = (float)$config->get('plugins.form.files.filesize', 0) * static::BYTES_TO_MB;
|
||||
$system_filesize = $config->get('system.media.upload_limit', 0);
|
||||
if ($filesize_mb > $system_filesize || $filesize_mb === 0) {
|
||||
$filesize_mb = $system_filesize;
|
||||
}
|
||||
|
||||
if ($mbytes) {
|
||||
return $filesize_mb;
|
||||
}
|
||||
|
||||
return $filesize_mb / static::BYTES_TO_MB;
|
||||
}
|
||||
|
||||
public function responseCode($code = null)
|
||||
{
|
||||
if ($code) {
|
||||
$this->response_code = $code;
|
||||
}
|
||||
return $this->response_code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,728 @@
|
||||
<?php
|
||||
namespace Grav\Plugin;
|
||||
|
||||
use Grav\Common\Data\ValidationException;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Page\Page;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Plugin;
|
||||
use Grav\Common\Twig\Twig;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Uri;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use RocketTheme\Toolbox\File\File;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
/**
|
||||
* Class FormPlugin
|
||||
* @package Grav\Plugin
|
||||
*/
|
||||
class FormPlugin extends Plugin
|
||||
{
|
||||
public $features = [
|
||||
'blueprints' => 1000
|
||||
];
|
||||
|
||||
/**
|
||||
* @var Form
|
||||
*/
|
||||
protected $form;
|
||||
|
||||
protected $forms = [];
|
||||
|
||||
protected $flat_forms = [];
|
||||
|
||||
protected $json_response = [];
|
||||
|
||||
protected $recache_forms = false;
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
'onPluginsInitialized' => ['onPluginsInitialized', 0],
|
||||
'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize forms from cache if possible
|
||||
*/
|
||||
public function onPluginsInitialized()
|
||||
{
|
||||
require_once(__DIR__ . '/classes/form.php');
|
||||
|
||||
if ($this->isAdmin()) {
|
||||
$this->enable([
|
||||
'onPagesInitialized' => ['onPagesInitialized', 0]
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->enable([
|
||||
'onPageProcessed' => ['onPageProcessed', 0],
|
||||
'onPagesInitialized' => ['onPagesInitialized', 0],
|
||||
'onTwigInitialized' => ['onTwigInitialized', 0],
|
||||
'onTwigPageVariables' => ['onTwigVariables', 0],
|
||||
'onTwigSiteVariables' => ['onTwigVariables', 0],
|
||||
'onFormValidationProcessed' => ['onFormValidationProcessed', 0],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process forms after page header processing, but before caching
|
||||
*
|
||||
* @param Event $e
|
||||
*/
|
||||
public function onPageProcessed(Event $e)
|
||||
{
|
||||
/** @var Page $page */
|
||||
$page = $e['page'];
|
||||
$page_route = $page->route();
|
||||
|
||||
if ($page->home()) {
|
||||
$page_route = '/';
|
||||
}
|
||||
|
||||
$header = $page->header();
|
||||
|
||||
//call event to allow filling the page header form dynamically (e.g. use case: Comments plugin)
|
||||
$this->grav->fireEvent('onFormPageHeaderProcessed', new Event(['header' => $header]));
|
||||
|
||||
if ((isset($header->forms) && is_array($header->forms)) ||
|
||||
(isset($header->form) && is_array($header->form))) {
|
||||
$page_forms = [];
|
||||
|
||||
// Force never_cache_twig if modular form
|
||||
if ($page->modular()) {
|
||||
$header->never_cache_twig = true;
|
||||
}
|
||||
|
||||
// Get the forms from the page headers
|
||||
if (isset($header->forms)) {
|
||||
$page_forms = $header->forms;
|
||||
} elseif (isset($header->form)) {
|
||||
$page_forms[] = $header->form;
|
||||
}
|
||||
|
||||
// Store the page forms in the forms instance
|
||||
foreach ($page_forms as $name => $page_form) {
|
||||
$form = new Form($page, $name, $page_form);
|
||||
$this->addForm($page_route, $form);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a form to the forms plugin
|
||||
*
|
||||
* @param $page_route
|
||||
* @param $form
|
||||
*/
|
||||
public function addForm($page_route, $form)
|
||||
{
|
||||
|
||||
$form_array = [$form['name'] => $form];
|
||||
if (array_key_exists($page_route, $this->forms)) {
|
||||
if (!isset($this->form[$page_route][$form['name']])) {
|
||||
$this->forms[$page_route] = array_merge($this->forms[$page_route], $form_array);
|
||||
}
|
||||
} else {
|
||||
$this->forms[$page_route] = $form_array;
|
||||
|
||||
}
|
||||
|
||||
$this->flattenForms();
|
||||
$this->recache_forms = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize form if the page has one. Also catches form processing if user posts the form.
|
||||
*/
|
||||
public function onPagesInitialized()
|
||||
{
|
||||
$submitted = false;
|
||||
$this->json_response = [];
|
||||
$cache_id = $this->grav['pages']->getPagesCacheId() . '-form-plugin';
|
||||
|
||||
// Get and set the cache of forms if it exists
|
||||
list($forms, $flat_forms) = $this->grav['cache']->fetch($cache_id);
|
||||
|
||||
// Only store the forms if they are an array
|
||||
if (is_array($forms)) {
|
||||
$this->forms = array_merge($this->forms, $forms);
|
||||
}
|
||||
|
||||
// Only store the flat_forms if they are an array
|
||||
if (is_array($flat_forms)) {
|
||||
$this->flat_forms = array_merge($this->flat_forms, $flat_forms);
|
||||
}
|
||||
|
||||
// Save the current state of the forms to cache
|
||||
if ($this->recache_forms) {
|
||||
$this->grav['cache']->save($cache_id, [$this->forms, $this->flat_forms]);
|
||||
}
|
||||
|
||||
// Enable form events if there's a POST
|
||||
if ($this->shouldProcessForm()) {
|
||||
$this->enable([
|
||||
'onFormProcessed' => ['onFormProcessed', 0],
|
||||
'onFormValidationError' => ['onFormValidationError', 0],
|
||||
'onFormFieldTypes' => ['onFormFieldTypes', 0],
|
||||
]);
|
||||
|
||||
// Post the form
|
||||
if ($this->form()) {
|
||||
if ($this->grav['uri']->extension() === 'json' && isset($_POST['__form-file-uploader__'])) {
|
||||
$this->json_response = $this->form->uploadFiles();
|
||||
} else {
|
||||
$this->form->post();
|
||||
$submitted = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear flash objects for previously uploaded files
|
||||
// whenever the user switches page / reloads
|
||||
// ignoring any JSON / extension call
|
||||
if (null === $this->grav['uri']->extension() && !$submitted) {
|
||||
// Discard any previously uploaded files session.
|
||||
// and if there were any uploaded file, remove them from the filesystem
|
||||
if ($flash = $this->grav['session']->getFlashObject('files-upload')) {
|
||||
$flash = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($flash));
|
||||
foreach ($flash as $key => $value) {
|
||||
if ($key !== 'tmp_name') {
|
||||
continue;
|
||||
}
|
||||
@unlink($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add simple `forms()` Twig function
|
||||
*/
|
||||
public function onTwigInitialized()
|
||||
{
|
||||
$this->grav['twig']->twig()->addFunction(
|
||||
new \Twig_SimpleFunction('forms', [$this, 'getForm'])
|
||||
);
|
||||
|
||||
$this->grav['twig']->twig()->getExtension('Twig_Extension_Core')->setEscaper('yaml', function($twig, $string, $charset) {
|
||||
return Yaml::dump($string);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add current directory to twig lookup paths.
|
||||
*/
|
||||
public function onTwigTemplatePaths()
|
||||
{
|
||||
$this->grav['twig']->twig_paths[] = __DIR__ . '/templates';
|
||||
}
|
||||
|
||||
/**
|
||||
* Make form accessible from twig.
|
||||
*
|
||||
* @param Event $event
|
||||
*/
|
||||
public function onTwigVariables(Event $event = null)
|
||||
{
|
||||
if ($event !== null && isset($event['page'])) {
|
||||
$page = $event['page'];
|
||||
} else {
|
||||
$page = $this->grav['page'];
|
||||
}
|
||||
|
||||
$twig = $this->grav['twig'];
|
||||
|
||||
if (!isset($twig->twig_vars['form'])) {
|
||||
$twig->twig_vars['form'] = $this->form($page);
|
||||
}
|
||||
|
||||
if ($this->config->get('plugins.form.built_in_css')) {
|
||||
$this->grav['assets']->addCss('plugin://form/assets/form-styles.css');
|
||||
}
|
||||
$twig->twig_vars['form_max_filesize'] = Form::getMaxFilesize();
|
||||
$twig->twig_vars['form_json_response'] = $this->json_response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle form processing instructions.
|
||||
*
|
||||
* @param Event $event
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function onFormProcessed(Event $event)
|
||||
{
|
||||
$form = $event['form'];
|
||||
$action = $event['action'];
|
||||
$params = $event['params'];
|
||||
|
||||
$this->process($form);
|
||||
|
||||
switch ($action) {
|
||||
case 'captcha':
|
||||
if (isset($params['recaptcha_secret'])) {
|
||||
$recaptchaSecret = $params['recaptcha_secret'];
|
||||
} elseif (isset($params['recatpcha_secret'])) {
|
||||
// Included for backwards compatibility with typo (issue #51)
|
||||
$recaptchaSecret = $params['recatpcha_secret'];
|
||||
} else {
|
||||
$recaptchaSecret = $this->config->get('plugins.form.recaptcha.secret_key');
|
||||
}
|
||||
|
||||
// Validate the captcha
|
||||
$query = http_build_query([
|
||||
'secret' => $recaptchaSecret,
|
||||
'response' => $form->value('g-recaptcha-response', true)
|
||||
]);
|
||||
$url = 'https://www.google.com/recaptcha/api/siteverify?' . $query;
|
||||
$response = json_decode(file_get_contents($url), true);
|
||||
|
||||
if (!isset($response['success']) || $response['success'] !== true) {
|
||||
$this->grav->fireEvent('onFormValidationError', new Event([
|
||||
'form' => $form,
|
||||
'message' => $this->grav['language']->translate('PLUGIN_FORM.ERROR_VALIDATING_CAPTCHA')
|
||||
]));
|
||||
$event->stopPropagation();
|
||||
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'ip':
|
||||
$label = isset($params['label']) ? $params['label'] : 'User IP';
|
||||
$blueprint = $form->value()->blueprints();
|
||||
$blueprint->set('form/fields/ip', ['name'=>'ip', 'label'=> $label]);
|
||||
$form->setFields($blueprint->fields());
|
||||
$form->setData('ip', Uri::ip());
|
||||
break;
|
||||
case 'message':
|
||||
$translated_string = $this->grav['language']->translate($params);
|
||||
$vars = array(
|
||||
'form' => $form
|
||||
);
|
||||
|
||||
/** @var Twig $twig */
|
||||
$twig = $this->grav['twig'];
|
||||
$processed_string = $twig->processString($translated_string, $vars);
|
||||
|
||||
$form->message = $processed_string;
|
||||
break;
|
||||
case 'redirect':
|
||||
$this->grav['session']->setFlashObject('form', $form);
|
||||
$url = ((string)$params);
|
||||
$vars = array(
|
||||
'form' => $form
|
||||
);
|
||||
/** @var Twig $twig */
|
||||
$twig = $this->grav['twig'];
|
||||
$url = $twig->processString($url, $vars);
|
||||
$this->grav->redirect($url);
|
||||
break;
|
||||
case 'reset':
|
||||
if (Utils::isPositive($params)) {
|
||||
$form->reset();
|
||||
}
|
||||
break;
|
||||
case 'display':
|
||||
$route = (string)$params;
|
||||
if (!$route || $route[0] !== '/') {
|
||||
/** @var Uri $uri */
|
||||
$uri = $this->grav['uri'];
|
||||
$route = rtrim($uri->route(), '/'). '/' . ($route ?: '');
|
||||
}
|
||||
|
||||
/** @var Twig $twig */
|
||||
$twig = $this->grav['twig'];
|
||||
$twig->twig_vars['form'] = $form;
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = $this->grav['pages'];
|
||||
$page = $pages->dispatch($route, true);
|
||||
|
||||
if (!$page) {
|
||||
throw new \RuntimeException('Display page not found. Please check the page exists.', 400);
|
||||
}
|
||||
|
||||
unset($this->grav['page']);
|
||||
$this->grav['page'] = $page;
|
||||
break;
|
||||
case 'remember':
|
||||
foreach ($params as $remember_field) {
|
||||
$field_cookie = 'forms-'.$form['name'].'-'.$remember_field;
|
||||
setcookie($field_cookie, $form->value($remember_field), time()+60*60*24*60);
|
||||
}
|
||||
break;
|
||||
case 'save':
|
||||
$prefix = !empty($params['fileprefix']) ? $params['fileprefix'] : '';
|
||||
$format = !empty($params['dateformat']) ? $params['dateformat'] : 'Ymd-His-u';
|
||||
$ext = !empty($params['extension']) ? '.' . trim($params['extension'], '.') : '.txt';
|
||||
$filename = !empty($params['filename']) ? $params['filename'] : '';
|
||||
$operation = !empty($params['operation']) ? $params['operation'] : 'create';
|
||||
|
||||
if (!$filename) {
|
||||
$filename = $prefix . $this->udate($format) . $ext;
|
||||
}
|
||||
|
||||
/** @var Twig $twig */
|
||||
$twig = $this->grav['twig'];
|
||||
$vars = [
|
||||
'form' => $form
|
||||
];
|
||||
|
||||
// Process with Twig
|
||||
$filename = $twig->processString($filename, $vars);
|
||||
|
||||
$locator = $this->grav['locator'];
|
||||
$path = $locator->findResource('user://data', true);
|
||||
$dir = $path . DS . $form->name();
|
||||
$fullFileName = $dir. DS . $filename;
|
||||
|
||||
$file = File::instance($fullFileName);
|
||||
|
||||
if ($operation === 'create') {
|
||||
$body = $twig->processString(!empty($params['body']) ? $params['body'] : '{% include "forms/data.txt.twig" %}',
|
||||
$vars);
|
||||
$file->save($body);
|
||||
} elseif ($operation === 'add') {
|
||||
if (!empty($params['body'])) {
|
||||
// use body similar to 'create' action and append to file as a log
|
||||
$body = $twig->processString($params['body'], $vars);
|
||||
|
||||
// create folder if it doesn't exist
|
||||
if (!file_exists($dir)) {
|
||||
Folder::create($dir);
|
||||
}
|
||||
|
||||
// append data to existing file
|
||||
file_put_contents($fullFileName, $body, FILE_APPEND | LOCK_EX);
|
||||
} else {
|
||||
// serialize YAML out to file for easier parsing as data sets
|
||||
$vars = $vars['form']->value()->toArray();
|
||||
|
||||
foreach ($form->fields as $field) {
|
||||
if (!empty($field['process']['ignore'])) {
|
||||
unset($vars[$field['name']]);
|
||||
}
|
||||
}
|
||||
|
||||
if (file_exists($fullFileName)) {
|
||||
$data = Yaml::parse($file->content());
|
||||
if (count($data) > 0) {
|
||||
array_unshift($data, $vars);
|
||||
} else {
|
||||
$data[] = $vars;
|
||||
}
|
||||
} else {
|
||||
$data[] = $vars;
|
||||
}
|
||||
|
||||
$file->save(Yaml::dump($data));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom field logic can go in here
|
||||
*
|
||||
* @param Event $event
|
||||
*/
|
||||
public function onFormValidationProcessed(Event $event)
|
||||
{
|
||||
// special check for honeypot field
|
||||
foreach ($event['form']->fields() as $field) {
|
||||
if ($field['type'] === 'honeypot' && !empty($event['form']->value($field['name']))) {
|
||||
throw new ValidationException('Are you a bot?');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle form validation error
|
||||
*
|
||||
* @param Event $event An event object
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function onFormValidationError(Event $event)
|
||||
{
|
||||
$form = $event['form'];
|
||||
if (isset($event['message'])) {
|
||||
$form->status = 'error';
|
||||
$form->message = $event['message'];
|
||||
$form->messages = $event['messages'];
|
||||
}
|
||||
|
||||
$uri = $this->grav['uri'];
|
||||
$route = $uri->route();
|
||||
|
||||
/** @var Twig $twig */
|
||||
$twig = $this->grav['twig'];
|
||||
$twig->twig_vars['form'] = $form;
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = $this->grav['pages'];
|
||||
$page = $pages->dispatch($route, true);
|
||||
|
||||
if ($page) {
|
||||
unset($this->grav['page']);
|
||||
$this->grav['page'] = $page;
|
||||
}
|
||||
|
||||
$event->stopPropagation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of form field types specified in this plugin. Only special types needs to be listed.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFormFieldTypes()
|
||||
{
|
||||
return [
|
||||
'column' => [
|
||||
'input@' => false
|
||||
],
|
||||
'columns' => [
|
||||
'input@' => false
|
||||
],
|
||||
'fieldset' => [
|
||||
'input@' => false
|
||||
],
|
||||
'conditional' => [
|
||||
'input@' => false
|
||||
],
|
||||
'display' => [
|
||||
'input@' => false
|
||||
],
|
||||
'spacer' => [
|
||||
'input@' => false
|
||||
],
|
||||
'captcha' => [
|
||||
'input@' => false
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a form
|
||||
*
|
||||
* Currently available processing tasks:
|
||||
*
|
||||
* - fillWithCurrentDateTime
|
||||
*
|
||||
* @param Form $form
|
||||
*/
|
||||
protected function process($form)
|
||||
{
|
||||
foreach ($form->fields as $field) {
|
||||
if (!empty($field['process']['fillWithCurrentDateTime'])) {
|
||||
$form->setData($field['name'], gmdate('D, d M Y H:i:s', time()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create unix timestamp for storing the data into the filesystem.
|
||||
*
|
||||
* @param string $format
|
||||
* @param int $utimestamp
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function udate($format = 'u', $utimestamp = null)
|
||||
{
|
||||
if (null === $utimestamp) {
|
||||
$utimestamp = microtime(true);
|
||||
}
|
||||
|
||||
$timestamp = floor($utimestamp);
|
||||
$milliseconds = round(($utimestamp - $timestamp) * 1000000);
|
||||
|
||||
return date(preg_replace('`(?<!\\\\)u`', \sprintf('%06d', $milliseconds), $format), $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Page $page
|
||||
* @return mixed
|
||||
*/
|
||||
private function getFormName(Page $page)
|
||||
{
|
||||
$name = filter_input(INPUT_POST, '__form-name__');
|
||||
|
||||
if (!$name) {
|
||||
$name = $page->slug();
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* function to get a specific form
|
||||
*
|
||||
* @param null|array|string $data optional form `name`
|
||||
*
|
||||
* @return null|Form
|
||||
*/
|
||||
public function getForm($data = null)
|
||||
{
|
||||
$page_route = null;
|
||||
$form_name = null;
|
||||
|
||||
if (is_array($data)) {
|
||||
if (isset($data['name'])) {
|
||||
$form_name = $data['name'];
|
||||
}
|
||||
if (isset($data['route'])) {
|
||||
$page_route = $data['route'];
|
||||
}
|
||||
} elseif (is_string($data)) {
|
||||
$form_name = $data;
|
||||
}
|
||||
|
||||
// if no form name, use the first form found in the page
|
||||
if (!$form_name) {
|
||||
// If page route not provided, use the current page
|
||||
if (!$page_route) {
|
||||
// Get page route
|
||||
$page_route = $this->grav['page']->route();
|
||||
|
||||
// fallback using current URI if page not initialized yet
|
||||
if (!$page_route) {
|
||||
$page_route = $this->getCurrentPageRoute();
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->forms[$page_route])) {
|
||||
$forms = $this->forms[$page_route];
|
||||
$first_form = array_shift($forms);
|
||||
$form_name = $first_form['name'];
|
||||
} else {
|
||||
//No form on this route. Try looking up in the current page first
|
||||
return new Form($this->grav['page']);
|
||||
}
|
||||
}
|
||||
|
||||
// return the form you are looking for if available
|
||||
return $this->getFormByName($form_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current page's route
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getCurrentPageRoute()
|
||||
{
|
||||
$path = $this->grav['uri']->route();
|
||||
$path = $path ?: '/';
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a form based on the form name
|
||||
*
|
||||
* @param $form_name
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getFormByName($form_name)
|
||||
{
|
||||
if (array_key_exists($form_name, $this->flat_forms)) {
|
||||
return $this->flat_forms[$form_name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the page has a form submission that should be processed
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function shouldProcessForm()
|
||||
{
|
||||
$status = isset($_POST) && isset($_POST['form-nonce']);
|
||||
$refresh_prevention = null;
|
||||
|
||||
if ($status && $this->form()) {
|
||||
// Set page template if passed by form
|
||||
if (isset($this->form->template)) {
|
||||
$this->grav['page']->template($this->form->template);
|
||||
}
|
||||
|
||||
if (!is_null($this->form->refresh_prevention)) {
|
||||
$refresh_prevention = (bool) $this->form->refresh_prevention;
|
||||
} else {
|
||||
$refresh_prevention = $this->config->get('plugins.form.refresh_prevention', false);
|
||||
}
|
||||
|
||||
$unique_form_id = filter_input(INPUT_POST, '__unique_form_id__', FILTER_SANITIZE_STRING);
|
||||
|
||||
if ($refresh_prevention && $unique_form_id) {
|
||||
if (($this->grav['session']->unique_form_id != $unique_form_id)) {
|
||||
$this->grav['session']->unique_form_id = $unique_form_id;
|
||||
} else {
|
||||
$status = false;
|
||||
$this->form->message = $this->grav['language']->translate('PLUGIN_FORM.FORM_ALREADY_SUBMITTED');
|
||||
$this->form->status = 'error';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten the forms array into something that can be more easily searched
|
||||
*/
|
||||
protected function flattenForms()
|
||||
{
|
||||
$this->flat_forms = Utils::arrayFlatten($this->forms);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current form, should already be processed but can get it directly from the page if necessary
|
||||
*
|
||||
* @param null $page
|
||||
* @return Form|mixed
|
||||
*/
|
||||
protected function form($page = null)
|
||||
{
|
||||
// Regenerate list of flat_forms if not already populated
|
||||
if (empty($this->flat_forms)) {
|
||||
$this->flattenForms();
|
||||
}
|
||||
|
||||
if (null === $this->form) {
|
||||
$current_form_name = $this->getFormName($this->grav['page']);
|
||||
$this->form = $this->getFormByName($current_form_name);
|
||||
}
|
||||
|
||||
// last attempt using current page's form
|
||||
if (null == $this->form) {
|
||||
|
||||
// try to get the page if possible
|
||||
if ($page == null) {
|
||||
$page = $this->grav['page'];
|
||||
}
|
||||
|
||||
if ($page) {
|
||||
$header = $page->header();
|
||||
|
||||
if (isset($header->form)) {
|
||||
$this->form = new Form($page);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
return $this->form;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
enabled: true
|
||||
built_in_css: true
|
||||
refresh_prevention: false
|
||||
files:
|
||||
multiple: false # To allow multiple files, default is single
|
||||
limit: 10 # Number of allowed files per field (multiple required)
|
||||
destination: 'self@' # Where to upload the files (path and self@, page@, theme@)
|
||||
avoid_overwriting: false # Prevent files with the same name to be overridden. Date prefix will be added
|
||||
random_name: false # Generate a random 15 long string name for the uploaded files
|
||||
filesize: 0 # Maximum file size allowed (in MB)
|
||||
accept: # List of mime/types or file extensions allowed (ie, image/*,.zip,.mp4)
|
||||
- image/*
|
||||
@@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
var gulp = require('gulp'),
|
||||
util = require('util'),
|
||||
path = require('path'),
|
||||
immutable = require('immutable'),
|
||||
gulpWebpack = require('gulp-webpack'),
|
||||
webpack = require('webpack'),
|
||||
sourcemaps = require('gulp-sourcemaps'),
|
||||
exec = require('child_process').execSync,
|
||||
pwd = exec('pwd').toString();
|
||||
|
||||
var plugins = {},
|
||||
base = immutable.fromJS(require('./webpack.conf.js')),
|
||||
options = {
|
||||
prod: base.mergeDeep({
|
||||
devtool: 'source-map',
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': { NODE_ENV: '"production"' }
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
sourceMap: true,
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
}),
|
||||
new webpack.ProvidePlugin(plugins)
|
||||
],
|
||||
output: {
|
||||
filename: 'form.min.js'
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
var compileJS = function(watch) {
|
||||
var prodOpts = options.prod.set('watch', watch);
|
||||
|
||||
return gulp.src('app/main.js')
|
||||
.pipe(gulpWebpack(prodOpts.toJS()))
|
||||
.pipe(gulp.dest('assets/'));
|
||||
};
|
||||
|
||||
gulp.task('js', function() {
|
||||
compileJS(false);
|
||||
});
|
||||
|
||||
gulp.task('watch', function() {
|
||||
compileJS(true);
|
||||
});
|
||||
|
||||
gulp.task('all', ['js']);
|
||||
gulp.task('default', ['all']);
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"project":"grav-plugin-form",
|
||||
"platforms":{
|
||||
"grav":{
|
||||
"nodes":{
|
||||
"plugin":[
|
||||
{
|
||||
"source":"/",
|
||||
"destination":"/user/plugins/form"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
en:
|
||||
PLUGIN_FORM:
|
||||
NOT_VALIDATED: "Form not validated. One or more required fields are missing."
|
||||
NONCE_NOT_VALIDATED: "Oops there was a problem, please check your input and submit the form again."
|
||||
FILES: "Files Upload"
|
||||
FORM_ALREADY_SUBMITTED: "This form has already been submitted."
|
||||
ALLOW_MULTIPLE: "Allow More than one file"
|
||||
ALLOW_MULTIPLE_HELP: "Allows to select more than one file for upload."
|
||||
DESTINATION: "Destination"
|
||||
DESTINATION_HELP: "The location where the files should be uploaded to"
|
||||
ACCEPT: "Allowed MIME Types"
|
||||
ACCEPT_HELP: "A list of MIME Types that are allowed for upload"
|
||||
ERROR_VALIDATING_CAPTCHA: "Error validating the Captcha"
|
||||
DATA_SUMMARY: "Here is the summary of what you wrote to us:"
|
||||
NO_FORM_DATA: "No form data available"
|
||||
RECAPTCHA: "ReCaptcha"
|
||||
RECAPTCHA_SITE_KEY: "Site key"
|
||||
RECAPTCHA_SITE_KEY_HELP: "For more info visit https://developers.google.com/recaptcha"
|
||||
RECAPTCHA_SECRET_KEY: "Secret key"
|
||||
RECAPTCHA_SECRET_KEY_HELP: "For more info visit https://developers.google.com/recaptcha"
|
||||
GENERAL: "General"
|
||||
USE_BUILT_IN_CSS: "Use built-in CSS"
|
||||
FILEUPLOAD_PREVENT_SELF: 'Cannot use "%s" outside of pages.'
|
||||
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Unable to upload file %s: %s'
|
||||
FILEUPLOAD_UNABLE_TO_MOVE: 'Unable to move file %s to "%s"'
|
||||
DROPZONE_CANCEL_UPLOAD: 'Cancel upload'
|
||||
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Are you sure you want to cancel this upload?'
|
||||
DROPZONE_DEFAULT_MESSAGE: 'Drop your files here or <strong>click in this area</strong>'
|
||||
DROPZONE_FALLBACK_MESSAGE: 'Your browser does not support drag and drop file uploads.'
|
||||
DROPZONE_FALLBACK_TEXT: 'Please use the fallback form below to upload your files like in the olden days.'
|
||||
DROPZONE_FILE_TOO_BIG: 'File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.'
|
||||
DROPZONE_INVALID_FILE_TYPE: "You can't upload files of this type."
|
||||
DROPZONE_MAX_FILES_EXCEEDED: "You can not upload any more files."
|
||||
DROPZONE_REMOVE_FILE: "Remove file"
|
||||
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Are you sure you want to delete this file?'
|
||||
DROPZONE_RESPONSE_ERROR: "Server responded with {{statusCode}} code."
|
||||
YES: "Yes"
|
||||
NO: "No"
|
||||
REFRESH_PREVENTION: "Refresh prevention"
|
||||
REFRESH_PREVENTION_HELP: "Use the form's unique ID to ensure the same form is not reprocessed when refreshing the browser"
|
||||
LIMIT: "Limit"
|
||||
LIMIT_HELP: "Maximum number of allowed files per field (multiple required)"
|
||||
FILESIZE: "Max Filesize"
|
||||
FILESIZE_HELP: "Maximum file size allowed (in MB), 0 = Use system default"
|
||||
AVOID_OVERWRITING: "Avoid overwriting"
|
||||
AVOID_OVERWRITING_HELP: "Prevent files with the same name to be overridden. Date prefix will be added"
|
||||
RANDOM_NAME: "Random name"
|
||||
RANDOM_NAME_HELP: "Generate a random 15 long string name for the uploaded files"
|
||||
|
||||
de:
|
||||
PLUGIN_FORM:
|
||||
NOT_VALIDATED: "Formularwerte nicht gültig. Für ein oder mehrere erforderliche Felder fehlen Werte."
|
||||
NONCE_NOT_VALIDATED: "Ups, es gibt da ein Problem. Eingabewerte bitte noch mal prüfen und das Formular erneut absenden."
|
||||
FILES: "Dateien hochladen"
|
||||
ALLOW_MULTIPLE: "Erlaube mehr als eine Datei"
|
||||
ALLOW_MULTIPLE_HELP: "Erlaubt es, mehr als eine Datei zum Hochladen auszuwählen."
|
||||
DESTINATION: "Ziel"
|
||||
DESTINATION_HELP: "Das Ziel, wohin die Dateien hochgeladen werden sollen."
|
||||
ACCEPT: "Erlaube MIME-Typen"
|
||||
ACCEPT_HELP: "Eine Liste von MIME-Typen, die hochgeladen werden dürfen."
|
||||
ERROR_VALIDATING_CAPTCHA: "Die Überprüfung des Captcha ist fehlgeschlagen."
|
||||
|
||||
es:
|
||||
PLUGIN_FORM:
|
||||
NOT_VALIDATED: "Falló la validación del formulario. Uno o más campos obligatorios no fueron cubiertos."
|
||||
NONCE_NOT_VALIDATED: "Oops, hay un problema, por favor revise la información e intente enviar el formulario otra vez."
|
||||
FILES: "Subida de Ficheros"
|
||||
ALLOW_MULTIPLE: "Permitir más de un fichero"
|
||||
ALLOW_MULTIPLE_HELP: "Permitir seleccionar más de un fichero para subir."
|
||||
DESTINATION: "Destino"
|
||||
DESTINATION_HELP: "El lugar de destino al que subir los ficheros"
|
||||
ACCEPT: "MIME Types permitidos"
|
||||
ACCEPT_HELP: "Una lista de MIME Types que se permiten subir."
|
||||
ERROR_VALIDATING_CAPTCHA: "Error al comprobar el Captcha"
|
||||
RECAPTCHA: "ReCaptcha"
|
||||
RECAPTCHA_SITE_KEY: "Site key"
|
||||
RECAPTCHA_SITE_KEY_HELP: "Para más información visita https://developers.google.com/recaptcha"
|
||||
RECAPTCHA_SECRET_KEY: "Secret key"
|
||||
RECAPTCHA_SECRET_KEY_HELP: "Para más información visita https://developers.google.com/recaptcha"
|
||||
|
||||
fr:
|
||||
PLUGIN_FORM:
|
||||
NOT_VALIDATED: "Formulaire non validé. Un ou plusieurs champs obligatoires sont manquants."
|
||||
NONCE_NOT_VALIDATED: "Oups, un problème est survenu. Veuillez vérifier votre saisie et soumettre à nouveau le formulaire."
|
||||
FILES: "Fichiers chargés"
|
||||
ALLOW_MULTIPLE: "Autoriser plus d'un fichier"
|
||||
ALLOW_MULTIPLE_HELP: "Permet la sélection de plusieurs fichiers pour chargement."
|
||||
DESTINATION: "Destination"
|
||||
DESTINATION_HELP: "L'emplacement où les fichiers doivent être chargés."
|
||||
ACCEPT: "Autoriser les Types MIME"
|
||||
ACCEPT_HELP: "Liste des Types MIME autorisés au chargement"
|
||||
ERROR_VALIDATING_CAPTCHA: "Erreur lors de la validation du Captcha"
|
||||
DATA_SUMMARY: "Voici le résumé de ce que vous nous avez écrit :"
|
||||
NO_FORM_DATA: "Aucune donnée de formulaire disponible"
|
||||
RECAPTCHA: "ReCaptcha"
|
||||
RECAPTCHA_SITE_KEY: "Clé du site"
|
||||
RECAPTCHA_SITE_KEY_HELP: "Pour plus d'informations veuillez vous rendre sur https://developers.google.com/recaptcha"
|
||||
RECAPTCHA_SECRET_KEY: "Clé secrète"
|
||||
RECAPTCHA_SECRET_KEY_HELP: "Pour plus d'informations veuillez vous rendre sur https://developers.google.com/recaptcha"
|
||||
GENERAL: "Général"
|
||||
USE_BUILT_IN_CSS: "Utiliser les CSS natifs"
|
||||
FILEUPLOAD_PREVENT_SELF: "Impossible d'utiliser '%s' en dehors des pages."
|
||||
FILEUPLOAD_UNABLE_TO_UPLOAD: "Impossible de charger le fichier %s: %s"
|
||||
FILEUPLOAD_UNABLE_TO_MOVE: 'Impossible de déplacer le fichier %s vers "%s"'
|
||||
DROPZONE_CANCEL_UPLOAD: "Annuler le chargement"
|
||||
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: "Êtes-vous certain de vouloir annuler ce téléchargement ?"
|
||||
DROPZONE_DEFAULT_MESSAGE: "Glissez vos fichiers ici ou <strong>cliquez dans cette zone</strong>"
|
||||
DROPZONE_FALLBACK_MESSAGE: "Votre navigateur ne prend pas en charge les téléchargements par glissé-déposé."
|
||||
DROPZONE_FALLBACK_TEXT: "Veuillez utiliser le formulaire de secours ci-dessous pour transférer vos fichiers."
|
||||
DROPZONE_FILE_TOO_BIG: "Le fichier est trop volumineux ({{filesize}}MiB). Taille maximale de fichier : {{maxFilesize}}MiB."
|
||||
DROPZONE_INVALID_FILE_TYPE: "Vous ne pouvez pas charger des fichiers de ce type."
|
||||
DROPZONE_MAX_FILES_EXCEEDED: "Vous ne pouvez plus télécharger de fichiers."
|
||||
DROPZONE_REMOVE_FILE: "Supprimer le fichier"
|
||||
DROPZONE_REMOVE_FILE_CONFIRMATION: "Êtes-vous sûr de vouloir supprimer ce fichier ?"
|
||||
DROPZONE_RESPONSE_ERROR: "Le serveur a répondu avec le code {{statusCode}}."
|
||||
|
||||
ru:
|
||||
PLUGIN_FORM:
|
||||
NOT_VALIDATED: "Форма не подтверждена. Отсутствует одно или несколько обязательных полей."
|
||||
NONCE_NOT_VALIDATED: "Упс, у вас возникла проблема, проверьте свои данные и отправьте форму еще раз."
|
||||
FILES: "Загрузка файлов"
|
||||
FORM_ALREADY_SUBMITTED: "Эта форма уже была отправлена."
|
||||
ALLOW_MULTIPLE: "Разрешить несколько файлов"
|
||||
ALLOW_MULTIPLE_HELP: "Позволяет выбрать более одного файла для загрузки."
|
||||
DESTINATION: "Место назначения"
|
||||
DESTINATION_HELP: "Место, куда файлы должны быть загружены в"
|
||||
ACCEPT: "Разрешенные MIME типы"
|
||||
ACCEPT_HELP: "Список MIME типов, разрешенных для загрузки"
|
||||
ERROR_VALIDATING_CAPTCHA: "Ошибка проверки Captcha"
|
||||
DATA_SUMMARY: "Вот краткое изложение того, что вы нам написали:"
|
||||
NO_FORM_DATA: "Данные формы отсутствуют"
|
||||
RECAPTCHA: "ReCaptcha"
|
||||
RECAPTCHA_SITE_KEY: "Site key"
|
||||
RECAPTCHA_SITE_KEY_HELP: "Для получения дополнительной информации посетите https://developers.google.com/recaptcha"
|
||||
RECAPTCHA_SECRET_KEY: "Secret key"
|
||||
RECAPTCHA_SECRET_KEY_HELP: "Для получения дополнительной информации посетите https://developers.google.com/recaptcha"
|
||||
GENERAL: "Общие"
|
||||
USE_BUILT_IN_CSS: "Использовать встроенный CSS"
|
||||
FILEUPLOAD_PREVENT_SELF: 'Нельзя использовать "%s" за пределами страниц.'
|
||||
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Не удалось загрузить файл %s: %s'
|
||||
FILEUPLOAD_UNABLE_TO_MOVE: 'Не удалось переместить файл %s в "%s"'
|
||||
DROPZONE_CANCEL_UPLOAD: 'Отменить загрузку'
|
||||
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Вы действительно хотите отменить эту загрузку?'
|
||||
DROPZONE_DEFAULT_MESSAGE: 'Перетащите свои файлы сюда или <strong>щелкните в этой области</strong>'
|
||||
DROPZONE_FALLBACK_MESSAGE: 'Ваш браузер не поддерживает загрузку файлов с перетаскиванием.'
|
||||
DROPZONE_FALLBACK_TEXT: 'Пожалуйста, используйте приведенную ниже форму для загрузки ваших файлов, как в старые времена.'
|
||||
DROPZONE_FILE_TOO_BIG: 'Файл слишком большой ({{filesize}}мб). Максимальный размер файла: {{maxFilesize}}мб.'
|
||||
DROPZONE_INVALID_FILE_TYPE: "Вы не можете загружать файлы этого типа."
|
||||
DROPZONE_MAX_FILES_EXCEEDED: "Вы не можете загружать больше файлов."
|
||||
DROPZONE_REMOVE_FILE: "Удалить файл"
|
||||
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Вы действительно хотите удалить этот файл?'
|
||||
DROPZONE_RESPONSE_ERROR: "Сервер ответил кодом {{statusCode}}."
|
||||
YES: "Да"
|
||||
NO: "Нет"
|
||||
REFRESH_PREVENTION: "Предотвращение обновления"
|
||||
REFRESH_PREVENTION_HELP: "Используйте уникальный идентификатор формы, чтобы гарантировать, что одна и та же форма не будет отправляться заново при обновлении страницы браузера"
|
||||
LIMIT: "Лимит"
|
||||
LIMIT_HELP: "Максимальное количество разрешенных файлов в поле (требуется несколько)"
|
||||
FILESIZE: "Максимальный размер файла"
|
||||
FILESIZE_HELP: "Максимально допустимый размер файла (в МБ), 0 = Использовать системные установки по умолчанию"
|
||||
AVOID_OVERWRITING: "Избегать перезаписи"
|
||||
AVOID_OVERWRITING_HELP: "Не допускать перезаписи файлов с тем же именем. Будет добавлен префикс с датой"
|
||||
RANDOM_NAME: "Случайное имя"
|
||||
RANDOM_NAME_HELP: "Создавать случайное строковое имя из 15 символов для загруженных файлов"
|
||||
|
||||
hr:
|
||||
PLUGIN_FORM:
|
||||
NOT_VALIDATED: "Formular nije validiran. Jedan ili više traženih polja nedostaju."
|
||||
NONCE_NOT_VALIDATED: "Ups, došlo je do problema, molimo provjerite svoj unos i pokušajte opet."
|
||||
FILES: "Upload Fajlova"
|
||||
ALLOW_MULTIPLE: "Dopusti više od jednog fajla"
|
||||
DESTINATION: "Destinacija"
|
||||
DESTINATION_HELP: "Lokacija gdje bi fajlovi trebali biti uploadani"
|
||||
ACCEPT: "Dopušteni MIME Tipovi"
|
||||
ACCEPT_HELP: "Lista dopuštenih MIME Tipova koji su dozvoljeni za upload"
|
||||
ERROR_VALIDATING_CAPTCHA: "Greška pri validiranju Captcha"
|
||||
|
||||
hu:
|
||||
PLUGIN_FORM:
|
||||
NOT_VALIDATED: "Érvénytelen az űrlap. Egy vagy több kötelező mező nincs kitöltve."
|
||||
NONCE_NOT_VALIDATED: "Upsz, van egy kis probléma, kérlek nézd át az űrlapot, majd küldd el újra."
|
||||
FILES: "Fájlok Feltöltése"
|
||||
ALLOW_MULTIPLE: "Több fájl feltöltése"
|
||||
ALLOW_MULTIPLE_HELP: "Engedélyezi egyszerre több állomány feltöltését."
|
||||
DESTINATION: "Feltöltés Helye"
|
||||
DESTINATION_HELP: "Ide lesznek feltöltve az állományok"
|
||||
ACCEPT: "Engedélyezett MIME-típusok"
|
||||
ACCEPT_HELP: "A feltölthető állományok MIME-típusainak listája"
|
||||
ERROR_VALIDATING_CAPTCHA: "Hiba lépett fel a Captcha validálása során"
|
||||
|
||||
it:
|
||||
PLUGIN_FORM:
|
||||
NOT_VALIDATED: "Il Form risulta invalido. Uno o più campi risultano omessi."
|
||||
NONCE_NOT_VALIDATED: "Oops è stato riscontrato un errore, si prega di ricontrollare i dati inseriti e provare di nuovo."
|
||||
FILES: "Invio dei Files"
|
||||
ALLOW_MULTIPLE: "Consenti più di un file"
|
||||
ALLOW_MULTIPLE_HELP: "Permette la selezione di più di un file per l'upload"
|
||||
DESTINATION: "Destinazione"
|
||||
DESTINATION_HELP: "La destinazione dove i files vengono uploadati"
|
||||
ACCEPT: "Tipi di MIME Concessi"
|
||||
ACCEPT_HELP: "Una lista di tipi di MIME che sono permessi per l'upload"
|
||||
ERROR_VALIDATING_CAPTCHA: "Errore durante la validazione del Captcha"
|
||||
DATA_SUMMARY: "Ecco il riassunto di ciò che ci hai scritto:"
|
||||
NO_FORM_DATA: "Nessuna informazione disponibile"
|
||||
RECAPTCHA: "ReCaptcha"
|
||||
RECAPTCHA_SITE_KEY: "Site key"
|
||||
RECAPTCHA_SITE_KEY_HELP: "Per maggiori informazioni visita https://developers.google.com/recaptcha"
|
||||
RECAPTCHA_SECRET_KEY: "Chiave segreta"
|
||||
RECAPTCHA_SECRET_KEY_HELP: "Per maggiori informazioni visita https://developers.google.com/recaptcha"
|
||||
GENERAL: "Generale"
|
||||
USE_BUILT_IN_CSS: "Usa CSS incorporato"
|
||||
FILEUPLOAD_PREVENT_SELF: 'Non si può usare "%s" fuori dalle pagine.'
|
||||
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Impossibile caricare il file %s: %s'
|
||||
FILEUPLOAD_UNABLE_TO_MOVE: 'Impossibile muovere il file %s to "%s"'
|
||||
DROPZONE_CANCEL_UPLOAD: 'Trasferimento annullato'
|
||||
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Sei sicuro di voler cancellare questo trasferimento?'
|
||||
DROPZONE_DEFAULT_MESSAGE: "Trascina qui i tuoi file o <strong>clicca su quest'area</strong>"
|
||||
DROPZONE_FALLBACK_MESSAGE: 'Il tuo browser non supporta il trascinamento dei file per il trasferimento.'
|
||||
DROPZONE_FALLBACK_TEXT: 'Utilizza il modulo di riserva qui sotto per caricare i tuoi file come ai vecchi tempi.'
|
||||
DROPZONE_FILE_TOO_BIG: 'Il file è troppo grande ({{filesize}}MiB). Dimensione massima consentita: {{maxFilesize}}MiB.'
|
||||
DROPZONE_INVALID_FILE_TYPE: "Non puoi caricare questo tipo di file"
|
||||
DROPZONE_MAX_FILES_EXCEEDED: "Non puoi caricare ulteriori file, hai raggiunto il limite consentito."
|
||||
DROPZONE_REMOVE_FILE: "Rimuovi il file"
|
||||
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Sei sicuro di voler eliminare questo file??'
|
||||
DROPZONE_RESPONSE_ERROR: "Il Server ha risposto con il codice {{statusCode}}."
|
||||
YES: "Si"
|
||||
NO: "No"
|
||||
|
||||
ro:
|
||||
PLUGIN_FORM:
|
||||
NOT_VALIDATED: "Formularul nu a fost validat. Unul sau mai multe câmpuri sunt goale."
|
||||
NONCE_NOT_VALIDATED: "Oops a apărut o problemă, vă rugăm verificați datele introduse și trimiteți formularul din nou."
|
||||
FILES: "Încărcare fișiere"
|
||||
ALLOW_MULTIPLE: "Permiteți mai multe fișiere"
|
||||
ALLOW_MULTIPLE_HELP: "Vă permite să selectați mai multe fișiere pentru încărcare."
|
||||
DESTINATION: "Destinație"
|
||||
DESTINATION_HELP: "Locația unde vor fi încărcate fișierele."
|
||||
ACCEPT: "Permite tipuri MIME "
|
||||
ACCEPT_HELP: "O listă cu tipuri MIME care sunt permise la încărcare."
|
||||
ERROR_VALIDATING_CAPTCHA: "Eroare la validarea Captcha."
|
||||
DATA_SUMMARY: "Mai jos aveți un rezumat al mesajului pe care ni l-ați trimis:"
|
||||
NO_FORM_DATA: "Nu e disponibilă nici o dată pentru formular"
|
||||
RECAPTCHA: "ReCaptcha"
|
||||
RECAPTCHA_SITE_KEY: "Cheia pentru Site"
|
||||
RECAPTCHA_SITE_KEY_HELP: "Pentru mai multe detalii vă rugăm vizitați https://developers.google.com/recaptcha"
|
||||
RECAPTCHA_SECRET_KEY: "Cheia secretă pentru Site"
|
||||
RECAPTCHA_SECRET_KEY_HELP: "Pentru mai multe detalii vă rugăm vizitați https://developers.google.com/recaptcha"
|
||||
|
||||
cs:
|
||||
PLUGIN_FORM:
|
||||
NOT_VALIDATED: "Formulář nebyl ověřen. Chybí jedno nebo více povinných polí."
|
||||
NONCE_NOT_VALIDATED: "Jejda, došlo k problému, zkontrolujte vstupní stránku a znovu odešlete formulář."
|
||||
FILES: "Nahrávání souborů"
|
||||
ALLOW_MULTIPLE: "Povolit více než jeden soubor"
|
||||
ALLOW_MULTIPLE_HELP: "Umožňuje vybrat více než jeden soubor pro nahrání."
|
||||
DESTINATION: "Cílové umístění"
|
||||
DESTINATION_HELP: "Místo, kam mají být soubory nahrány"
|
||||
ACCEPT: "Povolené MIME typy"
|
||||
ACCEPT_HELP: "Seznam MIME typů souborů povolených pro upload"
|
||||
ERROR_VALIDATING_CAPTCHA: "Nepodařilo se ověřit CAPTCHA (kontrola proti spamu)"
|
||||
DATA_SUMMARY: "Shrnutí toho, co jste nám napsali:"
|
||||
NO_FORM_DATA: "Formulář neobsahuje žádná data"
|
||||
RECAPTCHA: "ReCaptcha"
|
||||
RECAPTCHA_SITE_KEY: "Site key"
|
||||
RECAPTCHA_SITE_KEY_HELP: "Více informací https://developers.google.com/recaptcha"
|
||||
RECAPTCHA_SECRET_KEY: "Secret key"
|
||||
RECAPTCHA_SECRET_KEY_HELP: "Více informací https://developers.google.com/recaptcha"
|
||||
GENERAL: "Všeobecné"
|
||||
USE_BUILT_IN_CSS: "Použít built-in CSS"
|
||||
FILEUPLOAD_PREVENT_SELF: 'Nelze použít "% s" mimo stránky.'
|
||||
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Nelze nahrát soubor %s: %s'
|
||||
FILEUPLOAD_UNABLE_TO_MOVE: 'Nelze přesunout soubor %s do "%s"'
|
||||
DROPZONE_CANCEL_UPLOAD: 'Zrušit upload'
|
||||
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Opravdu chcete zrušit nahrávání souboru?'
|
||||
DROPZONE_DEFAULT_MESSAGE: 'Přetáhněte sem soubory nebo <strong>klikněte v tomto prostoru</strong>'
|
||||
DROPZONE_FALLBACK_MESSAGE: 'Váš prohlížeč nepodporuje nahrávání souborů táhni a pusť.'
|
||||
DROPZONE_FALLBACK_TEXT: 'Použijte níže uvedený formulář pro nahrání souborů, jako v minulých dnech.'
|
||||
DROPZONE_FILE_TOO_BIG: 'Soubor je příliš velký ({{filesize}}MiB). Max. velikost souboru: {{maxFilesize}}MiB.'
|
||||
DROPZONE_INVALID_FILE_TYPE: "Nelze nahrát soubory tohoto typu."
|
||||
DROPZONE_MAX_FILES_EXCEEDED: "Nelze nahrát další soubory."
|
||||
DROPZONE_REMOVE_FILE: "Odstranit soubor"
|
||||
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Opravdu chcete tento soubor smazat?'
|
||||
DROPZONE_RESPONSE_ERROR: "Server vrátil chybový kód: {{statusCode}}."
|
||||
YES: "Ano"
|
||||
NO: "Ne"
|
||||
pl:
|
||||
PLUGIN_FORM:
|
||||
NOT_VALIDATED: "Formularz został odrzucony. Jedno lub więcej pól wymaga wypełnienia."
|
||||
NONCE_NOT_VALIDATED: "Ups, pojawił się problem. Proszę sprawdź czy wszystkie pola zostały wypełnione i wyślij formularz ponownie."
|
||||
FILES: "Plik przesłany."
|
||||
FORM_ALREADY_SUBMITTED: "Ten formularz został już wysłany."
|
||||
ALLOW_MULTIPLE: "Zezwól na więcej niż jeden plik."
|
||||
ALLOW_MULTIPLE_HELP: "Zezwól na przesłanie więcej niż jednego pliku."
|
||||
DESTINATION: "Przeznaczenie."
|
||||
DESTINATION_HELP: "Lokalizacja przesłanego pliku."
|
||||
ACCEPT: "Zezwól na typ MIME."
|
||||
ACCEPT_HELP: "Lista wspieranych do przesłania typów MIME."
|
||||
ERROR_VALIDATING_CAPTCHA: "Błąd walidacji Captcha."
|
||||
DATA_SUMMARY: "Oto podsumowanie informacji, które do nas napisałeś:"
|
||||
NO_FORM_DATA: "Brak dostępnych danych formularza"
|
||||
RECAPTCHA: "ReCaptcha"
|
||||
RECAPTCHA_SITE_KEY: "Klucz strony"
|
||||
RECAPTCHA_SITE_KEY_HELP: "Aby uzyskać więcej informacji odwiedź https://developers.google.com/recaptcha"
|
||||
RECAPTCHA_SECRET_KEY: "Tajny klucz"
|
||||
RECAPTCHA_SECRET_KEY_HELP: "Aby uzyskać więcej informacji odwiedź https://developers.google.com/recaptcha"
|
||||
GENERAL: "Ogólne"
|
||||
USE_BUILT_IN_CSS: "Użyj wbudowanych styli CSS"
|
||||
FILEUPLOAD_PREVENT_SELF: 'Nie można użyć "%s" poza stronami.'
|
||||
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Nie można przesłać plików %s: %s'
|
||||
FILEUPLOAD_UNABLE_TO_MOVE: 'Nie można przenieść plików z %s do "%s"'
|
||||
DROPZONE_CANCEL_UPLOAD: 'Anuluj przesyłanie'
|
||||
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Czy na pewno chcesz anulować przesyłanie?'
|
||||
DROPZONE_DEFAULT_MESSAGE: 'Upuść pliki tutaj lub <strong>kliknij w tym obszarze</strong>'
|
||||
DROPZONE_FALLBACK_MESSAGE: 'Twoja przelądarka nie obsługują funkcji przeciągnij i upuść.'
|
||||
DROPZONE_FALLBACK_TEXT: 'Proszę użyć zapasowego formularza do przesyłania plików znajdującego się poniżej.'
|
||||
DROPZONE_FILE_TOO_BIG: 'Plik jest zbyt duży ({{filesize}}MiB). Maksymalna wielkość pliku to: {{maxFilesize}}MiB.'
|
||||
DROPZONE_INVALID_FILE_TYPE: "Nie można przesłać pliku. Format nie jest obsługiwany."
|
||||
DROPZONE_MAX_FILES_EXCEEDED: "Nie można przesłać więcej plików."
|
||||
DROPZONE_REMOVE_FILE: "Usuń plik"
|
||||
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Czy na pewno chcesz usunąć plik?'
|
||||
DROPZONE_RESPONSE_ERROR: "Status serwera {{statusCode}}."
|
||||
YES: "Tak"
|
||||
NO: "Nie"
|
||||
REFRESH_PREVENTION: "Zapobiegaj odświeżaniu"
|
||||
REFRESH_PREVENTION_HELP: "Użyj unikatowego identyfikatora ID , aby upewnić się, że przeglądarka nie przetworzy formularza ponownie."
|
||||
|
||||
nl:
|
||||
PLUGIN_FORM:
|
||||
NOT_VALIDATED: "Formulier niet gevalideerd. Een of meerdere verplichte velden ontbreken."
|
||||
NONCE_NOT_VALIDATED: "Oeps er was een probleem, controleer je input en stuur het formulier opnieuw."
|
||||
FILES: "Bestanden geupload"
|
||||
FORM_ALREADY_SUBMITTED: "Dit formulier is al eerder verstuurd."
|
||||
ALLOW_MULTIPLE: "Meer dan een bestand toegestaan"
|
||||
ALLOW_MULTIPLE_HELP: "Staat toe dat meer dan een bestand geselecteerd kan worden voor uploaden."
|
||||
DESTINATION: "Bestemming"
|
||||
DESTINATION_HELP: "De lokatie waar de bestanden naartoe moeten worden geupload"
|
||||
ACCEPT: "Toegestane MIME types"
|
||||
ACCEPT_HELP: "Een lijst van MIME types welke zijn toegestaan om te uploaden"
|
||||
ERROR_VALIDATING_CAPTCHA: "Fout bij Captcha validatie"
|
||||
DATA_SUMMARY: "Hier is de samenvatting van wat u ons heeft gestuurd:"
|
||||
NO_FORM_DATA: "Geen formdata beschikbaar"
|
||||
RECAPTCHA: "ReCaptcha"
|
||||
RECAPTCHA_SITE_KEY: "Site sleutel"
|
||||
RECAPTCHA_SITE_KEY_HELP: "Voor meer informatie ga naar https://developers.google.com/recaptcha"
|
||||
RECAPTCHA_SECRET_KEY: "Geheime sleutel"
|
||||
RECAPTCHA_SECRET_KEY_HELP: "Voor meer informatie ga naar https://developers.google.com/recaptcha"
|
||||
GENERAL: "Algemeen"
|
||||
USE_BUILT_IN_CSS: "Gebruik ingebouwde CSS"
|
||||
FILEUPLOAD_PREVENT_SELF: 'Kan "%s" niet buiten paginas gebruiken.'
|
||||
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Niet mogelijk om bestand %s: %s te uploaden'
|
||||
FILEUPLOAD_UNABLE_TO_MOVE: 'Niet mogelijk om bestand %s naar "%s" te verplaatsen'
|
||||
DROPZONE_CANCEL_UPLOAD: 'Upload annuleren'
|
||||
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Weet u zeker dat u deze upload wilt annuleren?'
|
||||
DROPZONE_DEFAULT_MESSAGE: 'Sleep hier uw bestanden heen of <strong>klik in dit gebied</strong>'
|
||||
DROPZONE_FALLBACK_MESSAGE: 'Uw browser ondersteund geen drag en drop bestand uploads.'
|
||||
DROPZONE_FALLBACK_TEXT: 'Gebruik alsublieft het fallback formulier hieronder om uw bestanden te uploaden zoals vroeger.'
|
||||
DROPZONE_FILE_TOO_BIG: 'Bestand is te groot ({{filesize}}MiB). Maximale bestandsgrootte: {{maxFilesize}}MiB.'
|
||||
DROPZONE_INVALID_FILE_TYPE: "U kunt geen bestanden uploaden van dit type."
|
||||
DROPZONE_MAX_FILES_EXCEEDED: "U kunt niet meer bestanden uploaden."
|
||||
DROPZONE_REMOVE_FILE: "Verwijder bestand"
|
||||
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Weet u zeker dat u het bestand wilt verwijderen?'
|
||||
DROPZONE_RESPONSE_ERROR: "Server reageert met {{statusCode}} code."
|
||||
YES: "Ja"
|
||||
NO: "Nee"
|
||||
REFRESH_PREVENTION: "Verversbescherming"
|
||||
REFRESH_PREVENTION_HELP: "Gebruik het unieke ID van her formulier om zeker te stellen dat hetzelfde formulier niet opnieuw wordt verwerkt als de browser ververst wordt"
|
||||
LIMIT: "Limiet"
|
||||
LIMIT_HELP: "Maximum aantal toegestande bestanden per veld (meerdere vereist)"
|
||||
FILESIZE: "Maximale bestandsgrootte"
|
||||
FILESIZE_HELP: "Maximale bestandsgrootte toegestaan (in MB), 0 = Gebruik systeem standaard"
|
||||
AVOID_OVERWRITING: "Vermijd overschrijven"
|
||||
AVOID_OVERWRITING_HELP: "Voorkom dat bestanden met dezelfde naam worden overschreven. Datum prefix wordt toegevoegd"
|
||||
RANDOM_NAME: "Willekeurige naam"
|
||||
RANDOM_NAME_HELP: "Genereer een willekeurige naam van 15 karakters voor de geuploade bestanden"
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "grav-plugin-form",
|
||||
"version": "1.0.0",
|
||||
"description": "Grav Plugin Form",
|
||||
"repository": "https://github.com/getgrav/grav-plugin-form",
|
||||
"main": "app/main.js",
|
||||
"scripts": {
|
||||
"watch": "webpack --watch --progress --colors --config webpack.conf.js",
|
||||
"dev": "webpack --progress --colors --config webpack.conf.js",
|
||||
"prod": "NODE_ENV=production webpack -p --config webpack.conf.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "RocketTheme, LLC",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dropzone": "^4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.14.0",
|
||||
"babel-loader": "^6.2.5",
|
||||
"babel-polyfill": "^6.13.0",
|
||||
"babel-preset-es2015": "^6.14.0",
|
||||
"eslint": "^3.4.0",
|
||||
"eslint-loader": "^1.5.0",
|
||||
"exports-loader": "^0.6.3",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-sourcemaps": "^1.6.0",
|
||||
"gulp-util": "^3.0.7",
|
||||
"gulp-webpack": "^1.5.0",
|
||||
"immutable": "^3.8.1",
|
||||
"imports-loader": "^0.6.5",
|
||||
"json-loader": "^0.5.4",
|
||||
"webpack": "^1.13.2"
|
||||
}
|
||||
}
|
||||
@@ -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,8 @@
|
||||
{% extends 'partials/base.html.twig' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{{ content|raw }}
|
||||
{% include "forms/form.html.twig" %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1 @@
|
||||
{% extends 'forms/ajax.json.twig' %}
|
||||
@@ -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,39 @@
|
||||
{% macro render_field(form, fields) %}
|
||||
{% for index, field in fields %}
|
||||
{% set input = attribute(field, "input@") %}
|
||||
{% if input is null or input == true %}
|
||||
{% if form.value(field.name) %}
|
||||
{% block field %}
|
||||
<div>
|
||||
{% block field_label %}
|
||||
<strong>{{ field.label|t|e }}</strong>:
|
||||
{% endblock %}
|
||||
|
||||
{% block field_value %}
|
||||
{% if field.type == 'checkboxes' %}
|
||||
<ul>
|
||||
{% for value in form.value(field.name) %}
|
||||
<li>{{ field.options[value]|e }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% elseif field.type == 'checkbox' %}
|
||||
{{ (form.value(field.name) == 1) ? "PLUGIN_FORM.YES"|t|e : "PLUGIN_FORM.NO"|t|e }}
|
||||
{% elseif field.type == 'select' %}
|
||||
{{ field.options[form.value(field.name)]|e }}
|
||||
{% else %}
|
||||
{{ string(form.value(field.name))|nl2br }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if field.fields %}
|
||||
{{ _self.render_field(form, field.fields) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
|
||||
{{ _self.render_field(form, form.fields) }}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{%- macro render_field(form, fields) %}
|
||||
{%- for index, field in fields %}
|
||||
{%- set input = attribute(field, "input@") %}
|
||||
{%- if input is null or input == true %}
|
||||
{%- set value = form.value(field.name ?? index) %}
|
||||
{{- field.name ?? index }}: {{ string(value is iterable ? value|json_encode : value|escape('yaml')) ~ "\r\n" }}
|
||||
{%- else %}
|
||||
{%- if field.fields %}
|
||||
{{- _self.render_field(form, field.fields) }}
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
{%- endmacro %}
|
||||
{%- autoescape false %}
|
||||
{{- _self.render_field(form, form.fields) ~ "\r\n" }}
|
||||
{%- endautoescape %}
|
||||
@@ -0,0 +1,98 @@
|
||||
{% set originalValue = originalValue is defined ? originalValue : value %}
|
||||
{% set toggleableChecked = field.toggleable and (originalValue is not null and originalValue is not empty) %}
|
||||
{% set isDisabledToggleable = field.toggleable and not toggleableChecked %}
|
||||
{% set value = (is_object(value) or value is null ? field.default : value) %}
|
||||
{% set cookie_name = 'forms-' ~ form.name ~ '-' ~ field.name %}
|
||||
{% set value = (is_object(value) or value is null ? (get_cookie(cookie_name) is null ? field.default : get_cookie(cookie_name)) : value) %}
|
||||
{% set errors = attribute(form.messages, field.name) %}
|
||||
|
||||
{% block field %}
|
||||
<div class="{{ form_field_outer_classes ?: 'form-field' }} {{ field.outerclasses }} {% if errors %} has-errors{% endif %} {% block outer_field_classes %}{% endblock %}">
|
||||
{% block contents %}
|
||||
{% if field.label is not same as(false) %}
|
||||
<div class="{{ form_field_outer_label_classes ?: 'form-label' }} {{ field.labelclasses }}">
|
||||
<label class="{{ form_field_label_classes ?: 'inline' }}" {% if field.id is defined %}for="{{ field.id|e }}" {% endif %} >
|
||||
{% block label %}
|
||||
{% if field.help %}
|
||||
{% if field.markdown %}
|
||||
<span class="tooltip" data-asTooltip-position="w" title="{{ field.help|t|markdown(false)|e }}">{{ field.label|markdown(false)|default(field.name|capitalize)|t }}</span>
|
||||
{% else %}
|
||||
<span class="tooltip" data-asTooltip-position="w" title="{{ field.help|t|e }}">{{ field.label|default(field.name|capitalize)|t }}</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if field.markdown %}
|
||||
{{ field.label|markdown(false)|default(field.name|capitalize)|t }}
|
||||
{% else %}
|
||||
{{ field.label|default(field.name|capitalize)|t }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{{ field.validate.required in ['on', 'true', 1] ? '<span class="required">*</span>' }}
|
||||
{% endblock %}
|
||||
</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="{{ form_field_outer_data_classes ?: 'form-data' }} {{ field.dataclasses }}"
|
||||
{% block global_attributes %}
|
||||
data-grav-field="{{ field.type }}"
|
||||
data-grav-disabled="{{ originalValue is null ? 'true' : 'false' }}"
|
||||
data-grav-default="{{ field.default|json_encode()|e('html_attr') }}"
|
||||
{% endblock %}
|
||||
>
|
||||
{% block group %}
|
||||
{% block input %}
|
||||
<div class="{{ form_field_wrapper_classes ?: 'form-input-wrapper' }} {{ field.size }} {{ field.wrapper_classes }}">
|
||||
{% block prepend %}{% endblock prepend %}
|
||||
<input
|
||||
{# required attribute structures #}
|
||||
name="{{ (scope ~ field.name)|fieldName }}"
|
||||
value="{{ value|join(', ')|e('html_attr') }}"
|
||||
{# input attribute structures #}
|
||||
{% block input_attributes %}
|
||||
class="{{ form_field_input_classes }} {{ field.classes }}"
|
||||
{% 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.attributes is defined %}
|
||||
{% for attribute in field.attributes %}
|
||||
{{ attribute.name }}="{{ attribute.value|e }}"
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if field.validate.required in ['on', 'true', 1] %}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 %}
|
||||
{% endblock %}
|
||||
/>
|
||||
{% block append %}{% endblock append %}
|
||||
{% if form.inline_errors and errors %}
|
||||
<div class="{{ form_errors_classes ?: 'form-errors' }}">
|
||||
{% for error in errors %}
|
||||
<p class="form-message"><i class="fa fa-exclamation-circle"></i> {{ error }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
{% if field.description %}
|
||||
<div class="form-extra-wrapper {{ field.size|e }} {{ field.wrapper_classes }}">
|
||||
<span class="form-description">
|
||||
{% if field.markdown %}
|
||||
{{ field.description|tu|markdown(false)|raw }}
|
||||
{% else %}
|
||||
{{ field.description|tu|raw }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,83 @@
|
||||
{% if form is null %}
|
||||
{% set form = grav.session.getFlashObject('form') %}
|
||||
{% endif %}
|
||||
|
||||
{% include 'partials/form-messages.html.twig' %}
|
||||
|
||||
{% set scope = scope ?: 'data.' %}
|
||||
{% set multipart = '' %}
|
||||
{% set method = form.method|upper|default('POST') %}
|
||||
|
||||
{% for field in form.fields %}
|
||||
{% if (method == 'POST' and field.type == 'file') %}
|
||||
{% set multipart = ' enctype="multipart/form-data"' %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% set action = form.action ? base_url ~ form.action : base_url ~ page.route ~ uri.params %}
|
||||
|
||||
{% if (action == base_url_relative) %}
|
||||
{% set action = base_url_relative ~ '/' ~ page.slug %}
|
||||
{% endif %}
|
||||
|
||||
<form name="{{ form.name }}"
|
||||
action="{{ action | trim('/', 'right') }}"
|
||||
method="{{ method }}"{{ multipart }}
|
||||
{% if form.id %}id="{{ form.id }}"{% endif %}
|
||||
{% block form_classes %}
|
||||
class="{{ form_outer_classes }} {{ form.classes }}"
|
||||
{% endblock %}
|
||||
{% if form.novalidate %}novalidate{% endif %}
|
||||
>
|
||||
|
||||
{% block inner_markup_fields_start %}{% endblock %}
|
||||
|
||||
{% for field in form.fields %}
|
||||
{% if field.type == 'file' %}
|
||||
{% do assets.addJs('plugin://form/assets/form.min.js') %}
|
||||
{% endif %}
|
||||
{% set value = form.value(field.name) %}
|
||||
{% include "forms/fields/#{field.type}/#{field.type}.html.twig" ignore missing %}
|
||||
{% endfor %}
|
||||
|
||||
{% include "forms/fields/formname/formname.html.twig" %}
|
||||
|
||||
{% block inner_markup_fields_end %}{% endblock %}
|
||||
|
||||
{% block inner_markup_buttons_start %}
|
||||
<div class="{{ form_button_outer_classes ?: 'buttons'}}">
|
||||
{% endblock %}
|
||||
|
||||
{% for button in form.buttons %}
|
||||
{% if button.outerclasses is defined %}<div class=" {{ button.outerclasses }}">{% endif %}
|
||||
{% if button.url %}
|
||||
<a href="{{ button.url starts with 'http' ? button.url : url(button.url) }}">
|
||||
{% endif %}
|
||||
<button
|
||||
{% if button.id %}id="{{ button.id }}"{% endif %}
|
||||
{% block button_classes %}
|
||||
class="{{ form_button_classes ?: 'button' }} {{ button.classes }}"
|
||||
{% endblock %}
|
||||
{% if button.disabled %}disabled="disabled"{% endif %}
|
||||
|
||||
type="{{ button.type|default('submit') }}"
|
||||
|
||||
{% if button.task %}
|
||||
name="task" value="{{ button.task }}"
|
||||
{% endif %}
|
||||
>
|
||||
{{ button.value|t|default('Submit') }}
|
||||
</button>
|
||||
{% if button.url %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if button.outerclasses is defined %}</div>{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% block inner_markup_buttons_end %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% include 'forms/fields/uniqueid/uniqueid.html.twig' %}
|
||||
{{ nonce_field('form', 'form-nonce')|raw }}
|
||||
</form>
|
||||
@@ -0,0 +1 @@
|
||||
{% extends "forms/default/field.html.twig" %}
|
||||
@@ -0,0 +1,5 @@
|
||||
{% if form.data.avatar %}
|
||||
<label><img style="max-width:200px;" src="{{ base_url_simple ~ '/' ~ (form.data.avatar|first).path }}" /></label>
|
||||
{% else %}
|
||||
<label><img src="https://www.gravatar.com/avatar/{{ form.data.email|md5 }}?s=200" /></label>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,32 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input %}
|
||||
<script>
|
||||
var captchaOnloadCallback = function captchaOnloadCallback() {
|
||||
grecaptcha.render('g-recaptcha', {
|
||||
'sitekey': "{{field.recaptcha_site_key ?: field.recaptcha_site_key ?: config.plugins.form.recaptcha.site_key }}",
|
||||
'callback': captchaValidatedCallback,
|
||||
'expired-callback': captchaExpiredCallback
|
||||
});
|
||||
};
|
||||
|
||||
var captchaValidatedCallback = function captchaValidatedCallback() {
|
||||
|
||||
};
|
||||
|
||||
var captchaExpiredCallback = function captchaExpiredCallback() {
|
||||
grecaptcha.reset();
|
||||
};
|
||||
|
||||
$('form').submit(function(event) {
|
||||
if ($(this).has("#g-recaptcha-response").length && $("#g-recaptcha-response").val() === '') {
|
||||
event.preventDefault();
|
||||
alert("{{grav.language.translate('PLUGIN_FORM.ERROR_VALIDATING_CAPTCHA')}}");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="https://www.google.com/recaptcha/api.js?onload=captchaOnloadCallback&render=explicit&hl={{ grav.language.language }}"
|
||||
async defer>
|
||||
</script>
|
||||
<div class="g-recaptcha" id="g-recaptcha"></div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,29 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block label %}
|
||||
{% endblock %}
|
||||
|
||||
{% block input %}
|
||||
{% set id = field.id|default(field.name) ~ '-' ~ key %}
|
||||
<div class="{{ form_field_wrapper_classes ?: 'form-input-wrapper' }} {{ field.size }} {{ field.wrapper_classes }}">
|
||||
<input
|
||||
{# required attribute structures #}
|
||||
name="{{ (scope ~ field.name)|fieldName }}"
|
||||
value="{{ value|join(', ') }}"
|
||||
type="checkbox"
|
||||
{% if value == true %} checked="checked" {% endif %}
|
||||
|
||||
{# input attribute structures #}
|
||||
{% block input_attributes %}
|
||||
id="{{ id|e }}"
|
||||
{% if field.classes is defined %}class="{{ field.classes }}" {% endif %}
|
||||
{% 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 field.validate.required in ['on', 'true', 1] %}required="required"{% endif %}
|
||||
{% endblock %}
|
||||
/>
|
||||
<label style="display:inline;" class="inline" for="{{ id|e }}">{{ field.label|t|e }} {{ field.validate.required in ['on', 'true', 1] ? '<span class="required">*</span>' }}</label>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,43 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% set originalValue = value %}
|
||||
{% set value = (value is null ? field.default : value) %}
|
||||
{% if field.use == 'keys' and field.default %}
|
||||
{% set value = field.default|merge(value) %}
|
||||
{% endif %}
|
||||
|
||||
{% block global_attributes %}
|
||||
{{ parent() }}
|
||||
data-grav-keys="{{ field.use == 'keys' ? 'true' : 'false' }}"
|
||||
data-grav-field-name="{{ (scope ~ field.name)|fieldName }}"
|
||||
{% endblock %}
|
||||
|
||||
{% block input %}
|
||||
{% for key, text in field.options %}
|
||||
|
||||
{% set id = field.id|default(field.name) ~ '-' ~ 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) %}
|
||||
|
||||
<div class="checkboxes {{ form_field_wrapper_classes }} {{ field.wrapper_classes }}">
|
||||
<input type="checkbox"
|
||||
id="{{ id|e }}"
|
||||
value="{{ val|e }}"
|
||||
name="{{ (scope ~ field.name)|fieldName ~ '[' ~ name ~ ']' }}"
|
||||
{% if checked %}checked="checked"{% endif %}
|
||||
{% if field.classes is defined %}class="{{ field.classes }}" {% endif %}
|
||||
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
|
||||
|
||||
>
|
||||
<label style="display: inline" 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>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,6 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="color"
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,10 @@
|
||||
{% if field.fields %}
|
||||
<div class="form-column {{ field.classes }}">
|
||||
{% for field in field.fields %}
|
||||
{% if field.type %}
|
||||
{% set value = form.value(field.name) %}
|
||||
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="form-columns {{ field.classes }}">
|
||||
{% if field.fields %}
|
||||
{% set cols = field.fields|length %}
|
||||
{% for field in field.fields %}
|
||||
{% set value = form.value(field.name) %}
|
||||
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/column/column.html.twig'] with {'cols':cols} %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{% 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 %}
|
||||
|
||||
{% if field.fields %}
|
||||
{% for field in field.fields %}
|
||||
{% if field.type %}
|
||||
{% set value = data.value(field.name) %}
|
||||
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if field.classes %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -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,2 @@
|
||||
{# DEPRECATED. Switched to Text field until implemented properly #}
|
||||
{% extends "forms/fields/text/text.html.twig" %}
|
||||
@@ -0,0 +1,19 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input %}
|
||||
<div class="form-display-wrapper {{ field.size }}" {% if field.id is defined %}id="{{ field.id|e }}" {% endif %}>
|
||||
{% if field.markdown %}
|
||||
{% if grav.twig.twig.filters['tu'] is defined %}
|
||||
{{ field.content|tu|markdown|raw }}
|
||||
{% else %}
|
||||
{{ field.content|t|markdown|raw }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if grav.twig.twig.filters['tu'] is defined %}
|
||||
{{ field.content|tu|raw }}
|
||||
{% else %}
|
||||
{{ field.content|t|raw }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,7 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="email"
|
||||
{% if field.multiple in ['on', 'true', 1] %}multiple="multiple"{% endif %}
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,14 @@
|
||||
<fieldset {% if field.id is defined %}id="{{ field.id }}"{% endif %} {% if field.classes is defined %}class="{{ field.classes }}" {% endif %}>
|
||||
{% if field.legend %}
|
||||
<legend>{% if grav.twig.twig.filters['tu'] is defined %}{{ field.legend|tu }}{% else %}{{ field.legend|t }}{% endif %}</legend>
|
||||
{% endif %}
|
||||
|
||||
{% if field.fields %}
|
||||
{% for field in field.fields %}
|
||||
{% if field.type %}
|
||||
{% set value = form.value(field.name) %}
|
||||
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
@@ -0,0 +1,106 @@
|
||||
{% 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 %}
|
||||
|
||||
{% macro bytesToSize(bytes) -%}
|
||||
{% 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 %}
|
||||
{% endspaceless %}
|
||||
{%- 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() %}
|
||||
{% 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 = base64_encode(blueprint_name) %}
|
||||
{% set real_path = global.form.getPagePathFromToken(path) %}
|
||||
{% set remove = uri.addNonce(global.base_url_relative ~
|
||||
'/media.json' ~
|
||||
'/route' ~ config.system.param_sep ~ base64_encode(global.base_path ~ '/' ~ real_path) ~
|
||||
'/task' ~ config.system.param_sep ~ 'removeFileFromBlueprint' ~
|
||||
'/proute' ~ config.system.param_sep ~ base64_encode(route) ~
|
||||
'/blueprint' ~ config.system.param_sep ~ blueprint ~
|
||||
'/type' ~ config.system.param_sep ~ type ~
|
||||
'/field' ~ config.system.param_sep ~ files.name ~
|
||||
'/path' ~ config.system.param_sep ~ base64_encode(value.path), 'admin-form', 'admin-nonce') %}
|
||||
|
||||
{% set file = value|merge({remove: remove, path: (uri.rootUrl == '/' ? '/' : uri.rootUrl ~ '/' ~ real_path) }) %}
|
||||
<div class="hidden" data-file="{{ file|json_encode|e('html_attr') }}"></div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% 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 settings = {name: field.name, paramName: (scope ~ field.name)|fieldName ~ (files.multiple ? '[]' : ''), limit: limit, filesize: form_max_filesize, accept: files.accept} %}
|
||||
<div class="{{ form_field_wrapper_classes ?: 'form-input-wrapper' }} dropzone files-upload form-input-file {{ field.size }}" data-grav-file-settings="{{ settings|json_encode|e('html_attr') }}">
|
||||
<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 %}
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
/>
|
||||
|
||||
{% for path, file in value %}
|
||||
{{ _self.preview(path, file, _context) }}
|
||||
{% endfor %}
|
||||
{% include 'forms/fields/hidden/hidden.html.twig' with {field: {name: '_json.' ~ field.name}, value:value|json_encode|e('html_attr')} %}
|
||||
</div>
|
||||
|
||||
{% do assets.addJs('jquery', 101) %}
|
||||
{% do assets.addJs('plugin://form/assets/form.min.js', { 'group': 'bottom', 'loading': 'defer' }) %}
|
||||
{% do assets.addCss('plugin://form/assets/dropzone.min.css', { 'group': 'form' }) %}
|
||||
{{ assets.css('form')|raw }}
|
||||
{% do assets.addInlineJs("
|
||||
window.GravForm = window.GravForm || {};
|
||||
window.GravForm.config = {
|
||||
current_url: '" ~ uri.route(true) ~"',
|
||||
base_url_relative: '" ~ base_url_relative ~ "',
|
||||
param_sep: '"~ config.system.param_sep ~ "',
|
||||
form_nonce: '" ~ form.getNonce ~ "',
|
||||
};
|
||||
window.GravForm.translations = {};
|
||||
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 ~ "
|
||||
};
|
||||
", { 'group': 'bottom' }) %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1 @@
|
||||
<input type="hidden" name="__form-name__" value="{{ form.name }}" />
|
||||
@@ -0,0 +1,3 @@
|
||||
{% set value = (value is null ? (field.evaluate ? evaluate(field.default) : field.default) : value) %}
|
||||
|
||||
<input data-grav-field="hidden" data-grav-disabled="false" type="hidden" class="input" name="{{ (scope ~ field.name)|fieldName }}" value="{{ value|join(', ') }}" />
|
||||
@@ -0,0 +1 @@
|
||||
<input type="text" style="display:none;" name="{{ (scope ~ field.name)|fieldName }}" value="{{ value|join(', ') }}" />
|
||||
@@ -0,0 +1,6 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="month"
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -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,7 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="password"
|
||||
class="input"
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,23 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% set originalValue = value %}
|
||||
{% set value = (value is null ? field.default : value) %}
|
||||
|
||||
{% 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 }}"
|
||||
{% if field.classes is defined %}class="{{ field.classes }}" {% endif %}
|
||||
{% if key == value %}checked="checked" {% endif %}
|
||||
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
|
||||
{% if field.validate.required in ['on', 'true', 1] %}required="required"{% endif %}
|
||||
/>
|
||||
<label style="display: inline" class="inline" for="{{ id|e }}">{% if grav.twig.twig.filters['tu'] is defined %}{{ text|tu|raw }}{% else %}{{ text|t|raw }}{% endif %}</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,34 @@
|
||||
{% 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 }}"
|
||||
{% 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 field.validate.required in ['on', 'true', 1] %}required="required"{% endif %}
|
||||
{% if field.multiple in ['on', 'true', 1] %}multiple="multiple"{% endif %}
|
||||
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
|
||||
{% if field.form %}form="{{ field.form }}"{% endif %}
|
||||
{% if field.key %}
|
||||
data-key-observe="{{ (scope ~ field.name)|fieldName }}"
|
||||
{% endif %}
|
||||
>
|
||||
{% if field.placeholder %}<option value="" disabled selected>{% if grav.twig.twig.filters['tu'] is defined %}{{ field.placeholder|tu|raw }}{% else %}{{ field.placeholder|t|raw }}{% endif %}</option>{% endif %}
|
||||
|
||||
{% for key, text in field.options %}
|
||||
{% set selected = field.selectize ? text : key %}
|
||||
{% set item_value = field.selectize and field.multiple ? text : key %}
|
||||
<option {% if key == value or (field.multiple and selected in value) %}selected="selected"{% endif %} value="{{ item_value }}">{% if grav.twig.twig.filters['tu'] is defined %}{{ text|tu|raw }}{% else %}{{ text|t|raw }}{% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,44 @@
|
||||
{% 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 }}"
|
||||
{% 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 field.validate.required in ['on', 'true', 1] %}required="required"{% endif %}
|
||||
{% if field.multiple in ['on', 'true', 1] %}multiple="multiple"{% endif %}
|
||||
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
|
||||
{% if field.form %}form="{{ field.form }}"{% endif %}
|
||||
{% if field.key %}
|
||||
data-key-observe="{{ (scope ~ field.name)|fieldName }}"
|
||||
{% endif %}
|
||||
>
|
||||
{% if field.placeholder %}<option value="" disabled selected>{% if grav.twig.twig.filters['tu'] is defined %}{{ field.placeholder|tu|raw }}{% else %}{{ field.placeholder|t|raw }}{% endif %}</option>{% endif %}
|
||||
|
||||
{% for key, optgroup in field.options %}
|
||||
{% set optgroup_label = optgroup|keys|first %}
|
||||
|
||||
<optgroup label="{{ optgroup_label }}">
|
||||
{%for subkey, suboption in field.options[key][optgroup_label] %}
|
||||
{% set selected = field.selectize ? suboption : subkey %}
|
||||
{% set item_value = field.selectize and field.multiple ? suboption : subkey %}
|
||||
<option {% if subkey == value or (field.multiple and selected in value) %}selected="selected"{% endif %} value="{{ suboption }}">
|
||||
{% if grav.twig.twig.filters['tu'] is defined %}
|
||||
{{ suboption|tu|raw }}{% else %}{{ suboption|t|raw }}
|
||||
{% endif %}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,33 @@
|
||||
<div class="form-spacer">
|
||||
{% if field.title %}
|
||||
<h3>
|
||||
{% if grav.twig.twig.filters['tu'] is defined %}
|
||||
{{- field.title|tu|raw -}}
|
||||
{% else %}
|
||||
{{- field.title|t|raw -}}
|
||||
{% endif %}
|
||||
</h3>
|
||||
{% endif %}
|
||||
|
||||
{% if field.markdown %}
|
||||
<p>
|
||||
{% if grav.twig.twig.filters['tu'] is defined %}
|
||||
{{- field.text|tu|markdown|raw -}}
|
||||
{% else %}
|
||||
{{- field.text|t|markdown|raw -}}
|
||||
{% endif %}
|
||||
</p>
|
||||
{% else %}
|
||||
<p>
|
||||
{% if grav.twig.twig.filters['tu'] is defined %}
|
||||
{{- field.text|tu|raw -}}
|
||||
{% else %}
|
||||
{{- field.text|t|raw -}}
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if field.underline %}
|
||||
<hr />
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
{% extends 'forms/fields/checkbox/checkbox.html.twig' %}
|
||||
@@ -0,0 +1,6 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="tel"
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,37 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block prepend %}
|
||||
{% if field.prepend %}
|
||||
<div class="form-input-addon form-input-prepend">
|
||||
{% if grav.twig.twig.filters['tu'] is defined %}
|
||||
{{- field.prepend|tu|raw -}}
|
||||
{% else %}
|
||||
{{- field.prepend|t|raw -}}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="text"
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
|
||||
{% block append %}
|
||||
{% if field.append %}
|
||||
<div class="form-input-addon form-input-append">
|
||||
{% if grav.twig.twig.filters['tu'] is defined %}
|
||||
{{- field.append|tu|raw -}}
|
||||
{% else %}
|
||||
{{- field.append|t|raw -}}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block input %}
|
||||
{% if field.prepend or field.append %}
|
||||
{% set field = field|merge({'wrapper_classes': 'form-input-addon-wrapper'}) %}
|
||||
{% endif %}
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,27 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input %}
|
||||
<div class="{{ form_field_wrapper_classes ?: 'form-textarea-wrapper' }} {{ field.size }} {{ field.wrapper_classes }}">
|
||||
<textarea
|
||||
{# required attribute structures #}
|
||||
name="{{ (scope ~ field.name)|fieldName }}"
|
||||
{# input attribute structures #}
|
||||
{% block input_attributes %}
|
||||
class="{{ form_field_textarea_classes }} {{ field.classes }}"
|
||||
{% 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.validate.required in ['on', 'true', 1] %}required="required"{% endif %}
|
||||
{% if field.validate.pattern %}pattern="{{ field.validate.pattern }}"{% endif %}
|
||||
{% if field.validate.message %}title="{% if grav.twig.twig.filters['tu'] is defined %}{{ field.validate.message|tu|e }}{% else %}{{ field.validate.message|t|e }}{% endif %}"{% endif %}
|
||||
{% if field.rows is defined %}rows="{{ field.rows }}"{% endif %}
|
||||
{% if field.cols is defined %}cols="{{ field.cols }}"{% endif %}
|
||||
{% endblock %}
|
||||
>{{ value|trim|e('html') }}</textarea>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,6 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="time"
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1 @@
|
||||
<input type="hidden" name="__unique_form_id__" value="{{ random_string(20) }}" />
|
||||
@@ -0,0 +1,6 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="url"
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,6 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="week"
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1 @@
|
||||
{% extends "forms/default/form.html.twig" %}
|
||||
@@ -0,0 +1,4 @@
|
||||
<div class="modular-row form {{ page.header.class }}">
|
||||
{{ content|raw }}
|
||||
{% include "forms/form.html.twig" %}
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
{% if form.message %}
|
||||
{% set status_mapping = {'success':'green', 'error': 'red', 'warning': 'yellow'} %}
|
||||
{% set message = form.inline_errors and form.messages ? "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,2 @@
|
||||
{% set message = form.inline_errors and form.messages ? "FORM.VALIDATION_FAIL"|t : form.message %}
|
||||
{{ {'status':form.status, 'message':message}|json_encode|raw }}
|
||||
@@ -0,0 +1,23 @@
|
||||
var webpack = require('webpack'),
|
||||
path = require('path'),
|
||||
exec = require('child_process').execSync,
|
||||
pwd = exec('pwd').toString();
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
app: './app/main.js'
|
||||
},
|
||||
externals: {
|
||||
jquery: 'jQuery',
|
||||
'grav-form': 'GravForm'
|
||||
},
|
||||
module: {
|
||||
preLoaders: [
|
||||
{ test: /\.json$/, loader: 'json' },
|
||||
{ test: /\.js$/, loader: 'eslint', exclude: /node_modules/ }
|
||||
],
|
||||
loaders: [
|
||||
{ test: /\.js$/, loader: 'babel', exclude: /node_modules/, query: { presets: ['es2015'] } }
|
||||
]
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user