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,680 @@
|
||||
# v2.15.1
|
||||
## 06/20/2018
|
||||
|
||||
1. [](#improved)
|
||||
* Including EXIF JS library in the modules dependencies to fix orientation when uploading images
|
||||
1. [](#bugfix)
|
||||
* Fix HTML data template for checkboxes fields where 'use' property is "keys" [#258](https://github.com/getgrav/grav-plugin-form/pull/258)
|
||||
|
||||
# v2.15.0
|
||||
## 05/31/2018
|
||||
|
||||
1. [](#new)
|
||||
* Added support for `Uri::post()`
|
||||
* Added support for `autocapitalize`, `inputmode`, and `spellcheck` options in field definitions
|
||||
|
||||
# v2.14.1
|
||||
## 05/15/2018
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed regression with select field, causing issues with filepicker field [grav-plugin-admin#1441](https://github.com/getgrav/grav-plugin-admin/issues/1441)
|
||||
|
||||
# v2.14.0
|
||||
## 05/11/2018
|
||||
|
||||
1. [](#new)
|
||||
* Make `pagemedia` field available outside of pages context
|
||||
* Added option on fields to disable displaying of label (`display_label: false`)
|
||||
* Moved Dropzone HTML into an overridable Twig template
|
||||
* Added support for image upload delete in Dropzone `file` field
|
||||
1. [](#improved)
|
||||
* Added support for `optgroup` within select field
|
||||
* Save forms only once (stops extra work being done)
|
||||
* Allow file field to pass dropzone options
|
||||
* Added datasets support to fields
|
||||
* Added `field.classes` support to display field
|
||||
1. [](#bugfix)
|
||||
* Removed overridden class in `password` field
|
||||
* Worked around forms being lost if form cache expired before page cache, see [#240](https://github.com/getgrav/grav-plugin-form/pull/240)
|
||||
* Fixed default form in dynamically created page if header uses `forms` instead of old `form` field
|
||||
* Escape placeholder text in default field
|
||||
|
||||
# v2.13.3
|
||||
## 04/13/2018
|
||||
|
||||
1. [](#new)
|
||||
* Added support to save form data in raw format (yaml or json)
|
||||
* Added new `timestamp` action to add a timestamp field
|
||||
|
||||
# v2.13.2
|
||||
## 04/12/2018
|
||||
|
||||
1. [](#new)
|
||||
* Added event `onFormPrepareValidation` to allow some pre-processing before form validation
|
||||
* Added new `postfix` and `dateraw` options to "Save" action
|
||||
1. [](#improved)
|
||||
* Added support for `nest_id` boolean flag to `fieldset` field to nest sub-fields with name of fieldset
|
||||
* Added classes attribute to `spacer` field
|
||||
1. [](#bugfix)
|
||||
* Fixed `Form::setFields()` causing validation to fail on added and removed fields
|
||||
|
||||
# v2.13.1
|
||||
## 03/21/2018
|
||||
|
||||
1. [](#improved)
|
||||
* CAPTCHA fallback to `cURL` if `Fopen` is not allowed [#224](https://github.com/getgrav/grav-plugin-form/pull/244)
|
||||
* Use `visibility:hidden` rather than `display:none` for honeypot field [#235](https://github.com/getgrav/grav-plugin-form/pull/235)
|
||||
* Added support for markdown in checkbox field [#233](https://github.com/getgrav/grav-plugin-form/pull/233)
|
||||
* Added option to control `inline_css: true|false` for fields such as honeypot
|
||||
* Added class and CSS for honeypot field
|
||||
|
||||
# v2.13.0
|
||||
## 03/09/2018
|
||||
|
||||
1. [](#new)
|
||||
* Forced registration of `Form` page template for admin
|
||||
* Implemented support for `resolution` setting for images in file field
|
||||
* Implemented support for `resizeWidth`, `resizeHeight`, `resizeQuality` and updated Dropzone to latest version
|
||||
* Added a new `signature` field
|
||||
1. [](#improved)
|
||||
* Force an `onPageProcessed()` event if page cache expires before form cache [#240](https://github.com/getgrav/grav-plugin-form/pull/240)
|
||||
1. [](#bugfix)
|
||||
* Fixed an issue where unlimited size `0` was not being set properly in File field
|
||||
* `field.description` now translated and displays properly
|
||||
|
||||
# v2.12.0
|
||||
## 02/22/2018
|
||||
|
||||
1. [](#new)
|
||||
* Added toggle to enable/disable client-side HTML5 validation
|
||||
* Added toggle to enable/disable inline-error messages
|
||||
1. [](#improved)
|
||||
* Reformatted `form.php` plugin class for better readability
|
||||
1. [](#bugfix)
|
||||
* Fixed an issue with in-content Twig forms not working because forms were not initialized yet
|
||||
|
||||
|
||||
# v2.11.5
|
||||
## 02/16/2018
|
||||
|
||||
1. [](#new)
|
||||
* Added support for `form: process: - call: ['Class', 'method']` for custom form handling
|
||||
1. [](#bugfix)
|
||||
* Fixed regression in v2.11.4: Call to a member function post() on null [grav#1720](https://github.com/getgrav/grav/issues/1720)
|
||||
|
||||
# v2.11.4
|
||||
## 02/15/2018
|
||||
|
||||
1. [](#improved)
|
||||
* Stopped Chrome from auto-completing admin user profile form [grav#1847](https://github.com/getgrav/grav/issues/1847)
|
||||
* Start using composer to autoload classes
|
||||
* Added support for `switch` to be treated as checkbox
|
||||
1. [](#bugfix)
|
||||
* Fixed missing form submit in dynamically created pages
|
||||
|
||||
# v2.11.3
|
||||
## 01/31/2018
|
||||
|
||||
1. [](#new)
|
||||
* Added support for `file` in **Display** field. Allows the ability to read a file and output it, works in combination with `|markdown` filter
|
||||
* Added `minlength` and `maxlength` to **Textarea** field [#231](https://github.com/getgrav/grav-plugin-form/pull/231)
|
||||
|
||||
# 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,298 @@
|
||||
import $ from 'jquery';
|
||||
import Dropzone from 'dropzone';
|
||||
import EXIF from 'exif-js';
|
||||
import { config, translations } from 'grav-form';
|
||||
|
||||
// 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: ''
|
||||
};
|
||||
|
||||
window.EXIF = EXIF;
|
||||
|
||||
export default class FilesField {
|
||||
constructor({ container = '.dropzone.files-upload', options = {} } = {}) {
|
||||
this.container = $(container);
|
||||
if (!this.container.length) { return; }
|
||||
|
||||
this.urls = {};
|
||||
DropzoneMediaConfig.previewTemplate = $('#dropzone-template').html();
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
getURI() {
|
||||
return this.container.data('mediaUri') || '';
|
||||
}
|
||||
|
||||
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');
|
||||
formData.append('uri', this.getURI());
|
||||
}
|
||||
|
||||
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 || `${location.href}.json`;
|
||||
let path = (url || '').match(/path:(.*)\//);
|
||||
let data = new FormData();
|
||||
|
||||
data.append('filename', file.name);
|
||||
data.append('__form-name__', this.container.closest('form').find('[name="__form-name__"]').val());
|
||||
data.append('name', this.options.dotNotation);
|
||||
data.append('form-nonce', config.form_nonce);
|
||||
data.append('uri', this.getURI());
|
||||
|
||||
if (file.sessionParams) {
|
||||
data.append('__form-file-remover__', '1');
|
||||
data.append('session', file.sessionParams);
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
data,
|
||||
method: 'POST',
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: () => {
|
||||
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,
|
||||
resizeWidth: settings.resizeWidth || null,
|
||||
resizeHeight: settings.resizeHeight || null,
|
||||
resizeQuality: settings.resizeQuality || null,
|
||||
accept: function(file, done) {
|
||||
const resolution = settings.resolution;
|
||||
if (!resolution) return done();
|
||||
|
||||
setTimeout(() => {
|
||||
let error = '';
|
||||
if (resolution.min) {
|
||||
Object.keys(resolution.min).forEach((attr) => {
|
||||
if (file[attr] < resolution.min[attr]) {
|
||||
error += translations.PLUGIN_FORM.RESOLUTION_MIN.replace(/{{attr}}/g, attr).replace(/{{min}}/g, resolution.min[attr]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!(settings.resizeWidth || settings.resizeHeight)) {
|
||||
if (resolution.max) {
|
||||
Object.keys(resolution.max).forEach((attr) => {
|
||||
if (file[attr] > resolution.max[attr]) {
|
||||
error += translations.PLUGIN_FORM.RESOLUTION_MAX.replace(/{{attr}}/g, attr).replace(/{{max}}/g, resolution.max[attr]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return done(error);
|
||||
}, 50);
|
||||
}
|
||||
};
|
||||
|
||||
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,124 @@
|
||||
.form-group.has-errors {
|
||||
background: rgba(255,0,0,0.05);
|
||||
border: 1px solid rgba(255,0,0,0.2);
|
||||
border-radius: 3px;
|
||||
margin: 0 -5px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.form-errors {
|
||||
color: #b52b27;
|
||||
}
|
||||
|
||||
.form-honeybear {
|
||||
visibility: hidden;
|
||||
position: absolute !important;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
}
|
||||
|
||||
.form-errors p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.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 {
|
||||
position: relative;
|
||||
min-height: 70px;
|
||||
border-radius: 3px;
|
||||
margin-bottom: .85rem;
|
||||
border: 2px dashed #ccc;
|
||||
color: #aaa;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
/* Signature Pad */
|
||||
.signature-pad {
|
||||
position: relative;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
font-size: 10px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 700px;
|
||||
max-height: 460px;
|
||||
border: 1px solid #f0f0f0;
|
||||
background-color: #fff;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.signature-pad--body {
|
||||
position: relative;
|
||||
-webkit-box-flex: 1;
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
border: 1px solid #f6f6f6;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.signature-pad--body canvas {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.02) inset;
|
||||
}
|
||||
|
||||
.signature-pad--footer {
|
||||
color: #C3C3C3;
|
||||
text-align: center;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.signature-pad--actions {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: justify;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
margin-top: 8px;
|
||||
}
|
||||
Vendored
+4
File diff suppressed because one or more lines are too long
Executable
+617
@@ -0,0 +1,617 @@
|
||||
/*!
|
||||
* Signature Pad v2.3.2
|
||||
* https://github.com/szimek/signature_pad
|
||||
*
|
||||
* Copyright 2017 Szymon Nowak
|
||||
* Released under the MIT license
|
||||
*
|
||||
* The main idea and some parts of the code (e.g. drawing variable width Bézier curve) are taken from:
|
||||
* http://corner.squareup.com/2012/07/smoother-signatures.html
|
||||
*
|
||||
* Implementation of interpolation using cubic Bézier curves is taken from:
|
||||
* http://benknowscode.wordpress.com/2012/09/14/path-interpolation-using-cubic-bezier-and-control-point-estimation-in-javascript
|
||||
*
|
||||
* Algorithm for approximated length of a Bézier curve is taken from:
|
||||
* http://www.lemoda.net/maths/bezier-length/index.html
|
||||
*
|
||||
*/
|
||||
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||
typeof define === 'function' && define.amd ? define(factory) :
|
||||
(global.SignaturePad = factory());
|
||||
}(this, (function () { 'use strict';
|
||||
|
||||
function Point(x, y, time) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.time = time || new Date().getTime();
|
||||
}
|
||||
|
||||
Point.prototype.velocityFrom = function (start) {
|
||||
return this.time !== start.time ? this.distanceTo(start) / (this.time - start.time) : 1;
|
||||
};
|
||||
|
||||
Point.prototype.distanceTo = function (start) {
|
||||
return Math.sqrt(Math.pow(this.x - start.x, 2) + Math.pow(this.y - start.y, 2));
|
||||
};
|
||||
|
||||
Point.prototype.equals = function (other) {
|
||||
return this.x === other.x && this.y === other.y && this.time === other.time;
|
||||
};
|
||||
|
||||
function Bezier(startPoint, control1, control2, endPoint) {
|
||||
this.startPoint = startPoint;
|
||||
this.control1 = control1;
|
||||
this.control2 = control2;
|
||||
this.endPoint = endPoint;
|
||||
}
|
||||
|
||||
// Returns approximated length.
|
||||
Bezier.prototype.length = function () {
|
||||
var steps = 10;
|
||||
var length = 0;
|
||||
var px = void 0;
|
||||
var py = void 0;
|
||||
|
||||
for (var i = 0; i <= steps; i += 1) {
|
||||
var t = i / steps;
|
||||
var cx = this._point(t, this.startPoint.x, this.control1.x, this.control2.x, this.endPoint.x);
|
||||
var cy = this._point(t, this.startPoint.y, this.control1.y, this.control2.y, this.endPoint.y);
|
||||
if (i > 0) {
|
||||
var xdiff = cx - px;
|
||||
var ydiff = cy - py;
|
||||
length += Math.sqrt(xdiff * xdiff + ydiff * ydiff);
|
||||
}
|
||||
px = cx;
|
||||
py = cy;
|
||||
}
|
||||
|
||||
return length;
|
||||
};
|
||||
|
||||
/* eslint-disable no-multi-spaces, space-in-parens */
|
||||
Bezier.prototype._point = function (t, start, c1, c2, end) {
|
||||
return start * (1.0 - t) * (1.0 - t) * (1.0 - t) + 3.0 * c1 * (1.0 - t) * (1.0 - t) * t + 3.0 * c2 * (1.0 - t) * t * t + end * t * t * t;
|
||||
};
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
// http://stackoverflow.com/a/27078401/815507
|
||||
function throttle(func, wait, options) {
|
||||
var context, args, result;
|
||||
var timeout = null;
|
||||
var previous = 0;
|
||||
if (!options) options = {};
|
||||
var later = function later() {
|
||||
previous = options.leading === false ? 0 : Date.now();
|
||||
timeout = null;
|
||||
result = func.apply(context, args);
|
||||
if (!timeout) context = args = null;
|
||||
};
|
||||
return function () {
|
||||
var now = Date.now();
|
||||
if (!previous && options.leading === false) previous = now;
|
||||
var remaining = wait - (now - previous);
|
||||
context = this;
|
||||
args = arguments;
|
||||
if (remaining <= 0 || remaining > wait) {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
previous = now;
|
||||
result = func.apply(context, args);
|
||||
if (!timeout) context = args = null;
|
||||
} else if (!timeout && options.trailing !== false) {
|
||||
timeout = setTimeout(later, remaining);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
function SignaturePad(canvas, options) {
|
||||
var self = this;
|
||||
var opts = options || {};
|
||||
|
||||
this.velocityFilterWeight = opts.velocityFilterWeight || 0.7;
|
||||
this.minWidth = opts.minWidth || 0.5;
|
||||
this.maxWidth = opts.maxWidth || 2.5;
|
||||
this.throttle = 'throttle' in opts ? opts.throttle : 16; // in miliseconds
|
||||
this.minDistance = 'minDistance' in opts ? opts.minDistance : 5;
|
||||
|
||||
if (this.throttle) {
|
||||
this._strokeMoveUpdate = throttle(SignaturePad.prototype._strokeUpdate, this.throttle);
|
||||
} else {
|
||||
this._strokeMoveUpdate = SignaturePad.prototype._strokeUpdate;
|
||||
}
|
||||
|
||||
this.dotSize = opts.dotSize || function () {
|
||||
return (this.minWidth + this.maxWidth) / 2;
|
||||
};
|
||||
this.penColor = opts.penColor || 'black';
|
||||
this.backgroundColor = opts.backgroundColor || 'rgba(0,0,0,0)';
|
||||
this.onBegin = opts.onBegin;
|
||||
this.onEnd = opts.onEnd;
|
||||
|
||||
this._canvas = canvas;
|
||||
this._ctx = canvas.getContext('2d');
|
||||
this.clear();
|
||||
|
||||
// We need add these inline so they are available to unbind while still having
|
||||
// access to 'self' we could use _.bind but it's not worth adding a dependency.
|
||||
this._handleMouseDown = function (event) {
|
||||
if (event.which === 1) {
|
||||
self._mouseButtonDown = true;
|
||||
self._strokeBegin(event);
|
||||
}
|
||||
};
|
||||
|
||||
this._handleMouseMove = function (event) {
|
||||
if (self._mouseButtonDown) {
|
||||
self._strokeMoveUpdate(event);
|
||||
}
|
||||
};
|
||||
|
||||
this._handleMouseUp = function (event) {
|
||||
if (event.which === 1 && self._mouseButtonDown) {
|
||||
self._mouseButtonDown = false;
|
||||
self._strokeEnd(event);
|
||||
}
|
||||
};
|
||||
|
||||
this._handleTouchStart = function (event) {
|
||||
// Prevent scrolling.
|
||||
event.preventDefault();
|
||||
|
||||
if (event.targetTouches.length === 1) {
|
||||
var touch = event.changedTouches[0];
|
||||
self._strokeBegin(touch);
|
||||
}
|
||||
};
|
||||
|
||||
this._handleTouchMove = function (event) {
|
||||
// Prevent scrolling.
|
||||
event.preventDefault();
|
||||
|
||||
var touch = event.targetTouches[0];
|
||||
self._strokeMoveUpdate(touch);
|
||||
};
|
||||
|
||||
this._handleTouchEnd = function (event) {
|
||||
var wasCanvasTouched = event.target === self._canvas;
|
||||
if (wasCanvasTouched) {
|
||||
event.preventDefault();
|
||||
self._strokeEnd(event);
|
||||
}
|
||||
};
|
||||
|
||||
// Enable mouse and touch event handlers
|
||||
this.on();
|
||||
}
|
||||
|
||||
// Public methods
|
||||
SignaturePad.prototype.clear = function () {
|
||||
var ctx = this._ctx;
|
||||
var canvas = this._canvas;
|
||||
|
||||
ctx.fillStyle = this.backgroundColor;
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
this._data = [];
|
||||
this._reset();
|
||||
this._isEmpty = true;
|
||||
};
|
||||
|
||||
SignaturePad.prototype.fromDataURL = function (dataUrl) {
|
||||
var _this = this;
|
||||
|
||||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||
|
||||
var image = new Image();
|
||||
var ratio = options.ratio || window.devicePixelRatio || 1;
|
||||
var width = options.width || this._canvas.width / ratio;
|
||||
var height = options.height || this._canvas.height / ratio;
|
||||
|
||||
this._reset();
|
||||
image.src = dataUrl;
|
||||
image.onload = function () {
|
||||
_this._ctx.drawImage(image, 0, 0, width, height);
|
||||
};
|
||||
this._isEmpty = false;
|
||||
};
|
||||
|
||||
SignaturePad.prototype.toDataURL = function (type) {
|
||||
var _canvas;
|
||||
|
||||
switch (type) {
|
||||
case 'image/svg+xml':
|
||||
return this._toSVG();
|
||||
default:
|
||||
for (var _len = arguments.length, options = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
||||
options[_key - 1] = arguments[_key];
|
||||
}
|
||||
|
||||
return (_canvas = this._canvas).toDataURL.apply(_canvas, [type].concat(options));
|
||||
}
|
||||
};
|
||||
|
||||
SignaturePad.prototype.on = function () {
|
||||
this._handleMouseEvents();
|
||||
this._handleTouchEvents();
|
||||
};
|
||||
|
||||
SignaturePad.prototype.off = function () {
|
||||
// Pass touch events to canvas element on mobile IE11 and Edge.
|
||||
this._canvas.style.msTouchAction = 'auto';
|
||||
this._canvas.style.touchAction = 'auto';
|
||||
|
||||
this._canvas.removeEventListener('mousedown', this._handleMouseDown);
|
||||
this._canvas.removeEventListener('mousemove', this._handleMouseMove);
|
||||
document.removeEventListener('mouseup', this._handleMouseUp);
|
||||
|
||||
this._canvas.removeEventListener('touchstart', this._handleTouchStart);
|
||||
this._canvas.removeEventListener('touchmove', this._handleTouchMove);
|
||||
this._canvas.removeEventListener('touchend', this._handleTouchEnd);
|
||||
};
|
||||
|
||||
SignaturePad.prototype.isEmpty = function () {
|
||||
return this._isEmpty;
|
||||
};
|
||||
|
||||
// Private methods
|
||||
SignaturePad.prototype._strokeBegin = function (event) {
|
||||
this._data.push([]);
|
||||
this._reset();
|
||||
this._strokeUpdate(event);
|
||||
|
||||
if (typeof this.onBegin === 'function') {
|
||||
this.onBegin(event);
|
||||
}
|
||||
};
|
||||
|
||||
SignaturePad.prototype._strokeUpdate = function (event) {
|
||||
var x = event.clientX;
|
||||
var y = event.clientY;
|
||||
|
||||
var point = this._createPoint(x, y);
|
||||
var lastPointGroup = this._data[this._data.length - 1];
|
||||
var lastPoint = lastPointGroup && lastPointGroup[lastPointGroup.length - 1];
|
||||
var isLastPointTooClose = lastPoint && point.distanceTo(lastPoint) < this.minDistance;
|
||||
|
||||
// Skip this point if it's too close to the previous one
|
||||
if (!(lastPoint && isLastPointTooClose)) {
|
||||
var _addPoint = this._addPoint(point),
|
||||
curve = _addPoint.curve,
|
||||
widths = _addPoint.widths;
|
||||
|
||||
if (curve && widths) {
|
||||
this._drawCurve(curve, widths.start, widths.end);
|
||||
}
|
||||
|
||||
this._data[this._data.length - 1].push({
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
time: point.time,
|
||||
color: this.penColor
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
SignaturePad.prototype._strokeEnd = function (event) {
|
||||
var canDrawCurve = this.points.length > 2;
|
||||
var point = this.points[0]; // Point instance
|
||||
|
||||
if (!canDrawCurve && point) {
|
||||
this._drawDot(point);
|
||||
}
|
||||
|
||||
if (point) {
|
||||
var lastPointGroup = this._data[this._data.length - 1];
|
||||
var lastPoint = lastPointGroup[lastPointGroup.length - 1]; // plain object
|
||||
|
||||
// When drawing a dot, there's only one point in a group, so without this check
|
||||
// such group would end up with exactly the same 2 points.
|
||||
if (!point.equals(lastPoint)) {
|
||||
lastPointGroup.push({
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
time: point.time,
|
||||
color: this.penColor
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof this.onEnd === 'function') {
|
||||
this.onEnd(event);
|
||||
}
|
||||
};
|
||||
|
||||
SignaturePad.prototype._handleMouseEvents = function () {
|
||||
this._mouseButtonDown = false;
|
||||
|
||||
this._canvas.addEventListener('mousedown', this._handleMouseDown);
|
||||
this._canvas.addEventListener('mousemove', this._handleMouseMove);
|
||||
document.addEventListener('mouseup', this._handleMouseUp);
|
||||
};
|
||||
|
||||
SignaturePad.prototype._handleTouchEvents = function () {
|
||||
// Pass touch events to canvas element on mobile IE11 and Edge.
|
||||
this._canvas.style.msTouchAction = 'none';
|
||||
this._canvas.style.touchAction = 'none';
|
||||
|
||||
this._canvas.addEventListener('touchstart', this._handleTouchStart);
|
||||
this._canvas.addEventListener('touchmove', this._handleTouchMove);
|
||||
this._canvas.addEventListener('touchend', this._handleTouchEnd);
|
||||
};
|
||||
|
||||
SignaturePad.prototype._reset = function () {
|
||||
this.points = [];
|
||||
this._lastVelocity = 0;
|
||||
this._lastWidth = (this.minWidth + this.maxWidth) / 2;
|
||||
this._ctx.fillStyle = this.penColor;
|
||||
};
|
||||
|
||||
SignaturePad.prototype._createPoint = function (x, y, time) {
|
||||
var rect = this._canvas.getBoundingClientRect();
|
||||
|
||||
return new Point(x - rect.left, y - rect.top, time || new Date().getTime());
|
||||
};
|
||||
|
||||
SignaturePad.prototype._addPoint = function (point) {
|
||||
var points = this.points;
|
||||
var tmp = void 0;
|
||||
|
||||
points.push(point);
|
||||
|
||||
if (points.length > 2) {
|
||||
// To reduce the initial lag make it work with 3 points
|
||||
// by copying the first point to the beginning.
|
||||
if (points.length === 3) points.unshift(points[0]);
|
||||
|
||||
tmp = this._calculateCurveControlPoints(points[0], points[1], points[2]);
|
||||
var c2 = tmp.c2;
|
||||
tmp = this._calculateCurveControlPoints(points[1], points[2], points[3]);
|
||||
var c3 = tmp.c1;
|
||||
var curve = new Bezier(points[1], c2, c3, points[2]);
|
||||
var widths = this._calculateCurveWidths(curve);
|
||||
|
||||
// Remove the first element from the list,
|
||||
// so that we always have no more than 4 points in points array.
|
||||
points.shift();
|
||||
|
||||
return { curve: curve, widths: widths };
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
SignaturePad.prototype._calculateCurveControlPoints = function (s1, s2, s3) {
|
||||
var dx1 = s1.x - s2.x;
|
||||
var dy1 = s1.y - s2.y;
|
||||
var dx2 = s2.x - s3.x;
|
||||
var dy2 = s2.y - s3.y;
|
||||
|
||||
var m1 = { x: (s1.x + s2.x) / 2.0, y: (s1.y + s2.y) / 2.0 };
|
||||
var m2 = { x: (s2.x + s3.x) / 2.0, y: (s2.y + s3.y) / 2.0 };
|
||||
|
||||
var l1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
|
||||
var l2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
|
||||
|
||||
var dxm = m1.x - m2.x;
|
||||
var dym = m1.y - m2.y;
|
||||
|
||||
var k = l2 / (l1 + l2);
|
||||
var cm = { x: m2.x + dxm * k, y: m2.y + dym * k };
|
||||
|
||||
var tx = s2.x - cm.x;
|
||||
var ty = s2.y - cm.y;
|
||||
|
||||
return {
|
||||
c1: new Point(m1.x + tx, m1.y + ty),
|
||||
c2: new Point(m2.x + tx, m2.y + ty)
|
||||
};
|
||||
};
|
||||
|
||||
SignaturePad.prototype._calculateCurveWidths = function (curve) {
|
||||
var startPoint = curve.startPoint;
|
||||
var endPoint = curve.endPoint;
|
||||
var widths = { start: null, end: null };
|
||||
|
||||
var velocity = this.velocityFilterWeight * endPoint.velocityFrom(startPoint) + (1 - this.velocityFilterWeight) * this._lastVelocity;
|
||||
|
||||
var newWidth = this._strokeWidth(velocity);
|
||||
|
||||
widths.start = this._lastWidth;
|
||||
widths.end = newWidth;
|
||||
|
||||
this._lastVelocity = velocity;
|
||||
this._lastWidth = newWidth;
|
||||
|
||||
return widths;
|
||||
};
|
||||
|
||||
SignaturePad.prototype._strokeWidth = function (velocity) {
|
||||
return Math.max(this.maxWidth / (velocity + 1), this.minWidth);
|
||||
};
|
||||
|
||||
SignaturePad.prototype._drawPoint = function (x, y, size) {
|
||||
var ctx = this._ctx;
|
||||
|
||||
ctx.moveTo(x, y);
|
||||
ctx.arc(x, y, size, 0, 2 * Math.PI, false);
|
||||
this._isEmpty = false;
|
||||
};
|
||||
|
||||
SignaturePad.prototype._drawCurve = function (curve, startWidth, endWidth) {
|
||||
var ctx = this._ctx;
|
||||
var widthDelta = endWidth - startWidth;
|
||||
var drawSteps = Math.floor(curve.length());
|
||||
|
||||
ctx.beginPath();
|
||||
|
||||
for (var i = 0; i < drawSteps; i += 1) {
|
||||
// Calculate the Bezier (x, y) coordinate for this step.
|
||||
var t = i / drawSteps;
|
||||
var tt = t * t;
|
||||
var ttt = tt * t;
|
||||
var u = 1 - t;
|
||||
var uu = u * u;
|
||||
var uuu = uu * u;
|
||||
|
||||
var x = uuu * curve.startPoint.x;
|
||||
x += 3 * uu * t * curve.control1.x;
|
||||
x += 3 * u * tt * curve.control2.x;
|
||||
x += ttt * curve.endPoint.x;
|
||||
|
||||
var y = uuu * curve.startPoint.y;
|
||||
y += 3 * uu * t * curve.control1.y;
|
||||
y += 3 * u * tt * curve.control2.y;
|
||||
y += ttt * curve.endPoint.y;
|
||||
|
||||
var width = startWidth + ttt * widthDelta;
|
||||
this._drawPoint(x, y, width);
|
||||
}
|
||||
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
};
|
||||
|
||||
SignaturePad.prototype._drawDot = function (point) {
|
||||
var ctx = this._ctx;
|
||||
var width = typeof this.dotSize === 'function' ? this.dotSize() : this.dotSize;
|
||||
|
||||
ctx.beginPath();
|
||||
this._drawPoint(point.x, point.y, width);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
};
|
||||
|
||||
SignaturePad.prototype._fromData = function (pointGroups, drawCurve, drawDot) {
|
||||
for (var i = 0; i < pointGroups.length; i += 1) {
|
||||
var group = pointGroups[i];
|
||||
|
||||
if (group.length > 1) {
|
||||
for (var j = 0; j < group.length; j += 1) {
|
||||
var rawPoint = group[j];
|
||||
var point = new Point(rawPoint.x, rawPoint.y, rawPoint.time);
|
||||
var color = rawPoint.color;
|
||||
|
||||
if (j === 0) {
|
||||
// First point in a group. Nothing to draw yet.
|
||||
|
||||
// All points in the group have the same color, so it's enough to set
|
||||
// penColor just at the beginning.
|
||||
this.penColor = color;
|
||||
this._reset();
|
||||
|
||||
this._addPoint(point);
|
||||
} else if (j !== group.length - 1) {
|
||||
// Middle point in a group.
|
||||
var _addPoint2 = this._addPoint(point),
|
||||
curve = _addPoint2.curve,
|
||||
widths = _addPoint2.widths;
|
||||
|
||||
if (curve && widths) {
|
||||
drawCurve(curve, widths, color);
|
||||
}
|
||||
} else {
|
||||
// Last point in a group. Do nothing.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._reset();
|
||||
var _rawPoint = group[0];
|
||||
drawDot(_rawPoint);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SignaturePad.prototype._toSVG = function () {
|
||||
var _this2 = this;
|
||||
|
||||
var pointGroups = this._data;
|
||||
var canvas = this._canvas;
|
||||
var ratio = Math.max(window.devicePixelRatio || 1, 1);
|
||||
var minX = 0;
|
||||
var minY = 0;
|
||||
var maxX = canvas.width / ratio;
|
||||
var maxY = canvas.height / ratio;
|
||||
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
|
||||
svg.setAttributeNS(null, 'width', canvas.width);
|
||||
svg.setAttributeNS(null, 'height', canvas.height);
|
||||
|
||||
this._fromData(pointGroups, function (curve, widths, color) {
|
||||
var path = document.createElement('path');
|
||||
|
||||
// Need to check curve for NaN values, these pop up when drawing
|
||||
// lines on the canvas that are not continuous. E.g. Sharp corners
|
||||
// or stopping mid-stroke and than continuing without lifting mouse.
|
||||
if (!isNaN(curve.control1.x) && !isNaN(curve.control1.y) && !isNaN(curve.control2.x) && !isNaN(curve.control2.y)) {
|
||||
var attr = 'M ' + curve.startPoint.x.toFixed(3) + ',' + curve.startPoint.y.toFixed(3) + ' ' + ('C ' + curve.control1.x.toFixed(3) + ',' + curve.control1.y.toFixed(3) + ' ') + (curve.control2.x.toFixed(3) + ',' + curve.control2.y.toFixed(3) + ' ') + (curve.endPoint.x.toFixed(3) + ',' + curve.endPoint.y.toFixed(3));
|
||||
|
||||
path.setAttribute('d', attr);
|
||||
path.setAttribute('stroke-width', (widths.end * 2.25).toFixed(3));
|
||||
path.setAttribute('stroke', color);
|
||||
path.setAttribute('fill', 'none');
|
||||
path.setAttribute('stroke-linecap', 'round');
|
||||
|
||||
svg.appendChild(path);
|
||||
}
|
||||
}, function (rawPoint) {
|
||||
var circle = document.createElement('circle');
|
||||
var dotSize = typeof _this2.dotSize === 'function' ? _this2.dotSize() : _this2.dotSize;
|
||||
circle.setAttribute('r', dotSize);
|
||||
circle.setAttribute('cx', rawPoint.x);
|
||||
circle.setAttribute('cy', rawPoint.y);
|
||||
circle.setAttribute('fill', rawPoint.color);
|
||||
|
||||
svg.appendChild(circle);
|
||||
});
|
||||
|
||||
var prefix = 'data:image/svg+xml;base64,';
|
||||
var header = '<svg' + ' xmlns="http://www.w3.org/2000/svg"' + ' xmlns:xlink="http://www.w3.org/1999/xlink"' + (' viewBox="' + minX + ' ' + minY + ' ' + maxX + ' ' + maxY + '"') + (' width="' + maxX + '"') + (' height="' + maxY + '"') + '>';
|
||||
var body = svg.innerHTML;
|
||||
|
||||
// IE hack for missing innerHTML property on SVGElement
|
||||
if (body === undefined) {
|
||||
var dummy = document.createElement('dummy');
|
||||
var nodes = svg.childNodes;
|
||||
dummy.innerHTML = '';
|
||||
|
||||
for (var i = 0; i < nodes.length; i += 1) {
|
||||
dummy.appendChild(nodes[i].cloneNode(true));
|
||||
}
|
||||
|
||||
body = dummy.innerHTML;
|
||||
}
|
||||
|
||||
var footer = '</svg>';
|
||||
var data = header + body + footer;
|
||||
|
||||
return prefix + btoa(data);
|
||||
};
|
||||
|
||||
SignaturePad.prototype.fromData = function (pointGroups) {
|
||||
var _this3 = this;
|
||||
|
||||
this.clear();
|
||||
|
||||
this._fromData(pointGroups, function (curve, widths) {
|
||||
return _this3._drawCurve(curve, widths.start, widths.end);
|
||||
}, function (rawPoint) {
|
||||
return _this3._drawDot(rawPoint);
|
||||
});
|
||||
|
||||
this._data = pointGroups;
|
||||
};
|
||||
|
||||
SignaturePad.prototype.toData = function () {
|
||||
return this._data;
|
||||
};
|
||||
|
||||
return SignaturePad;
|
||||
|
||||
})));
|
||||
@@ -0,0 +1,180 @@
|
||||
name: Form
|
||||
version: 2.15.1
|
||||
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.4.5' }
|
||||
|
||||
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
|
||||
|
||||
inline_css:
|
||||
type: toggle
|
||||
label: PLUGIN_FORM.USE_INLINE_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
|
||||
|
||||
client_side_validation:
|
||||
type: toggle
|
||||
label: PLUGIN_FORM.CLIENT_SIDE_VALIDATION
|
||||
help: PLUGIN_FORM.CLIENT_SIDE_VALIDATION_HELP
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
inline_errors:
|
||||
type: toggle
|
||||
label: PLUGIN_FORM.INLINE_ERRORS
|
||||
help: PLUGIN_FORM.INLINE_ERRORS_HELP
|
||||
highlight: 0
|
||||
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,812 @@
|
||||
<?php
|
||||
namespace Grav\Plugin\Form;
|
||||
|
||||
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) {
|
||||
// If form is given, use it.
|
||||
$this->items = $form;
|
||||
} elseif ($name && isset($header->forms[$name])) {
|
||||
// If form with that name was found, use that.
|
||||
$this->items = $header->forms[$name];
|
||||
} elseif (isset($header->form)) {
|
||||
// For backwards compatibility.
|
||||
$this->items = $header->form;
|
||||
} elseif (!empty($header->forms)) {
|
||||
// Pick up the first form.
|
||||
$form = reset($header->forms);
|
||||
$name = key($header->forms);
|
||||
$this->items = $form;
|
||||
}
|
||||
|
||||
// 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 array $fields
|
||||
*/
|
||||
public function setFields(array $fields = [])
|
||||
{
|
||||
// Make sure blueprints are updated, otherwise validation may fail.
|
||||
$blueprint = $this->data->blueprints();
|
||||
$blueprint->set('form/fields', $fields);
|
||||
$blueprint->undef('form/field');
|
||||
|
||||
$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();
|
||||
$this->fields = $this->fields(true);
|
||||
|
||||
// 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($reset = false)
|
||||
{
|
||||
|
||||
if ($reset || 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Data
|
||||
*/
|
||||
public function getValues()
|
||||
{
|
||||
return $this->values;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
$json_response = [
|
||||
'status' => 'success',
|
||||
'session' => \json_encode([
|
||||
'sessionField' => base64_encode($uri),
|
||||
'path' => $upload->file->path,
|
||||
'field' => $settings->name
|
||||
])
|
||||
];
|
||||
|
||||
// Return JSON
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($json_response);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a file from the flash object session, before it gets saved
|
||||
*
|
||||
* @return bool True if the action was performed.
|
||||
*/
|
||||
public function filesSessionRemove()
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
$post = $_POST;
|
||||
$session = $grav['session'];
|
||||
// Retrieve the current session of the uploaded files for the field
|
||||
// and initialize it if it doesn't exist
|
||||
$sessionField = base64_encode($grav['uri']->url(true));
|
||||
$request = \json_decode($post['session']);
|
||||
|
||||
// Ensure the URI requested matches the current one, otherwise fail
|
||||
if ($request->sessionField !== $sessionField) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Retrieve the flash object and remove the requested file from it
|
||||
$flash = $session->getFlashObject('files-upload');
|
||||
$endpoint = $flash[$request->sessionField][$request->field][$request->path];
|
||||
|
||||
if (isset($endpoint)) {
|
||||
if (file_exists($endpoint['tmp_name'])) {
|
||||
unlink($endpoint['tmp_name']);
|
||||
}
|
||||
|
||||
unset($endpoint);
|
||||
}
|
||||
|
||||
// Walk backward to cleanup any empty field that's left
|
||||
// Field
|
||||
if (isset($flash[$request->sessionField][$request->field][$request->path])) {
|
||||
unset($flash[$request->sessionField][$request->field][$request->path]);
|
||||
}
|
||||
|
||||
// Field
|
||||
if (isset($flash[$request->sessionField][$request->field]) && empty($flash[$request->sessionField][$request->field])) {
|
||||
unset($flash[$request->sessionField][$request->field]);
|
||||
}
|
||||
|
||||
// Session Field
|
||||
if (isset($flash[$request->sessionField]) && empty($flash[$request->sessionField])) {
|
||||
unset($flash[$request->sessionField]);
|
||||
}
|
||||
|
||||
|
||||
// If there's anything left to restore in the flash object, do so
|
||||
if (count($flash)) {
|
||||
$session->setFlashObject('files-upload', $flash);
|
||||
}
|
||||
|
||||
// json_response
|
||||
$json_response = ['status' => 'success'];
|
||||
|
||||
// Return JSON
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($json_response);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle form processing on POST action.
|
||||
*/
|
||||
public function post()
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
$uri = $grav['uri'];
|
||||
$url = $uri->url;
|
||||
$session = $grav['session'];
|
||||
|
||||
$post = $uri->post();
|
||||
|
||||
if ($post) {
|
||||
$this->values = new Data((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' || $field['type'] === 'switch') {
|
||||
$data[$name] = isset($data[$name]) ? true : false;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
$this->data->merge($data);
|
||||
}
|
||||
|
||||
// Validate and filter data
|
||||
try {
|
||||
$grav->fireEvent('onFormPrepareValidation', new Event(['form' => $this]));
|
||||
|
||||
$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($url)];
|
||||
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 = (int)($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,31 @@
|
||||
{
|
||||
"name": "grav-plugin-form",
|
||||
"type": "grav-plugin",
|
||||
"description": "Grav Plugin Form",
|
||||
"keywords": ["form"],
|
||||
"homepage": "https://github.com/getgrav/grav-plugin-form",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Team Grav",
|
||||
"email": "devs@getgrav.org",
|
||||
"homepage": "http://getgrav.org",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/getgrav/grav-plugin-form/issues",
|
||||
"irc": "https://gitter.im/getgrav/grav",
|
||||
"forum": "http://getgrav.org/forum",
|
||||
"docs": "https://github.com/getgrav/grav-plugin-form/blob/master/README.md"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Grav\\Plugin\\Form\\": "classes/"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,861 @@
|
||||
<?php
|
||||
namespace Grav\Plugin;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Grav\Common\Data\ValidationException;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Page\Page;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Page\Types;
|
||||
use Grav\Common\Plugin;
|
||||
use Grav\Common\Twig\Twig;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Plugin\Form\Form;
|
||||
use RocketTheme\Toolbox\File\JsonFile;
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use RocketTheme\Toolbox\File\File;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
/**
|
||||
* Class FormPlugin
|
||||
* @package Grav\Plugin
|
||||
*/
|
||||
class FormPlugin extends Plugin
|
||||
{
|
||||
/** @var array */
|
||||
public $features = [
|
||||
'blueprints' => 1000
|
||||
];
|
||||
|
||||
/** @var Form */
|
||||
protected $form;
|
||||
|
||||
/** @var array */
|
||||
protected $forms = [];
|
||||
|
||||
/** @var array */
|
||||
protected $flat_forms = [];
|
||||
|
||||
/** @var array */
|
||||
protected $json_response = [];
|
||||
|
||||
/** @var bool */
|
||||
protected $recache_forms = false;
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
'onPluginsInitialized' => [
|
||||
['autoload', 100000],
|
||||
['onPluginsInitialized', 0]
|
||||
],
|
||||
'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* [onPluginsInitialized:100000] Composer autoload.
|
||||
*
|
||||
* @return ClassLoader
|
||||
*/
|
||||
public function autoload()
|
||||
{
|
||||
return require __DIR__ . '/vendor/autoload.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize forms from cache if possible
|
||||
*/
|
||||
public function onPluginsInitialized()
|
||||
{
|
||||
// Backwards compatibility for plugins that use forms.
|
||||
class_alias(Form::class, 'Grav\Plugin\Form');
|
||||
|
||||
if ($this->isAdmin()) {
|
||||
$this->enable([
|
||||
'onPageInitialized' => ['onPageInitialized', 0],
|
||||
'onGetPageTemplates' => ['onGetPageTemplates', 0],
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->enable([
|
||||
'onPageProcessed' => ['onPageProcessed', 0],
|
||||
'onPagesInitialized' => ['onPagesInitialized', 0],
|
||||
'onPageInitialized' => ['onPageInitialized', 0],
|
||||
'onTwigInitialized' => ['onTwigInitialized', 0],
|
||||
'onTwigPageVariables' => ['onTwigVariables', 0],
|
||||
'onTwigSiteVariables' => ['onTwigVariables', 0],
|
||||
'onFormValidationProcessed' => ['onFormValidationProcessed', 0],
|
||||
]);
|
||||
}
|
||||
|
||||
public function onGetPageTemplates(Event $event)
|
||||
{
|
||||
/** @var Types $types */
|
||||
$types = $event->types;
|
||||
$types->register('form');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(['page' => $page, '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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all the forms
|
||||
*/
|
||||
public function onPagesInitialized()
|
||||
{
|
||||
$this->loadCachedForms();
|
||||
}
|
||||
|
||||
/**
|
||||
* Catches form processing if user posts the form.
|
||||
*/
|
||||
public function onPageInitialized()
|
||||
{
|
||||
$submitted = false;
|
||||
$this->json_response = [];
|
||||
|
||||
// Save cached forms.
|
||||
if ($this->recache_forms) {
|
||||
$this->saveCachedForms();
|
||||
}
|
||||
|
||||
// Force rebuild form when form has not been built and form cache expired.
|
||||
// This happens when form cache expires before the page cache
|
||||
// and then does not trigger 'onPageProcessed' event.
|
||||
if (!$this->forms) {
|
||||
$this->onPageProcessed(new Event(['page' => $this->grav['page']]));
|
||||
}
|
||||
|
||||
// Enable form events if there's a POST
|
||||
if ($this->shouldProcessForm()) {
|
||||
$this->enable([
|
||||
'onFormProcessed' => ['onFormProcessed', 0],
|
||||
'onFormValidationError' => ['onFormValidationError', 0],
|
||||
'onFormFieldTypes' => ['onFormFieldTypes', 0],
|
||||
]);
|
||||
|
||||
$uri = $this->grav['uri'];
|
||||
|
||||
// Post the form
|
||||
if ($this->form) {
|
||||
if ($uri->post('__form-file-uploader__') && $uri->extension() === 'json') {
|
||||
$this->json_response = $this->form->uploadFiles();
|
||||
} else if ($this->form && isset($_POST['__form-file-remover__']) && $this->grav['uri']->extension() === 'json') {
|
||||
$this->json_response = $this->form->filesSessionRemove();
|
||||
} 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 (!$submitted && null === $uri->extension()) {
|
||||
// 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)
|
||||
{
|
||||
/** @var Form $form */
|
||||
$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';
|
||||
if (ini_get('allow_url_fopen')) {
|
||||
$response = json_decode(file_get_contents($url . '?' . $query), true);
|
||||
} else {
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
$response = json_decode(curl_exec($ch), 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 'timestamp':
|
||||
$label = isset($params['label']) ? $params['label'] : 'Timestamp';
|
||||
$format = isset($params['format']) ? $params['format'] : 'Y-m-d H:i:s';
|
||||
$blueprint = $form->value()->blueprints();
|
||||
$blueprint->set('form/fields/timestamp', ['name'=>'timestamp', 'label'=> $label]);
|
||||
$now = new \DateTime('now');
|
||||
$date_string = $now->format($format);
|
||||
$form->setFields($blueprint->fields());
|
||||
$form->setData('timestamp',$date_string);
|
||||
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';
|
||||
$raw_format = !empty($params['dateraw']) ? (bool) $params['dateraw'] : false;
|
||||
$postfix = !empty($params['filepostfix']) ? $params['filepostfix'] : '';
|
||||
$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, $raw_format) . $postfix. $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;
|
||||
|
||||
if (!empty($params['raw']) || !empty($params['template'])) {
|
||||
// Save data as it comes from the form.
|
||||
if ($operation === 'add') {
|
||||
throw new \RuntimeException('Form save: \'operation: add\' is not supported for raw files');
|
||||
}
|
||||
switch ($ext) {
|
||||
case '.yaml':
|
||||
$file = YamlFile::instance($fullFileName);
|
||||
break;
|
||||
case '.json':
|
||||
$file = JsonFile::instance($fullFileName);
|
||||
break;
|
||||
default:
|
||||
throw new \RuntimeException('Form save: Unsupported RAW file format, please use either yaml or json');
|
||||
}
|
||||
|
||||
$data = [
|
||||
'_data_type' => 'form',
|
||||
'template' => !empty($params['template']) ? $params['template'] : null,
|
||||
'name' => $form->name(),
|
||||
'timestamp' => date('Y-m-d H:i:s'),
|
||||
'content' => $form->getData()->toArray()
|
||||
];
|
||||
|
||||
$file->save(array_filter($data));
|
||||
break;
|
||||
}
|
||||
|
||||
$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;
|
||||
case 'call':
|
||||
$callable = $params;
|
||||
|
||||
if (is_array($callable) && !method_exists($callable[0], $callable[1])) {
|
||||
throw new \RuntimeException('Form cannot be processed (method does not exist)');
|
||||
}
|
||||
if (is_string($callable) && !function_exists($callable)) {
|
||||
throw new \RuntimeException('Form cannot be processed (function does not exist)');
|
||||
}
|
||||
|
||||
call_user_func($callable, $form);
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
$uri = $this->grav['uri'];
|
||||
$nonce = $uri->post('form-nonce');
|
||||
$status = $nonce ? true : false; // php72 quirk?
|
||||
$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 (isset($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 = $uri->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 Page|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) {
|
||||
// try to get the page if possible
|
||||
if (null === $page) {
|
||||
$page = $this->grav['page'];
|
||||
}
|
||||
|
||||
$form_name = $this->grav['uri']->post('__form-name__', FILTER_SANITIZE_STRING);
|
||||
|
||||
if (!$form_name) {
|
||||
$form_name = $page ? $page->slug() : null;
|
||||
}
|
||||
|
||||
$this->form = $this->getFormByName($form_name);
|
||||
|
||||
// last attempt using current page's form
|
||||
if (null === $this->form && $page) {
|
||||
$header = $page->header();
|
||||
|
||||
if (isset($header->form)) {
|
||||
$this->form = new Form($page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load cached forms and merge with any currently found forms
|
||||
*/
|
||||
protected function loadCachedForms()
|
||||
{
|
||||
// Get and set the cache of forms if it exists
|
||||
list($forms, $flat_forms) = $this->grav['cache']->fetch($this->getFormCacheId());
|
||||
|
||||
// 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
|
||||
*/
|
||||
protected function saveCachedForms()
|
||||
{
|
||||
// Save the current state of the forms to cache
|
||||
if ($this->recache_forms) {
|
||||
$this->recache_forms = false;
|
||||
$this->grav['cache']->save($this->getFormCacheId(), [$this->forms, $this->flat_forms]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current page cache based id for the forms cache
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getFormCacheId()
|
||||
{
|
||||
return $this->grav['pages']->getPagesCacheId() . '-form-plugin';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create unix timestamp for storing the data into the filesystem.
|
||||
*
|
||||
* @param string $format
|
||||
* @param bool $raw
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function udate($format = 'u', $raw = false)
|
||||
{
|
||||
|
||||
$utimestamp = microtime(true);
|
||||
|
||||
if ($raw) {
|
||||
return date($format);
|
||||
}
|
||||
|
||||
$timestamp = floor($utimestamp);
|
||||
$milliseconds = round(($utimestamp - $timestamp) * 1000000);
|
||||
|
||||
return date(preg_replace('`(?<!\\\\)u`', \sprintf('%06d', $milliseconds), $format), $timestamp);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
enabled: true
|
||||
built_in_css: true
|
||||
inline_css: true
|
||||
refresh_prevention: false
|
||||
client_side_validation: true
|
||||
inline_errors: 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,421 @@
|
||||
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"
|
||||
USE_INLINE_CSS: "Use inline 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"
|
||||
CLIENT_SIDE_VALIDATION: "Client-side validation"
|
||||
CLIENT_SIDE_VALIDATION_HELP: "By default forms will use HTML5 client validation as first line of defense"
|
||||
INLINE_ERRORS: "Inline errors"
|
||||
INLINE_ERRORS_HELP: "Useful with 'client-side validation' off, shows inline contextual form errors"
|
||||
RESOLUTION_MIN: "The {{attr}} was less than the minimum of {{min}}px <br />"
|
||||
RESOLUTION_MAX: "The {{attr}} was more than the maximum of {{max}}px <br />"
|
||||
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."
|
||||
DATA_SUMMARY: "Hier die Zusammenfassung von dem, was Sie geschrieben haben:"
|
||||
YES: "Ja"
|
||||
NO: "Nein"
|
||||
|
||||
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"
|
||||
FORM_ALREADY_SUBMITTED: "Este formulario ya ha sido enviado"
|
||||
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"
|
||||
DATA_SUMMARY: "Este es el resumen de lo que nos has escrito:"
|
||||
NO_FORM_DATA: "No hay datos disponibles del formulario"
|
||||
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"
|
||||
GENERAL: "General"
|
||||
USE_BUILT_IN_CSS: "Utilizar CSS incorporado"
|
||||
USE_INLINE_CSS: "Utilizar CSS en línea"
|
||||
FILEUPLOAD_PREVENT_SELF: 'No se puede utilizar "%s" fuera de las páginas'
|
||||
FILEUPLOAD_UNABLE_TO_UPLOAD: 'No se puede subir el fichero %s: %s'
|
||||
FILEUPLOAD_UNABLE_TO_MOVE: 'No se puede mover el fichero %s to "%s"'
|
||||
DROPZONE_CANCEL_UPLOAD: 'Cancelar subida'
|
||||
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: '¿Estas seguro que quieres cancelar esta subida?'
|
||||
DROPZONE_DEFAULT_MESSAGE: 'Arrastra tus ficheros aquí o <strong>haz click en esta área</strong>'
|
||||
DROPZONE_FALLBACK_MESSAGE: 'Tu navegador no soporta el arrastrar y soltar ficheros de subida.'
|
||||
DROPZONE_FALLBACK_TEXT: 'Por favor utiliza el formulario alternativo que esta debajo, para subir ficheros como antiguamente.'
|
||||
DROPZONE_FILE_TOO_BIG: 'Fichero demasiado grande ({{filesize}}MiB). Máximo tamaño: {{maxFilesize}}MiB.'
|
||||
DROPZONE_INVALID_FILE_TYPE: "No puedes subir ficheros de este tipo."
|
||||
DROPZONE_MAX_FILES_EXCEEDED: "No puedes subir mas ficheros."
|
||||
DROPZONE_REMOVE_FILE: "Borrar fichero"
|
||||
DROPZONE_REMOVE_FILE_CONFIRMATION: '¿Estas seguro de que quieres borrar este fichero?'
|
||||
DROPZONE_RESPONSE_ERROR: "El servidor respondió con el código de estado {{statusCode}}."
|
||||
YES: "Si"
|
||||
NO: "No"
|
||||
REFRESH_PREVENTION: "Prevenir refresco"
|
||||
REFRESH_PREVENTION_HELP: "Utiliza el ID único del formulario para garantizar que el este no se vuelve a procesar cuando se refresca el navegador"
|
||||
LIMIT: "Límite"
|
||||
LIMIT_HELP: "Máximo número de ficheros permitidos por campo (múltiples requeridos)"
|
||||
FILESIZE: "Tamaño máximo de fichero"
|
||||
FILESIZE_HELP: "Tamaño máximo de fichero permitido (en MB), 0 = Utiliza el de por defecto del sistema"
|
||||
AVOID_OVERWRITING: "Evitar sobreescribir"
|
||||
AVOID_OVERWRITING_HELP: "Evita que los ficheros con el mismo nombre sean sobreescritos. Se agregará el prefijo de fecha"
|
||||
RANDOM_NAME: "Nombre aleatorio"
|
||||
RANDOM_NAME_HELP: "Genera una cadela aleatoria de 15 caracteres como nombre de fichero a subir"
|
||||
CLIENT_SIDE_VALIDATION: "Validación del lado del cliente"
|
||||
CLIENT_SIDE_VALIDATION_HELP: "Por defecto los formularios usarán validación HTML5 del lado del cliente como primera línea de defensa"
|
||||
INLINE_ERRORS: "Errores en línea"
|
||||
INLINE_ERRORS_HELP: "Útil con 'validación del lado del cliente' desactivado, muestra errores en el formulario contextual en línea"
|
||||
RESOLUTION_MIN: "{{attr}} fué menor que el mínimo {{min}}px <br />"
|
||||
RESOLUTION_MAX: "{{attr}} fué mayor que el máximo {{max}}px <br />"
|
||||
|
||||
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,36 @@
|
||||
{
|
||||
"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": "^5.3.0",
|
||||
"exif-js": "^2.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,44 @@
|
||||
{% macro render_field(form, fields, scope) %}
|
||||
{% for index, field in fields %}
|
||||
{% set input = attribute(field, "input@") %}
|
||||
|
||||
{% if input is null or input == true %}
|
||||
|
||||
{% if form.value(scope ~ field.name) %}
|
||||
{% block field %}
|
||||
<div>
|
||||
{% block field_label %}
|
||||
<strong>{{ field.label|t|e }}</strong>:
|
||||
{% endblock %}
|
||||
|
||||
{% block field_value %}
|
||||
{% if field.type == 'checkboxes' %}
|
||||
<ul>
|
||||
{% set use_keys = field.use is defined and field.use == 'keys' %}
|
||||
{% for key,value in form.value(scope ~ field.name) %}
|
||||
{% set index = (use_keys ? key : value) %}
|
||||
<li>{{ field.options[index]|e }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% elseif field.type == 'checkbox' %}
|
||||
{{ (form.value(scope ~ field.name) == 1) ? "PLUGIN_FORM.YES"|t|e : "PLUGIN_FORM.NO"|t|e }}
|
||||
{% elseif field.type == 'select' %}
|
||||
{{ field.options[form.value(scope ~ field.name)]|e }}
|
||||
{% else %}
|
||||
{{ string(form.value(scope ~ field.name))|nl2br }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if field.fields %}
|
||||
{% set new_scope = field.nest_id ? scope ~ field.name ~ '.' : scope %}
|
||||
{{ _self.render_field(form, field.fields, new_scope) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
|
||||
{{ _self.render_field(form, form.fields, '') }}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{%- macro render_field(form, fields, scope) %}
|
||||
{%- for index, field in fields %}
|
||||
{%- set input = attribute(field, "input@") %}
|
||||
{%- if input is null or input == true %}
|
||||
{%- set value = form.value(scope ~ (field.name ?? index)) %}
|
||||
{{- scope ~ (field.name ?? index) }}: {{ string(value is iterable ? value|json_encode : value|escape('yaml')) ~ "\r\n" }}
|
||||
{%- else %}
|
||||
{%- if field.fields %}
|
||||
{%- set new_scope = field.nest_id ? scope ~ field.name ~ '.' : scope -%}
|
||||
{{- _self.render_field(form, field.fields, new_scope) }}
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
{%- endmacro %}
|
||||
{%- autoescape false %}
|
||||
{{- _self.render_field(form, form.fields, '') ~ "\r\n" }}
|
||||
{%- endautoescape %}
|
||||
@@ -0,0 +1,113 @@
|
||||
{% 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) %}
|
||||
{% set required = client_side_validation and field.validate.required in ['on', 'true', 1] %}
|
||||
{% set autofocus = (inline_errors == false) and field.autofocus in ['on', 'true', 1] %}
|
||||
|
||||
{% if inline_errors and errors %}
|
||||
{% set autofocus = true %}
|
||||
{% endif %}
|
||||
|
||||
{% 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) and field.display_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|e('html_attr') }}
|
||||
{% else %}
|
||||
{{ field.label|default(field.name|capitalize)|t|e('html_attr') }}
|
||||
{% 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|e('html_attr') }}"{% endif %}
|
||||
{% if autofocus %}autofocus="autofocus"{% endif %}
|
||||
{% if field.novalidate in ['on', 'true', 1] %}novalidate="novalidate"{% endif %}
|
||||
{% if field.readonly in ['on', 'true', 1] %}readonly="readonly"{% endif %}
|
||||
{% if field.autocomplete in ['on', 'off', 'new-password'] %}autocomplete="{{ field.autocomplete }}"{% endif %}
|
||||
{% if field.autocapitalize in ['off', 'characters', 'words', 'sentences'] %}autocapitalize="{{ field.autocapitalize }}"{% endif %}
|
||||
{% if field.inputmode in ['none', 'text', 'decimal', 'numeric', 'tel', 'search', 'emaiil', 'url'] %}inputmode="{{ field.inputmode }}"{% endif %}
|
||||
{% if field.spellcheck in ['true', 'false'] %}spellcheck="{{ field.spellcheck }}"{% endif %}
|
||||
|
||||
{% if field.attributes is defined %}
|
||||
{% for attribute in field.attributes %}
|
||||
{{ attribute.name }}="{{ attribute.value|e }}"
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if required %}required="required"{% endif %}
|
||||
{% if field.validate.pattern %}pattern="{{ field.validate.pattern|e }}"{% endif %}
|
||||
{% if field.validate.message %}title="{{ field.validate.message|t|e }}"
|
||||
{% elseif field.title is defined %}title="{{ field.title|t|e }}" {% endif %}
|
||||
{% if field.datasets %}
|
||||
{% for datakey, datavalue in field.datasets %}
|
||||
data-{{ datakey }}="{{ datavalue|e('html_attr') }}"
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
/>
|
||||
{% block append %}{% endblock append %}
|
||||
{% if 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|t|markdown(false)|raw }}
|
||||
{% else %}
|
||||
{{ field.description|t|raw }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,91 @@
|
||||
{% 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') %}
|
||||
{% set client_side_validation = form.client_side_validation is not null ? form.client_side_validation : config.plugins.form.client_side_validation|default(true) %}
|
||||
{% set inline_errors = form.inline_errors is not null ? form.inline_errors : config.plugins.form.inline_errors(false) %}
|
||||
|
||||
{% 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>
|
||||
|
||||
{% if config.forms.dropzone.enabled %}
|
||||
<div id="dropzone-template" style="display:none;">
|
||||
{% include 'forms/dropzone/template.html.twig' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,39 @@
|
||||
<div class="dz-preview dz-file-preview">
|
||||
<div class="dz-image"><img data-dz-thumbnail /></div>
|
||||
|
||||
<div class="dz-details">
|
||||
<div class="dz-size"><span data-dz-size></span></div>
|
||||
<div class="dz-filename"><span data-dz-name></span></div>
|
||||
</div>
|
||||
<div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div>
|
||||
<div class="dz-error-message"><span data-dz-errormessage></span></div>
|
||||
<a class="dz-remove" title="remove" href="javascript:undefined;" data-dz-remove>×</a>
|
||||
|
||||
<div class="dz-success-mark">
|
||||
|
||||
<svg width="54px" height="54px" viewBox="0 0 54 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.2.1 (9971) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Check</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<path d="M23.5,31.8431458 L17.5852419,25.9283877 C16.0248253,24.3679711 13.4910294,24.366835 11.9289322,25.9289322 C10.3700136,27.4878508 10.3665912,30.0234455 11.9283877,31.5852419 L20.4147581,40.0716123 C20.5133999,40.1702541 20.6159315,40.2626649 20.7218615,40.3488435 C22.2835669,41.8725651 24.794234,41.8626202 26.3461564,40.3106978 L43.3106978,23.3461564 C44.8771021,21.7797521 44.8758057,19.2483887 43.3137085,17.6862915 C41.7547899,16.1273729 39.2176035,16.1255422 37.6538436,17.6893022 L23.5,31.8431458 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z" id="Oval-2" stroke-opacity="0.198794158" stroke="#747474" fill-opacity="0.816519475" fill="#FFFFFF" sketch:type="MSShapeGroup"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
</div>
|
||||
<div class="dz-error-mark">
|
||||
|
||||
<svg width="54px" height="54px" viewBox="0 0 54 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.2.1 (9971) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>error</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="Check-+-Oval-2" sketch:type="MSLayerGroup" stroke="#747474" stroke-opacity="0.198794158" fill="#FFFFFF" fill-opacity="0.816519475">
|
||||
<path d="M32.6568542,29 L38.3106978,23.3461564 C39.8771021,21.7797521 39.8758057,19.2483887 38.3137085,17.6862915 C36.7547899,16.1273729 34.2176035,16.1255422 32.6538436,17.6893022 L27,23.3431458 L21.3461564,17.6893022 C19.7823965,16.1255422 17.2452101,16.1273729 15.6862915,17.6862915 C14.1241943,19.2483887 14.1228979,21.7797521 15.6893022,23.3461564 L21.3431458,29 L15.6893022,34.6538436 C14.1228979,36.2202479 14.1241943,38.7516113 15.6862915,40.3137085 C17.2452101,41.8726271 19.7823965,41.8744578 21.3461564,40.3106978 L27,34.6568542 L32.6538436,40.3106978 C34.2176035,41.8744578 36.7547899,41.8726271 38.3137085,40.3137085 C39.8758057,38.7516113 39.8771021,36.2202479 38.3106978,34.6538436 L32.6568542,29 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z" id="Oval-2" sketch:type="MSShapeGroup"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
{% 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,36 @@
|
||||
{% 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 required %}required="required"{% endif %}
|
||||
{% endblock %}
|
||||
/>
|
||||
<label style="display:inline;" class="inline" for="{{ id|e }}">
|
||||
{% if field.markdown %}
|
||||
{{ field.label|t|markdown(false) }}
|
||||
{% else %}
|
||||
{{ field.label|t|e }}
|
||||
{% endif %}
|
||||
{{ field.validate.required in ['on', 'true', 1] ? '<span class="required">*</span>' }}
|
||||
</label>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,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,25 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% if field.file %}
|
||||
{% set content = read_file(field.file) %}
|
||||
{% else %}
|
||||
{% set content = field.content %}
|
||||
{% endif %}
|
||||
|
||||
{% block input %}
|
||||
<div class="form-display-wrapper {{ field.size }} {{ field.classes }}" {% if field.id is defined %}id="{{ field.id|e }}" {% endif %}>
|
||||
{% if field.markdown %}
|
||||
{% if grav.twig.twig.filters['tu'] is defined %}
|
||||
{{ content|tu|markdown|raw }}
|
||||
{% else %}
|
||||
{{ content|t|markdown|raw }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if grav.twig.twig.filters['tu'] is defined %}
|
||||
{{ content|tu|raw }}
|
||||
{% else %}
|
||||
{{ 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,15 @@
|
||||
{% set scope = field.nest_id ? scope ~ field.name ~ '.' : scope %}
|
||||
<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,112 @@
|
||||
{% 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 %}
|
||||
|
||||
{% do config.set('forms.dropzone.enabled', true) %}
|
||||
|
||||
{% 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, resolution: files.resolution, resizeWidth: files.resizeWidth, resizeHeight: files.resizeHeight, resizeQuality: files.resizeQuality } %}
|
||||
{% set dropzoneSettings = field.dropzone %}
|
||||
<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') }}" data-dropzone-options="{{ dropzoneSettings|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 %}
|
||||
{% if required %}required="required"{% 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 ~ ",
|
||||
'RESOLUTION_MIN': " ~ 'PLUGIN_FORM.RESOLUTION_MIN'|t|json_encode ~ ",
|
||||
'RESOLUTION_MAX': " ~ 'PLUGIN_FORM.RESOLUTION_MAX'|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,8 @@
|
||||
<input aria-hidden="true"
|
||||
type="text"
|
||||
{% if config.plugins.form.inline_css == true %}
|
||||
style="visibility:hidden;position:absolute!important;height:1px;width:1px;overflow:hidden;clip:rect(1px,1px,1px,1px);"
|
||||
{% endif %}
|
||||
class="form-honeybear"
|
||||
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,6 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="password"
|
||||
{{ 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 required %}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,49 @@
|
||||
{% 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 required %}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, item_value in field.options %}
|
||||
{% if item_value is iterable %}
|
||||
<optgroup label="{{ key }}">
|
||||
{%for subkey, suboption in item_value %}
|
||||
{% 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>
|
||||
{% else %}
|
||||
{% set selected = field.selectize ? item_value : key %}
|
||||
{% set val = field.selectize and field.multiple ? item_value : key %}
|
||||
<option {% if key == value or (field.multiple and selected in value) %}selected="selected"{% endif %} value="{{ val }}">{%- if grav.twig.twig.filters['tu'] is defined -%}{{ item_value|tu|raw }}{% else %}{{ item_value|t|raw }}{%- endif -%}</option>
|
||||
{% endif %}
|
||||
{% 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 required %}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,105 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% block prepend %}
|
||||
<div id="signature-pad" class="signature-pad">
|
||||
<div class="signature-pad--body">
|
||||
<canvas></canvas>
|
||||
</div>
|
||||
<div class="signature-pad--footer">
|
||||
<div class="description">Sign above</div>
|
||||
|
||||
<div class="signature-pad--actions">
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary-border" data-action="clear">Clear Signature</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{{ url('plugin://form/assets/signature_pad.js') }}"></script>
|
||||
<script>
|
||||
|
||||
var wrapper = document.getElementById("signature-pad");
|
||||
var clearButton = wrapper.querySelector("[data-action=clear]");
|
||||
|
||||
var canvas = wrapper.querySelector("canvas");
|
||||
var signaturePad = new SignaturePad(canvas, {
|
||||
// It's Necessary to use an opaque color when saving image as JPEG;
|
||||
// this option can be omitted if only saving as PNG or SVG
|
||||
backgroundColor: 'rgb(255, 255, 255)',
|
||||
onEnd: function() {
|
||||
var input = document.querySelector('[name="data[{{ field.name }}]"]');
|
||||
input.value = this.toDataURL();
|
||||
}
|
||||
});
|
||||
|
||||
// Adjust canvas coordinate space taking into account pixel ratio,
|
||||
// to make it look crisp on mobile devices.
|
||||
// This also causes canvas to be cleared.
|
||||
function resizeCanvas() {
|
||||
// When zoomed out to less than 100%, for some very strange reason,
|
||||
// some browsers report devicePixelRatio as less than 1
|
||||
// and only part of the canvas is cleared then.
|
||||
var ratio = Math.max(window.devicePixelRatio || 1, 1);
|
||||
|
||||
// This part causes the canvas to be cleared
|
||||
canvas.width = canvas.offsetWidth * ratio;
|
||||
canvas.height = canvas.offsetHeight * ratio;
|
||||
canvas.getContext("2d").scale(ratio, ratio);
|
||||
|
||||
// This library does not listen for canvas changes, so after the canvas is automatically
|
||||
// cleared by the browser, SignaturePad#isEmpty might still return false, even though the
|
||||
// canvas looks empty, because the internal data of this library wasn't cleared. To make sure
|
||||
// that the state of this library is consistent with visual state of the canvas, you
|
||||
// have to clear it manually.
|
||||
signaturePad.clear();
|
||||
}
|
||||
|
||||
// On mobile devices it might make more sense to listen to orientation change,
|
||||
// rather than window resize events.
|
||||
window.onresize = resizeCanvas;
|
||||
resizeCanvas();
|
||||
|
||||
function download(dataURL, filename) {
|
||||
var blob = dataURLToBlob(dataURL);
|
||||
var url = window.URL.createObjectURL(blob);
|
||||
|
||||
var a = document.createElement("a");
|
||||
a.style = "display: none";
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// One could simply use Canvas#toBlob method instead, but it's just to show
|
||||
// that it can be done using result of SignaturePad#toDataURL.
|
||||
function dataURLToBlob(dataURL) {
|
||||
// Code taken from https://github.com/ebidel/filer.js
|
||||
var parts = dataURL.split(';base64,');
|
||||
var contentType = parts[0].split(":")[1];
|
||||
var raw = window.atob(parts[1]);
|
||||
var rawLength = raw.length;
|
||||
var uInt8Array = new Uint8Array(rawLength);
|
||||
|
||||
for (var i = 0; i < rawLength; ++i) {
|
||||
uInt8Array[i] = raw.charCodeAt(i);
|
||||
}
|
||||
|
||||
return new Blob([uInt8Array], { type: contentType });
|
||||
}
|
||||
|
||||
clearButton.addEventListener("click", function (event) {
|
||||
signaturePad.clear();
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock prepend %}
|
||||
|
||||
{% block input_attributes %}
|
||||
type="hidden"
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,33 @@
|
||||
<div class="form-spacer {{ field.classes }}">
|
||||
{% 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,29 @@
|
||||
{% 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 required %}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 %}
|
||||
{% if field.minlength is defined %}minlength="{{ field.minlength }}"{% endif %}
|
||||
{% if field.maxlength is defined %}maxlength="{{ field.maxlength }}"{% 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,6 @@
|
||||
{% if form.message %}
|
||||
{% set inline_errors = form.inline_errors is not null ? form.inline_errors : config.plugins.form.inline_errors(false) %}
|
||||
{% set status_mapping = {'success':'green', 'error': 'red', 'warning': 'yellow'} %}
|
||||
{% set message = inline_errors and form.messages ? "FORM.VALIDATION_FAIL"|t : form.message %}
|
||||
<div class="notices {{ form.status }} {{ status_mapping[form.status] ?: 'green' }}"><p>{{ message|raw }}</p></div>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,3 @@
|
||||
{% set inline_errors = form.inline_errors is not null ? form.inline_errors : config.plugins.form.inline_errors(false) %}
|
||||
{% set message = inline_errors and form.messages ? "FORM.VALIDATION_FAIL"|t : form.message %}
|
||||
{{ {'status':form.status, 'message':message}|json_encode|raw }}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInitd9f2f96e3ad6fd86ce688af0527a1d7b::getLoader();
|
||||
+445
@@ -0,0 +1,445 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @see http://www.php-fig.org/psr/psr-0/
|
||||
* @see http://www.php-fig.org/psr/psr-4/
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
// PSR-4
|
||||
private $prefixLengthsPsr4 = array();
|
||||
private $prefixDirsPsr4 = array();
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
private $prefixesPsr0 = array();
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
private $useIncludePath = false;
|
||||
private $classMap = array();
|
||||
private $classMapAuthoritative = false;
|
||||
private $missingClasses = array();
|
||||
private $apcuPrefix;
|
||||
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', $this->prefixesPsr0);
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $classMap Class to filename map
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix, either
|
||||
* appending or prepending to the ones previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prepending to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix,
|
||||
* replacing any others previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 base directories
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||
*
|
||||
* @param string|null $apcuPrefix
|
||||
*/
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getApcuPrefix()
|
||||
{
|
||||
return $this->apcuPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return bool|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||
if ($hit) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if (null !== $this->apcuPrefix) {
|
||||
apcu_add($this->apcuPrefix.$class, $file);
|
||||
}
|
||||
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
$subPath = $class;
|
||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||
$subPath = substr($subPath, 0, $lastPos);
|
||||
$search = $subPath.'\\';
|
||||
if (isset($this->prefixDirsPsr4[$search])) {
|
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||
$length = $this->prefixLengthsPsr4[$first][$search];
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*/
|
||||
function includeFile($file)
|
||||
{
|
||||
include $file;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
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,9 @@
|
||||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Grav\\Plugin\\Form\\' => array($baseDir . '/classes'),
|
||||
);
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInitd9f2f96e3ad6fd86ce688af0527a1d7b
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInitd9f2f96e3ad6fd86ce688af0527a1d7b', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInitd9f2f96e3ad6fd86ce688af0527a1d7b', 'loadClassLoader'));
|
||||
|
||||
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
|
||||
if ($useStaticLoader) {
|
||||
require_once __DIR__ . '/autoload_static.php';
|
||||
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInitd9f2f96e3ad6fd86ce688af0527a1d7b::getInitializer($loader));
|
||||
} else {
|
||||
$map = require __DIR__ . '/autoload_namespaces.php';
|
||||
foreach ($map as $namespace => $path) {
|
||||
$loader->set($namespace, $path);
|
||||
}
|
||||
|
||||
$map = require __DIR__ . '/autoload_psr4.php';
|
||||
foreach ($map as $namespace => $path) {
|
||||
$loader->setPsr4($namespace, $path);
|
||||
}
|
||||
|
||||
$classMap = require __DIR__ . '/autoload_classmap.php';
|
||||
if ($classMap) {
|
||||
$loader->addClassMap($classMap);
|
||||
}
|
||||
}
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInitd9f2f96e3ad6fd86ce688af0527a1d7b
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'G' =>
|
||||
array (
|
||||
'Grav\\Plugin\\Form\\' => 17,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'Grav\\Plugin\\Form\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/../..' . '/classes',
|
||||
),
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInitd9f2f96e3ad6fd86ce688af0527a1d7b::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInitd9f2f96e3ad6fd86ce688af0527a1d7b::$prefixDirsPsr4;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -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'] } }
|
||||
]
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user