From 23e4015d6cdfa7dd3178b21cbec94b9575b6d7f5 Mon Sep 17 00:00:00 2001 From: Shohrat Date: Sun, 22 Oct 2023 18:17:13 +0500 Subject: [PATCH] martin plugin --- plugins/martin/forms/LICENCE.md | 19 + plugins/martin/forms/Plugin.php | 107 + plugins/martin/forms/README.md | 36 + plugins/martin/forms/assets/css/records.css | 8 + plugins/martin/forms/assets/css/uploader.css | 704 +++++++ plugins/martin/forms/assets/imgs/icon.svg | 64 + plugins/martin/forms/assets/imgs/upload.png | Bin 0 -> 8528 bytes .../martin/forms/assets/js/inline-errors.js | 7 + plugins/martin/forms/assets/js/recaptcha.js | 12 + plugins/martin/forms/assets/js/uploader.js | 360 ++++ .../forms/assets/less/uploader.base.less | 354 ++++ .../forms/assets/less/uploader.filemulti.less | 163 ++ .../assets/less/uploader.filesingle.less | 112 ++ .../assets/less/uploader.imagemulti.less | 117 ++ .../assets/less/uploader.imagesingle.less | 91 + .../martin/forms/assets/less/uploader.less | 19 + .../forms/assets/vendor/dropzone/dropzone.js | 1752 +++++++++++++++++ .../martin/forms/classes/BackendHelpers.php | 87 + plugins/martin/forms/classes/GDPR.php | 37 + plugins/martin/forms/classes/MagicForm.php | 308 +++ plugins/martin/forms/classes/ReCaptcha.php | 52 + .../forms/classes/ReCaptchaValidator.php | 21 + plugins/martin/forms/classes/SendMail.php | 146 ++ .../martin/forms/classes/SharedProperties.php | 259 +++ .../martin/forms/classes/UnreadRecords.php | 16 + plugins/martin/forms/components/EmptyForm.php | 18 + .../martin/forms/components/GenericForm.php | 18 + .../martin/forms/components/UploadForm.php | 104 + .../forms/components/emptyform/default.htm | 12 + .../forms/components/genericform/default.htm | 32 + .../forms/components/partials/flash.htm | 33 + .../components/partials/js/recaptcha.htm | 1 + .../components/partials/js/reset-form.htm | 1 + .../components/partials/js/reset-uploader.htm | 2 + .../forms/components/partials/recaptcha.htm | 5 + .../forms/components/uploadform/default.htm | 51 + .../components/uploadform/file-multi.htm | 71 + .../components/uploadform/file-single.htm | 79 + .../components/uploadform/file-upload.htm | 21 + plugins/martin/forms/composer.json | 8 + plugins/martin/forms/controllers/Exports.php | 121 ++ plugins/martin/forms/controllers/Records.php | 107 + .../controllers/exports/config_form.yaml | 2 + .../forms/controllers/exports/index.htm | 16 + .../controllers/records/config_filter.yaml | 13 + .../controllers/records/config_list.yaml | 19 + .../forms/controllers/records/index.htm | 1 + .../records/partials/_action_button.htm | 19 + .../records/partials/_list_toolbar.htm | 76 + .../records/partials/_view_toolbar.htm | 13 + .../martin/forms/controllers/records/view.htm | 50 + plugins/martin/forms/lang/de/lang.php | 177 ++ plugins/martin/forms/lang/en/lang.php | 177 ++ plugins/martin/forms/lang/fr/lang.php | 153 ++ plugins/martin/forms/lang/pt-br/lang.php | 116 ++ plugins/martin/forms/lang/ru/lang.php | 170 ++ plugins/martin/forms/lang/tr/lang.php | 107 + plugins/martin/forms/lang/zh-cn/lang.php | 166 ++ plugins/martin/forms/models/Record.php | 33 + plugins/martin/forms/models/Settings.php | 35 + .../martin/forms/models/export/_header.htm | 10 + .../martin/forms/models/export/fields.yaml | 84 + .../martin/forms/models/record/columns.yaml | 31 + .../models/record/fields/_files_fields.htm | 1 + .../models/record/fields/_stored_fields.htm | 1 + .../forms/models/settings/_gdpr_help.htm | 10 + .../forms/models/settings/_plugin_help.htm | 9 + .../forms/models/settings/_recaptcha_help.htm | 13 + .../martin/forms/models/settings/fields.yaml | 50 + plugins/martin/forms/phpunit.xml | 23 + .../tests/unit/classes/BackendHelpersTest.php | 91 + .../forms/tests/unit/classes/GDPRTest.php | 83 + .../tests/unit/classes/UnreadRecordsTest.php | 51 + .../martin/forms/traits/ComponentUtils.php | 199 ++ plugins/martin/forms/traits/FileUploader.php | 140 ++ .../martin/forms/updates/add_group_field.php | 27 + .../martin/forms/updates/add_unread_field.php | 33 + .../forms/updates/create_records_table.php | 28 + plugins/martin/forms/updates/version.yaml | 127 ++ .../martin/forms/views/mail/autoresponse.htm | 9 + .../martin/forms/views/mail/notification.htm | 31 + 81 files changed, 7929 insertions(+) create mode 100644 plugins/martin/forms/LICENCE.md create mode 100644 plugins/martin/forms/Plugin.php create mode 100644 plugins/martin/forms/README.md create mode 100644 plugins/martin/forms/assets/css/records.css create mode 100644 plugins/martin/forms/assets/css/uploader.css create mode 100644 plugins/martin/forms/assets/imgs/icon.svg create mode 100644 plugins/martin/forms/assets/imgs/upload.png create mode 100644 plugins/martin/forms/assets/js/inline-errors.js create mode 100644 plugins/martin/forms/assets/js/recaptcha.js create mode 100644 plugins/martin/forms/assets/js/uploader.js create mode 100644 plugins/martin/forms/assets/less/uploader.base.less create mode 100644 plugins/martin/forms/assets/less/uploader.filemulti.less create mode 100644 plugins/martin/forms/assets/less/uploader.filesingle.less create mode 100644 plugins/martin/forms/assets/less/uploader.imagemulti.less create mode 100644 plugins/martin/forms/assets/less/uploader.imagesingle.less create mode 100644 plugins/martin/forms/assets/less/uploader.less create mode 100644 plugins/martin/forms/assets/vendor/dropzone/dropzone.js create mode 100644 plugins/martin/forms/classes/BackendHelpers.php create mode 100644 plugins/martin/forms/classes/GDPR.php create mode 100644 plugins/martin/forms/classes/MagicForm.php create mode 100644 plugins/martin/forms/classes/ReCaptcha.php create mode 100644 plugins/martin/forms/classes/ReCaptchaValidator.php create mode 100644 plugins/martin/forms/classes/SendMail.php create mode 100644 plugins/martin/forms/classes/SharedProperties.php create mode 100644 plugins/martin/forms/classes/UnreadRecords.php create mode 100644 plugins/martin/forms/components/EmptyForm.php create mode 100644 plugins/martin/forms/components/GenericForm.php create mode 100644 plugins/martin/forms/components/UploadForm.php create mode 100644 plugins/martin/forms/components/emptyform/default.htm create mode 100644 plugins/martin/forms/components/genericform/default.htm create mode 100644 plugins/martin/forms/components/partials/flash.htm create mode 100644 plugins/martin/forms/components/partials/js/recaptcha.htm create mode 100644 plugins/martin/forms/components/partials/js/reset-form.htm create mode 100644 plugins/martin/forms/components/partials/js/reset-uploader.htm create mode 100644 plugins/martin/forms/components/partials/recaptcha.htm create mode 100644 plugins/martin/forms/components/uploadform/default.htm create mode 100644 plugins/martin/forms/components/uploadform/file-multi.htm create mode 100644 plugins/martin/forms/components/uploadform/file-single.htm create mode 100644 plugins/martin/forms/components/uploadform/file-upload.htm create mode 100644 plugins/martin/forms/composer.json create mode 100644 plugins/martin/forms/controllers/Exports.php create mode 100644 plugins/martin/forms/controllers/Records.php create mode 100644 plugins/martin/forms/controllers/exports/config_form.yaml create mode 100644 plugins/martin/forms/controllers/exports/index.htm create mode 100644 plugins/martin/forms/controllers/records/config_filter.yaml create mode 100644 plugins/martin/forms/controllers/records/config_list.yaml create mode 100644 plugins/martin/forms/controllers/records/index.htm create mode 100644 plugins/martin/forms/controllers/records/partials/_action_button.htm create mode 100644 plugins/martin/forms/controllers/records/partials/_list_toolbar.htm create mode 100644 plugins/martin/forms/controllers/records/partials/_view_toolbar.htm create mode 100644 plugins/martin/forms/controllers/records/view.htm create mode 100644 plugins/martin/forms/lang/de/lang.php create mode 100644 plugins/martin/forms/lang/en/lang.php create mode 100644 plugins/martin/forms/lang/fr/lang.php create mode 100644 plugins/martin/forms/lang/pt-br/lang.php create mode 100644 plugins/martin/forms/lang/ru/lang.php create mode 100644 plugins/martin/forms/lang/tr/lang.php create mode 100644 plugins/martin/forms/lang/zh-cn/lang.php create mode 100644 plugins/martin/forms/models/Record.php create mode 100644 plugins/martin/forms/models/Settings.php create mode 100644 plugins/martin/forms/models/export/_header.htm create mode 100644 plugins/martin/forms/models/export/fields.yaml create mode 100644 plugins/martin/forms/models/record/columns.yaml create mode 100644 plugins/martin/forms/models/record/fields/_files_fields.htm create mode 100644 plugins/martin/forms/models/record/fields/_stored_fields.htm create mode 100644 plugins/martin/forms/models/settings/_gdpr_help.htm create mode 100644 plugins/martin/forms/models/settings/_plugin_help.htm create mode 100644 plugins/martin/forms/models/settings/_recaptcha_help.htm create mode 100644 plugins/martin/forms/models/settings/fields.yaml create mode 100644 plugins/martin/forms/phpunit.xml create mode 100644 plugins/martin/forms/tests/unit/classes/BackendHelpersTest.php create mode 100644 plugins/martin/forms/tests/unit/classes/GDPRTest.php create mode 100644 plugins/martin/forms/tests/unit/classes/UnreadRecordsTest.php create mode 100644 plugins/martin/forms/traits/ComponentUtils.php create mode 100644 plugins/martin/forms/traits/FileUploader.php create mode 100644 plugins/martin/forms/updates/add_group_field.php create mode 100644 plugins/martin/forms/updates/add_unread_field.php create mode 100644 plugins/martin/forms/updates/create_records_table.php create mode 100644 plugins/martin/forms/updates/version.yaml create mode 100644 plugins/martin/forms/views/mail/autoresponse.htm create mode 100644 plugins/martin/forms/views/mail/notification.htm diff --git a/plugins/martin/forms/LICENCE.md b/plugins/martin/forms/LICENCE.md new file mode 100644 index 0000000..38cee79 --- /dev/null +++ b/plugins/martin/forms/LICENCE.md @@ -0,0 +1,19 @@ +# MIT license + +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. diff --git a/plugins/martin/forms/Plugin.php b/plugins/martin/forms/Plugin.php new file mode 100644 index 0000000..107cb00 --- /dev/null +++ b/plugins/martin/forms/Plugin.php @@ -0,0 +1,107 @@ + 'martin.forms::lang.plugin.name', + 'description' => 'martin.forms::lang.plugin.description', + 'author' => 'Martin M.', + 'icon' => 'icon-bolt', + 'homepage' => 'https://github.com/skydiver/' + ]; + } + + public function registerNavigation() { + if(Settings::get('global_hide_button', false)) { return; } + return [ + 'forms' => [ + 'label' => 'martin.forms::lang.menu.label', + 'icon' => 'icon-bolt', + 'iconSvg' => 'plugins/martin/forms/assets/imgs/icon.svg', + 'url' => BackendHelpers::getBackendURL(['martin.forms.access_records' => 'martin/forms/records', 'martin.forms.access_exports' => 'martin/forms/exports'], 'martin.forms.access_records'), + 'permissions' => ['martin.forms.*'], + 'sideMenu' => [ + 'records' => [ + 'label' => 'martin.forms::lang.menu.records.label', + 'icon' => 'icon-database', + 'url' => Backend::url('martin/forms/records'), + 'permissions' => ['martin.forms.access_records'], + 'counter' => UnreadRecords::getTotal(), + 'counterLabel' => 'Un-Read Messages' + ], + 'exports' => [ + 'label' => 'martin.forms::lang.menu.exports.label', + 'icon' => 'icon-download', + 'url' => Backend::url('martin/forms/exports'), + 'permissions' => ['martin.forms.access_exports'] + ], + ] + ] + ]; + } + + public function registerSettings() { + return [ + 'config' => [ + 'label' => 'martin.forms::lang.menu.label', + 'description' => 'martin.forms::lang.menu.settings', + 'category' => SettingsManager::CATEGORY_CMS, + 'icon' => 'icon-bolt', + 'class' => 'Martin\Forms\Models\Settings', + 'permissions' => ['martin.forms.access_settings'], + 'order' => 500 + ] + ]; + } + + public function registerPermissions() { + return [ + 'martin.forms.access_settings' => ['tab' => 'martin.forms::lang.permissions.tab', 'label' => 'martin.forms::lang.permissions.access_settings'], + 'martin.forms.access_records' => ['tab' => 'martin.forms::lang.permissions.tab', 'label' => 'martin.forms::lang.permissions.access_records'], + 'martin.forms.access_exports' => ['tab' => 'martin.forms::lang.permissions.tab', 'label' => 'martin.forms::lang.permissions.access_exports'], + 'martin.forms.gdpr_cleanup' => ['tab' => 'martin.forms::lang.permissions.tab', 'label' => 'martin.forms::lang.permissions.gdpr_cleanup'], + ]; + } + + public function registerComponents() { + return [ + 'Martin\Forms\Components\GenericForm' => 'genericForm', + 'Martin\Forms\Components\UploadForm' => 'uploadForm', + 'Martin\Forms\Components\EmptyForm' => 'emptyForm', + ]; + } + + public function registerMailTemplates() { + return [ + 'martin.forms::mail.notification' => Lang::get('martin.forms::lang.mails.form_notification.description'), + 'martin.forms::mail.autoresponse' => Lang::get('martin.forms::lang.mails.form_autoresponse.description'), + ]; + } + + public function register() { + $this->app->resolving('validator', function($validator) { + Validator::extend('recaptcha', 'Martin\Forms\Classes\ReCaptchaValidator@validateReCaptcha'); + }); + } + + public function registerSchedule($schedule) { + $schedule->call(function () { + $records = GDPR::cleanRecords(); + })->daily(); + } + + } + +?> diff --git a/plugins/martin/forms/README.md b/plugins/martin/forms/README.md new file mode 100644 index 0000000..d870380 --- /dev/null +++ b/plugins/martin/forms/README.md @@ -0,0 +1,36 @@ +# Magic Forms for OctoberCMS +Create easy (and almost magic) AJAX forms. + + + +## Why Magic Forms? +Almost everyday we do forms for our clients, personal projects, etc + +Sometimes we need to add or remove fields, change validations, store data and at some point, this can be boring and repetitive. + +So, the objective was to find a way to just put the HTML elements on the page, skip the repetitive task of coding and (with some kind of magic) store this data on a database or send by mail. + + + +## Features +* Create any type of form: contact, feedback, registration, uploads, etc +* Write only HTML +* Don't code forms logic +* Laravel validation +* Custom validation errors +* Use multiple forms on same page +* Store on database +* Export data in CSV +* Access database records from backend +* Send mail notifications to multiple recipients +* Auto-response email on form submit +* reCAPTCHA validation +* Support for Translate plugin +* Inline errors with fields (read documentation for more info) +* AJAX file uploads (BETA, available since v1.3.0) + + + +## Documentation +Checkout our docs at: +> https://skydiver.github.io/october-plugin-forms/ diff --git a/plugins/martin/forms/assets/css/records.css b/plugins/martin/forms/assets/css/records.css new file mode 100644 index 0000000..cdac4db --- /dev/null +++ b/plugins/martin/forms/assets/css/records.css @@ -0,0 +1,8 @@ +.files-container ul { padding:0; list-style:none; } + +.record-table { width:100%; border-collapse:collapse; box-shadow:5px 5px 10px #AAA; } +.record-table td { padding:10px; border:solid 1px #D4D8DA; } +.record-label { width:20%; background-color:#ECF0F1; text-align:right; } +.record-value { width:80%; background-color:#F9F9F9; } +.record-value ul { margin:0; } +.record-metadata { font-size:0.8em; color:#888; font-weight:bold; } \ No newline at end of file diff --git a/plugins/martin/forms/assets/css/uploader.css b/plugins/martin/forms/assets/css/uploader.css new file mode 100644 index 0000000..fc13815 --- /dev/null +++ b/plugins/martin/forms/assets/css/uploader.css @@ -0,0 +1,704 @@ +.responsiv-uploader-fileupload:after { + content: ""; + display: table; + clear: both; +} +.responsiv-uploader-fileupload .upload-object { + border-radius: 3px; + position: relative; + outline: none; + overflow: hidden; + display: inline-block; + vertical-align: top; +} +.responsiv-uploader-fileupload .upload-object img { + width: 100%; + height: 100%; +} +.responsiv-uploader-fileupload .upload-object .icon-container { + display: table; + opacity: .6; +} +.responsiv-uploader-fileupload .upload-object .icon-container i { + color: #95a5a6; + display: inline-block; +} +.responsiv-uploader-fileupload .upload-object .icon-container div { + display: table-cell; + text-align: center; + vertical-align: middle; +} +.responsiv-uploader-fileupload .upload-object .icon-container.image > div.icon-wrapper { + display: none; +} +.responsiv-uploader-fileupload .upload-object h4 { + font-weight: 600; + font-size: 13px; + color: #2b3e50; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + line-height: 150%; + margin: 15px 0 5px 0; + padding-right: 0; + transition: padding 0.1s; + position: relative; +} +.responsiv-uploader-fileupload .upload-object h4 a { + position: absolute; + right: 0; + top: 0; + display: none; + font-weight: 400; +} +.responsiv-uploader-fileupload .upload-object p.size, +.responsiv-uploader-fileupload .upload-object p.error { + font-size: 12px; + color: #95a5a6; +} +.responsiv-uploader-fileupload .upload-object p.size strong, +.responsiv-uploader-fileupload .upload-object p.error strong { + font-weight: 400; +} +.responsiv-uploader-fileupload .upload-object p.error { + display: none; + color: #ab2a1c; +} +.responsiv-uploader-fileupload .upload-object .info h4 a, +.responsiv-uploader-fileupload .upload-object .meta a.upload-remove-button { + color: #2b3e50; + display: none; + font-size: 24px; + line-height: 16px; + text-decoration: none; +} +.responsiv-uploader-fileupload .upload-object .icon-container { + position: relative; +} +.responsiv-uploader-fileupload .upload-object .icon-container:after { + background-image: url('../../../../../modules/system/assets/ui/images/loader-transparent.svg'); + position: absolute; + content: ' '; + width: 40px; + height: 40px; + left: 50%; + top: 50%; + margin-top: -20px; + margin-left: -20px; + display: block; + background-size: 40px 40px; + background-position: 50% 50%; + animation: spin 1s linear infinite; +} +.responsiv-uploader-fileupload .upload-object.is-success .icon-container { + opacity: 1; +} +.responsiv-uploader-fileupload .upload-object.is-success .icon-container:after { + opacity: 0; + transition: opacity .3s ease; +} +.responsiv-uploader-fileupload .upload-object.is-loading .icon-container { + opacity: .6; +} +.responsiv-uploader-fileupload .upload-object.is-loading .icon-container:after { + opacity: 1; + transition: opacity .3s ease; +} +.responsiv-uploader-fileupload .upload-object.is-success { + cursor: pointer; +} +.responsiv-uploader-fileupload .upload-object.is-success .progress-bar { + opacity: 0; + transition: opacity .3s ease; +} +.responsiv-uploader-fileupload .upload-object.is-success:hover h4 a, +.responsiv-uploader-fileupload .upload-object.is-success:hover .meta .upload-remove-button { + display: block; +} +.responsiv-uploader-fileupload .upload-object.is-error { + cursor: pointer; +} +.responsiv-uploader-fileupload .upload-object.is-error .progress-bar { + opacity: 0; + transition: opacity .3s ease; +} +.responsiv-uploader-fileupload .upload-object.is-error .icon-container { + opacity: 1; +} +.responsiv-uploader-fileupload .upload-object.is-error .icon-container > img, +.responsiv-uploader-fileupload .upload-object.is-error .icon-container > i { + opacity: .5; +} +.responsiv-uploader-fileupload .upload-object.is-error .info h4 { + color: #ab2a1c; +} +.responsiv-uploader-fileupload .upload-object.is-error p.error { + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.responsiv-uploader-fileupload .upload-object.is-error .info .upload-remove-button, +.responsiv-uploader-fileupload .upload-object.is-error .meta .upload-remove-button { + display: block; +} +.responsiv-uploader-fileupload.is-preview .upload-button, +.responsiv-uploader-fileupload.is-preview .upload-remove-button { + display: none !important; +} +@media (max-width: 1024px) { + .responsiv-uploader-fileupload .upload-object.is-success h4 a, + .responsiv-uploader-fileupload .upload-object.is-success .meta .upload-remove-button { + display: block !important; + } +} +@-moz-keyframes spin { + 0% { + -moz-transform: rotate(0deg); + } + 100% { + -moz-transform: rotate(359deg); + } +} +@-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + } +} +@-o-keyframes spin { + 0% { + -o-transform: rotate(0deg); + } + 100% { + -o-transform: rotate(359deg); + } +} +@-ms-keyframes spin { + 0% { + -ms-transform: rotate(0deg); + } + 100% { + -ms-transform: rotate(359deg); + } +} +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(359deg); + } +} +.responsiv-uploader-fileupload.style-image-multi .upload-button, +.responsiv-uploader-fileupload.style-image-multi .upload-object { + margin: 0 10px 10px 0; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object { + background: #fff; + border: 1px solid #ecf0f1; + width: 260px; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object .progress-bar { + display: block; + width: 100%; + overflow: hidden; + height: 5px; + background-color: #f5f5f5; + border-radius: 2px; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + position: absolute; + bottom: 10px; + left: 0; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object .progress-bar .upload-progress { + float: left; + width: 0%; + height: 100%; + line-height: 5px; + color: #ffffff; + background-color: #5fb6f5; + box-shadow: none; + transition: width .6s ease; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object .icon-container { + border-right: 1px solid #f6f8f9; + float: left; + display: inline-block; + overflow: hidden; + width: 75px; + height: 75px; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object .icon-container i { + font-size: 35px; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object .icon-container.image img { + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; + width: auto; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object .info { + margin-left: 90px; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object .info h4 { + padding-right: 15px; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object .info h4 a { + right: 15px; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object .meta { + position: absolute; + bottom: 0; + left: 0; + right: 0; + margin: 0 15px 0 90px; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object.upload-placeholder { + height: 75px; + background-color: transparent; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object.upload-placeholder:after { + opacity: 0; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object:hover { + background: #4da7e8 !important; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object:hover i, +.responsiv-uploader-fileupload.style-image-multi .upload-object:hover p.size, +.responsiv-uploader-fileupload.style-image-multi .upload-object:hover p.error, +.responsiv-uploader-fileupload.style-image-multi .upload-object:hover .upload-remove-button { + color: #ecf0f1; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object:hover h4 { + color: white; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object:hover .icon-container { + border-right-color: #4da7e8 !important; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object:hover.is-error { + background: #ab2a1c !important; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object:hover h4 { + padding-right: 35px; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object.is-error h4 { + padding-right: 35px; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object.is-error .info p.size { + display: none; +} +.responsiv-uploader-fileupload.style-image-multi .upload-object.is-error .info p.error { + padding-bottom: 11px; +} +.responsiv-uploader-fileupload.style-image-multi.is-preview .upload-files-container { + margin-left: 0; +} +@media (max-width: 1280px) { + .responsiv-uploader-fileupload.style-image-multi .upload-object { + width: 230px; + } +} +@media (max-width: 1024px) { + .responsiv-uploader-fileupload.style-image-multi .upload-button { + width: 100%; + } + .responsiv-uploader-fileupload.style-image-multi .upload-files-container { + margin-left: 0; + } + .responsiv-uploader-fileupload.style-image-multi .upload-object { + margin-right: 0; + display: block; + width: auto; + } +} +.responsiv-uploader-fileupload.style-image-single.is-populated .upload-button { + display: none; +} +.responsiv-uploader-fileupload.style-image-single .upload-button { + display: block; + float: left; + border: 2px dotted rgba(0, 0, 0, 0.1); + position: relative; + outline: none; + min-height: 100px; + min-width: 100px; +} +.responsiv-uploader-fileupload.style-image-single .upload-button .upload-button-icon { + position: absolute; + width: 22px; + height: 22px; + top: 50%; + left: 50%; + margin-top: -11px; + margin-left: -11px; +} +.responsiv-uploader-fileupload.style-image-single .upload-button .upload-button-icon:before { + content: "+"; + text-align: center; + display: block; + font-size: 22px; + height: 22px; + width: 22px; + line-height: 22px; + color: rgba(0, 0, 0, 0.1); + font-weight: 700; +} +.responsiv-uploader-fileupload.style-image-single .upload-button:hover { + border: 2px dotted rgba(0, 0, 0, 0.2); +} +.responsiv-uploader-fileupload.style-image-single .upload-button:hover .upload-button-icon:before { + color: #5cb85c; + color: rgba(0, 0, 0, 0.2); +} +.responsiv-uploader-fileupload.style-image-single .upload-button:focus { + border: 2px solid rgba(0, 0, 0, 0.3); + background: transparent; +} +.responsiv-uploader-fileupload.style-image-single .upload-button:focus .upload-button-icon:before { + color: #5cb85c; + color: rgba(0, 0, 0, 0.2); +} +.responsiv-uploader-fileupload.style-image-single .upload-object { + padding-bottom: 66px; +} +.responsiv-uploader-fileupload.style-image-single .upload-object .icon-container { + border: 1px solid #f6f8f9; + background: rgba(255, 255, 255, 0.5); +} +.responsiv-uploader-fileupload.style-image-single .upload-object .icon-container.image img { + border-radius: 3px; + display: block; + max-width: 100%; + height: auto; + min-height: 100px; + min-width: 100px; +} +.responsiv-uploader-fileupload.style-image-single .upload-object .progress-bar { + display: block; + width: 100%; + overflow: hidden; + height: 5px; + background-color: #f5f5f5; + border-radius: 2px; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + position: absolute; + bottom: 10px; + left: 0; +} +.responsiv-uploader-fileupload.style-image-single .upload-object .progress-bar .upload-progress { + float: left; + width: 0%; + height: 100%; + line-height: 5px; + color: #ffffff; + background-color: #5fb6f5; + box-shadow: none; + transition: width .6s ease; +} +.responsiv-uploader-fileupload.style-image-single .upload-object .info { + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 66px; +} +.responsiv-uploader-fileupload.style-image-single .upload-object .meta { + position: absolute; + bottom: 65px; + left: 0; + right: 0; + margin: 0 15px; +} +.responsiv-uploader-fileupload.style-image-single .upload-object:hover h4 { + padding-right: 20px; +} +.responsiv-uploader-fileupload.style-image-single .upload-object.is-error h4 { + padding-right: 20px; +} +.responsiv-uploader-fileupload.style-image-single .upload-object.is-error .info p.size { + display: none; +} +.responsiv-uploader-fileupload.style-image-single .upload-object.is-error .info p.error { + padding-bottom: 11px; +} +@media (max-width: 1024px) { + .responsiv-uploader-fileupload.style-image-single .upload-object h4 { + padding-right: 20px !important; + } +} +.responsiv-uploader-fileupload.style-file-multi .upload-button { + margin-bottom: 10px; +} +.responsiv-uploader-fileupload.style-file-multi .upload-files-container { + border: 1px solid #eeeeee; + border-radius: 3px; + border-bottom: none; + display: none; +} +.responsiv-uploader-fileupload.style-file-multi.is-populated .upload-files-container { + display: block; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object { + display: block; + width: 100%; + border-bottom: 1px solid #eeeeee; + padding-left: 10px; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object:nth-child(even) { + background-color: #f5f5f5; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object .icon-container { + position: absolute; + top: 0; + left: 5px; + width: 35px; + padding: 11px 7px; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object .info { + margin-left: 35px; + margin-right: 15%; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object .info h4, +.responsiv-uploader-fileupload.style-file-multi .upload-object .info p { + margin: 0; + padding: 11px 0; + font-size: 12px; + font-weight: normal; + line-height: 150%; + color: #666666; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object .info h4 { + padding-right: 15px; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object .info h4 a { + padding: 10px 0; + right: 15px; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object .info p.size { + position: absolute; + top: 0; + right: 0; + width: 15%; + display: none; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object .info p.error { + color: #ab2a1c; + padding-top: 0; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object .progress-bar { + display: block; + width: 100%; + overflow: hidden; + height: 5px; + background-color: #f5f5f5; + border-radius: 2px; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + position: absolute; + top: 18px; + left: 0; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object .progress-bar .upload-progress { + float: left; + width: 0%; + height: 100%; + line-height: 5px; + color: #ffffff; + background-color: #5fb6f5; + box-shadow: none; + transition: width .6s ease; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object .meta { + position: absolute; + top: 0; + right: 0; + margin-right: 15px; + width: 15%; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object .meta .upload-remove-button { + position: absolute; + top: -9px; + right: 0; + bottom: auto; + line-height: 150%; + padding: 10px 0; + z-index: 100; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object .icon-container:after { + width: 20px; + height: 20px; + margin-top: -10px; + margin-left: -10px; + background-size: 20px 20px; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object.is-success .info p.size { + display: block; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object:hover { + background: #4da7e8 !important; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object:hover i, +.responsiv-uploader-fileupload.style-file-multi .upload-object:hover p.size, +.responsiv-uploader-fileupload.style-file-multi .upload-object:hover p.error, +.responsiv-uploader-fileupload.style-file-multi .upload-object:hover .upload-remove-button { + color: #ecf0f1; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object:hover h4 { + color: white; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object:hover .icon-container { + border-right-color: #4da7e8 !important; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object:hover.is-error { + background: #ab2a1c !important; +} +.responsiv-uploader-fileupload.style-file-multi .upload-object:hover h4 { + padding-right: 35px; +} +@media (max-width: 1199px) { + .responsiv-uploader-fileupload.style-file-multi .info { + margin-right: 20% !important; + } + .responsiv-uploader-fileupload.style-file-multi .info p.size { + width: 20% !important; + } + .responsiv-uploader-fileupload.style-file-multi .meta { + width: 20% !important; + } +} +@media (max-width: 991px) { + .responsiv-uploader-fileupload.style-file-multi .upload-object h4 { + padding-right: 35px !important; + } + .responsiv-uploader-fileupload.style-file-multi .info { + margin-right: 25% !important; + } + .responsiv-uploader-fileupload.style-file-multi .info p.size { + width: 25% !important; + padding-right: 35px !important; + } + .responsiv-uploader-fileupload.style-file-multi .meta { + width: 25% !important; + } +} +.responsiv-uploader-fileupload.style-file-single { + background-color: #fff; + border: 1px solid #e0e0e0; + overflow: hidden; + position: relative; + padding-right: 11px; +} +.responsiv-uploader-fileupload.style-file-single .upload-button { + position: absolute; + top: 50%; + margin-top: -44px; + height: 88px; + right: 0; + margin-right: 0; +} +.responsiv-uploader-fileupload.style-file-single .upload-empty-message { + padding: 10px 0 10px 11px; + font-size: 13px; +} +.responsiv-uploader-fileupload.style-file-single.is-populated .upload-button, +.responsiv-uploader-fileupload.style-file-single.is-populated .upload-empty-message { + display: none; +} +.responsiv-uploader-fileupload.style-file-single .upload-object { + display: block; + width: 100%; + padding: 8px 0 10px 0; +} +.responsiv-uploader-fileupload.style-file-single .upload-object .icon-container { + position: absolute; + top: 0; + left: 0; + width: 35px; + padding: 0 5px; + margin: 8px 0 0 7px; + text-align: center; +} +.responsiv-uploader-fileupload.style-file-single .upload-object .info { + margin-left: 54px; + margin-right: 15%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.responsiv-uploader-fileupload.style-file-single .upload-object .info h4, +.responsiv-uploader-fileupload.style-file-single .upload-object .info p { + display: inline; + margin: 0; + padding: 0; + font-size: 12px; + line-height: 150%; + color: #666666; +} +.responsiv-uploader-fileupload.style-file-single .upload-object .info p.size { + font-weight: normal; +} +.responsiv-uploader-fileupload.style-file-single .upload-object .info p.size:before { + content: " - "; +} +.responsiv-uploader-fileupload.style-file-single .upload-object .info p.error { + color: #ab2a1c; + padding-top: 0; +} +.responsiv-uploader-fileupload.style-file-single .upload-object .progress-bar { + display: block; + width: 100%; + overflow: hidden; + height: 5px; + background-color: #f5f5f5; + border-radius: 2px; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + position: absolute; + top: 50%; + margin-top: -2px; + right: 5px; +} +.responsiv-uploader-fileupload.style-file-single .upload-object .progress-bar .upload-progress { + float: left; + width: 0%; + height: 100%; + line-height: 5px; + color: #ffffff; + background-color: #5fb6f5; + box-shadow: none; + transition: width .6s ease; +} +.responsiv-uploader-fileupload.style-file-single .upload-object .meta { + position: absolute; + top: 50%; + margin-top: -44px; + height: 88px; + right: 0; + width: 15%; +} +.responsiv-uploader-fileupload.style-file-single .upload-object .meta .upload-remove-button { + position: absolute; + top: 50%; + right: 0; + height: 20px; + line-height: 20px; + margin-top: -10px; + margin-right: 10px; + z-index: 100; +} +.responsiv-uploader-fileupload.style-file-single .upload-object .icon-container:after { + width: 20px; + height: 20px; + margin-top: -10px; + margin-left: -10px; + background-size: 20px 20px; +} +.responsiv-uploader-fileupload.style-file-single .upload-object.is-error .info p.size { + display: none; +} +.responsiv-uploader-fileupload.style-file-single .upload-object.is-error .info p.error:before { + content: " - "; +} diff --git a/plugins/martin/forms/assets/imgs/icon.svg b/plugins/martin/forms/assets/imgs/icon.svg new file mode 100644 index 0000000..987832f --- /dev/null +++ b/plugins/martin/forms/assets/imgs/icon.svg @@ -0,0 +1,64 @@ + +image/svg+xml \ No newline at end of file diff --git a/plugins/martin/forms/assets/imgs/upload.png b/plugins/martin/forms/assets/imgs/upload.png new file mode 100644 index 0000000000000000000000000000000000000000..bec4988fe0c1113ae6ac541aa281cc7d0ff6fbd9 GIT binary patch literal 8528 zcmb7JXIIltu>Pe$LhlfI=uN70g(UPUARwY3Q99B)(n2p%MFeSyNK=p^3MdMpNKph7 z1f(M_fF8})p?wxb?%(HW5XZGybnHTeDK32^iAMH*^rBYpjST--gK0U&hJ z5sNLEU-`B4S(r#PH@CXJ#J7atWa2C*MwP^*$2^VsPXy%OwGicz#H9bQEJ`*P#3rX8 z7tz{7>?0D1zP97vKK19uC=v_o%&j^v47iw>4snsUsu!mhXdn>n2VsU7DhR}p76OSg zxE}n!81x?wHpsesVZZQUF*Eegz3_p=1w>(-EnFVxrNllfFE5XEG>W?ZQp?xb&(OoW zA=dSdf0P9#%Hp1(2la*XC_Uwc+{rs*ra0^#xJn@ev8I%aOYRe=fYUH(!sVd`GSZT9 zFcC-`5C@I}#X+k@)5<#e5d)RtA)%@i!!@?aVw(O6k3#AU-qAA=d4%t;%k8`ey=E_P-M`~4=+^|1* zVu%0m@QgpM=+MBJy)q&PO3#rdPKYD4el=k*ckmVY`@~YAgMZ8z; zeJM}Wq{NK%XykjFw{;EVO`Sr+Mlo_R7iwdhHvQ>&W808ne)xm7 z@}MyOJSghB?uM~`o$#IWkjO#P!rLKRqjQ#R(ZBA_IyQ)%;+@+V!@{60U$^Y z09LU8pq>u^96pa6KIs7f?`uOHj8*95y8T?8h^;kgtXBQd^>9CMXdiIQAuut6uhGVW zgcdLl6uIb_qj6LQ@YGTvab_X7IIUb4t)rIt(|D|+Jcd7FhXEjET7yaHO2u>2j`G%dSsd4sCeWP_qaagbZtyyha3CS|}Pt zwj!W?>HHWZ7-L7p(MN|z<~AGZ;wF5q#-QU7QaZ^3wa`wi+;w4t`VLAeWdn@NSlJ7G zz@5Z)qXilxfUGru0topVHy4@X>8^8xe-!}3b&?PHN%M8Qe3eXsj1~t!3bF(^(;$*E zj7nEC(+5Y!dF+aEzZ_OjDer%cKB{c*6%n)p46-I}2NuESnV{F>5wM#xp}(6+JIZ4s zPi%v6?-CMVM+`hA5%b4BB3ACivHZFaNUDyu;_Z*57*#tyJxR6bXVB8n@n`_idrN<`RJga6H|_V-^z> z*lGFsu3BqPyWvrYJnA2s>%ANL6TYK_R`qLr$z_dMijRL9Y z8)K^r>|Q0=uMkdkM<}9$X#f1;ukR2U^rM=Ga>Lt59B3~^Dsa{Ic~3eK7_f1de%4Tuv2MWCTQ6nq)vr5C;YkvmdH*A^_ObQ+~A z9cb#yRGf}O9QJbdemlR?c7kKmjXi-PWCt}Al!%66)7eNXGtE94=d`4ec=kaIm8bdH z^a}FBFlHpw+!|=H*NBOZM^!mYNt5+O(OB_Sdgx9!=K0Uw%~3eG4fH@H2^PimC?c=mPXjc=l- zNoz5!!uME+@24}vor#Y|P(}*rG~dK4Ds2aUo1=lu$40t}0lY+3_(2z=liY#nW3*cj zXYS^dWK0+@67~<;W4NQKg@I(J!SY((oh)X4IP3~lyZGFY?$_=awK#h04URW!^m64m zr(=mJdu$=u_D{6T%lSiA8h8+EZY|1#pfa<&g zkEN`KtXX0}C*QTD#>1s_<`n#f3C#AKLCNu8Y|GhkAHlwFh+2@L9de zl?BwdBj38S)%&zS5tTTZRiYtfU5b_zm~Ue9kAr=7wzkGt^AC70w*>5ci9UM1)n&@V zk%E4sAJeZ3sonZ&7W*ZfNdH4d0m@t+oG?pY751eSm8A|A z?pdEX7f#-|E!&18OYY;K-~kA8EH5EOIh7!#=hlY5QmpN@Ij2RmT7cV9BR`a3*f}NA z%8oCq=U?fdOmhh9O)qpfvD@8xeam11XSkNzI1K7Lt@e_P`zy++|4k8EbZ6g_6uRjZgT>p8)*3!=c}?JLVSXw7W-)skp-9!g)N` z$vZu2%lcA~9tG`HcPpOH2H0wP9c0nMCJR2AG-V+tG3jYzMIFz=f3Xf(n@35FuK_ws}=ZK-;j;JOdz>#lR#-dAf5nDU+z*Ljh& zrGyMWhXh-75!`*G@BP_VAD$awg2eOHsM!8tEOc-ta(5R0?6_+B9ma*SCV6xmQR@OB zro(TndB)CejWd>Xp@Jf4;OmPu-$1)gOcLL>ZwL;OX6EHiW*E-D>+h)2H&Zw=hwl4een|IS`cszO$93sE0NFRSwloC?88E470h0UaW zTT)Xem8!59<6@?t%`0LpB;P}gSMNk1R}SFNGOs=G+B>E$6{h%0Bz)$Ie+2e7=)1XC zY!O!`1>kN5JwXidMM9>w>IthRXV_UQ-EJ{yG;w%{hk5Y{X6%A7@u z)|t^Kf5*?SdK)++Wf3D~f`IAzwD2c<@Lpr$La%>V_o+0;EgSyn0+ggk9Q^*(-R)k~ z@sqVwx^Z9LaliOTo_V0UQ$yj$0;^B>nx*V?sPqkmh)W3MdPgy}OUYNugz?LW z@QHcW>>`Qw58F@t zBmNB-OVb=$$ZzgZT}j(mMT(~qOK;L#iSzy`l%(1@p7jf)j0FxGvNs%$3+}n^$l?TV z`8Es&$HPzkb)QD^EC4Xo9&J^W#(Bm7+nx((`e&E%onF=ZWbaHWJ1oxN8dFND*#UI~ zhii+#a>R_*n1&AWr`cm;qf6C5*gb-ladN#@8rwmAQY6nW0M?Sol5wrWn0ot{V$jn( z#SFr2W~1N0)13t2Kw1#;BG03ZQp0bkB33piYq`@uld|>K4WC%0x+l8YMsZNBw-nuu z1uh=~Y+TP*9${|zWl5j0yspUmyp6Bp$`_O<5_)9aW#BFNh<&K6^tRFCiVV*+NJD(C z6qO7G{8_L=a;3)mnLGW`Ofi!2b*o^jeXz@O%|{9LlChC($tIG$;kUZ8UoA9We|?Lq z)CiBZ5~mm~!UvClLMx{euDEd3Cj$NuenRPGRlEXM|0&BmQ)($wNcc+J>SZ&%4@h%5DXWgTta7g zG*W99Mk8VKgLHRJ5(8g{+9+q-w2H+^f^qi<({G;k>y{^FK4iAsq*Qx6_ZpBqZ(pap z3+t(3BAizTB?XK<345_8>mhO6x>vk0 z<37h<-kzD|Dh|I%Wwf3xR5C=4GraqrqEjy3Wf=spsJd{^PVwJ=NV}RpQi+pvt!ss0 zUO_z5-=@=BU1G7;Ww1JBwK`7!u3;DU+cOXsB{w|nceAz2Ea$j7oqx36sjOgVM2mXz zI{L@3M|V|}w~T+}@%j(b@j4!Opdt7KF`ULQ0cZ73;mjI~G@#lotq^>Ig5?z%TMZI2 zA|Im#Y7-&(<9qM#p_^&Um&X`QKccNJ(cFf)zRZ3X$Z(&r`+!N3O7|1 z$f5Pki=o-D-g^E+FZbjtSz5)g(GeZB`#Fi~FQI-lzZYQLK-+h{%k(Rs57&P;`a1uG zOD^CJKBlf_`QE0gLES#|kafqB3I}lb=R^{ zk8Cf*dqOy_f&(^cR|UUOl)uWv`b_`b zmqyQiJk^d=UDK!Z#L62&t9}X8kix@DT0-1zMhNGReu+j{hh0|)FeQ(Y{LxYdqAt}{ zEKRi=9h6$7isOFNtAoLY4FrpT_7KDSk~1EfF5fc^r|*3Yl}U`QG$U`fSVPT1R$!9S_Xw<*CTyi0rc#`jPkLm|c-PW3b*L>|R zCGXv(8#q-R+fX7aHWe~Zpgd(q_&EP1mtwwPM`N7GOI!SYcG6-CH1@H&YWT}`Dgp6* zsg{fIk=l(}dFp@_yP_d^uYKb? zK^9Vs0UM>)wVUb+TWb`zd3ex1iowa8_RQwveaji;`0Bdf^soEb8kqu(`;u?XkN0=F zA&wOZSD>5Jo8OPU0;5MZ+ndy}m%B%(Q2#F3eS%nd(9{J(R<4_{k^VlZu1~MZ0b)G+ z!JhH_w<&N}`0GSt|M-|EhLt{IY>8!P5m+C$2DSFR>rxyZB_8Z&-o73qMJdPYSN2Np z$?55oI1i__wCS5q#eqOD*V;K7ut|vA$jeoF&L`cF!1}dr#_qRg-OzR!ry|evSzItw z>3+mlF9&tqSMIL(gKQ_8SefNNUJp5L7d75G`@@pSIlUlW+xjK6Mwd(gR(>5N@ zf~}2bcm_H`F~8OFUw`-O;^TTs?*FtjPL}CQh9E#YhKJa+yY&m~Vh_x;`n=A?>P(gq zjWEq73}1)aNcIEW`^6M7Uc+V#TUC1vC*-^GFdVNPLT^z2l}^4SzPc5uY-Nu`(yc!~ znsNi8tN@P)X^9tvhqzT1Rwd>k(XUQbJE+eJeqLbT(~0I{_`RgEpWE^|cc4LkUw9|? z=laMwsfn)`Jrlh>>#mo@GItsBGXXbSdq$XqR=Q}1SFn4AygTMXGA zI(&|dQ!MoWo!ILcHvSD%85OyoR}7_DIhp!OaTWqzxv9<3MVKs;wOsx)CO(!{DOv^D zFOS@W4m&UYYs%=X2w97bvtQ}cp(_%Guh*Uze$(9J!&(mixbsWIG4bA$ib=Z?^$B9P zEn4M7Me1bKb>(e`W=?9!6*0L5i0B`&IKx%#c^u{sMgFDTQIX?%XNutnP4u4B*Sr$> zhmcX`W|RF#{ol|z|1<`yQJXivR95Gv7U_M6vgFKbFuPSdB%~kHaf7PSne>2T<<4*l zkIA$N^*fe`rb0Xyw8UE}P@GGZc)j6|0il25;@QofM|jKV7u@Wmq)kvKlo;Axjo?Cf zK2^mBp#rEU^r_rZsZC!bzWLMa9$Lr?UpEF$Ez$f4!YjWT9hm@NkWM%hTu^Z-FT_l&ATFA22eBim*Yuu{Enp>9wEq6Nk>JpUXT} z$$T(%AX#WwbI?63ELfj2vMx@yzXrDdN8Wsk)+XA9TjS{Tt}q}NC`h3@`2SWwh<`^6k+&ca7sx68Af`N|%HF=bpMfRY*haXgX-eb0!=`NO`K zEEjRW4~LdIn@pLba}H$Zgom%P2O8g~{ezE?CcXiEP!E`mKR)zBQ zH-vyJCHiEk*4w593bY5x2q~ttcV%U8Pn>`?I)Ql&zWjf%eHF5>5bhO?qBUt<9XPWF zZ!lwS%E%eAW9PP(CqZaFt#;Fadi+|;lpU5;q~2k#GK@Q!j?M{I3I9YPmwycNpbjcN zbZleJ$LpD+ByrWs6BY1ltk6lhD1OuVw((KlZ;;d2j%VcrrpLsnBZqd!BnMXFU^X)T zSd1S0RhR$rnNQ`piYf7pDZOX|F@t73@fgM)YZ4n3eYQ1w@|%xlIvrJe3@hX;i2HLV7&@t!c_0n*m-~FiVGKEC>4v4 zk<%6#&Hzu=fIe@}!GGyNT3w{qFGAK)7R*QA7F(Hu-{TYMSp?@$#P|})wYr2Xsg0@T zgrg;JP5_|H8mqNHu9qN$Pd7jPX3A5rua&)lK|x7dIpFmvEiL0N7E@4gshR585MR3m zTxR~Y(5e#}$+JdUT#V)zIb8;uZpuGno!8uJmnBsqT~q>KsLA;OG&Z22w}NWmE$xz{ zH-Uz3KLOS$&8OjZyTx!;B_`_N2oFo4Af=(CMCzkw9NJ@UbCV9=>6k<>Zq**gsRFl` zP7dC;hjZtidI8IBRDkaxTCA4ry>uH&D{PJ-mZ62Wpo>WhN5tvRxK?IV+<;iVF zbLF&7d_obvpm1w!CxSq?{&WAdr730y-argeQya1WE-D7?2>UIYrz}Uk^f@Z6UFumh zmKHm{|AT>?WD44}BRa2}MLkt*u8_qa;UO(S(IIm85rrUlpu>KDgmiuVIWBm(u1;Jr z!U*Bi1wS;LQw~C*oRK3tD%a9|OSQ5#$`}gI(dSC>zGju%u4kN03C)&786&rz6K!dx z-|TeL_v0JcV%OKAYM+;YBXTrTi2CWiomz}VYf&;Cb4s8Id*9~rGOr$zd97TiO})nU z8j9^PECv274fT^v>jd2HTOE6PjlxAL;sl6iVUIl{L zl0`e_%J_%R1sfZ$w17LG!ZEePp-f6d2JML}!N^&qI#> zSp(Y22?swd2n@&~MfiFuP4G&4_~;(J4g{|emjjORaH6hJQ|G;gXVMqqXo1kck$c@KJT3(mnkM#I@)^h+qXKRu=qJ^kea!j?*@C5 zF-;C6rna;${au%F$9liWiB>073_8FRjp}KA9G#=gNbR;Sa70YID0eS>Ed_EmJ9J(l z$&C^@Y`U=)cP_DF;P()&nTFzKT!EYxK8bciK0o{oam+aHKx1vHn!lZ!X17aF(PAjh zIiuN)2)1p_KhEucpy;_cBF11Y<sms{{! zG!bx6+2pU|B7&x4!!-l6E)h94x9CZcd+p~Uhq|y{W6B+Kp`K|f6aBNS9IePt(1YD| z!+K!T%e&BUzH1)G81H)#_!>yradw8TQymf>=8Y&-rJ^OK_lVL|HV8$#RL7rw=;j%f z2z^f{AS0wXD$Gc~KNF#;d|P57s=IKUlxPjI%WgL-#tI^bhmz;m#QQM3i=PcTyc>vD z_fNlrJZ0Or^5yPJOqd=8bpdx}%gDjm&(cwX@FhM>v>h$+PKm1&i)jkwqjZ-2@l&lg zvFX{26CrOy%2oZxZgD_KFWX5*3Ad7cy%! z?~OqDQm@HFp{yK9~7g(b3tUZ(BI{t@eC!`y2q?9L7IVgh@~*nMvpo>S+T*y)5s& zgxrWr?ceU54HB}D*^E9y#S6?FEkrKYmhSSv^s*yE)xT^~GtXg6a4ixb%!#?=^RAzw zszv(woUl}x!rA5T;4N1R0Zc43%}VpL?cYdYm^lw9l?(ldl{$s#Nf%xBk{yboOvuT_ zramT5Ww-y41pk~?RD$z|Qw-%hAtHVOq6(y&4J~idfE1;F{eFMvuVeqW|2NTj!HUX% cStP>%F1Fq<;f~yu3#Jz^)V-!ttA&mI9|UW 0) { + $.each($form.serializeArray(), function (index, field) { + formData.append(field.name, field.value) + }) + } + } + + FileUpload.prototype.removeFileFromElement = function($element) { + var self = this + + $element.each(function() { + var $el = $(this), + obj = $el.data('dzFileObject') + + if (obj) { + self.dropzone.removeFile(obj) + } + else { + $el.remove() + } + }) + } + + // + // User interaction + // + + FileUpload.prototype.onRemoveObject = function(ev) { + var self = this, + $object = $(ev.target).closest('.upload-object') + + $(ev.target) + .closest('.upload-remove-button') + .one('ajaxPromise', function(){ + $object.addClass('is-loading') + }) + .one('ajaxDone', function(){ + self.removeFileFromElement($object) + self.evalIsPopulated() + }) + .request() + + ev.stopPropagation() + } + + FileUpload.prototype.onClickSuccessObject = function(ev) { + // if ($(ev.target).closest('.meta').length) return + // + // var $target = $(ev.target).closest('.upload-object') + // window.open($target.data('path')) + } + + FileUpload.prototype.onClickErrorObject = function(ev) { + var + self = this, + $object = $(ev.target).closest('.upload-object'), + errorMsg = $('[data-dz-errormessage]', $object).text() + + alert(errorMsg) + + this.removeFileFromElement($object) + self.evalIsPopulated() + } + + // + // Helpers + // + + FileUpload.prototype.evalIsPopulated = function() { + var isPopulated = !!$('.upload-object', this.$filesContainer).length + this.$el.toggleClass('is-populated', isPopulated) + + // Reset maxFiles counter + if (!isPopulated) { + this.dropzone.removeAllFiles() + } + } + + FileUpload.DEFAULTS = { + url: window.location, + uniqueId: null, + extraData: {}, + paramName: 'file_data', + fileTypes: null, + template: null, + isMulti: null, + isPreview: null, + thumbnailWidth: 120, + thumbnailHeight: 120 + } + + // FILEUPLOAD PLUGIN DEFINITION + // ============================ + + var old = $.fn.fileUploader + + $.fn.fileUploader = function (option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('oc.fileUpload') + var options = $.extend({}, FileUpload.DEFAULTS, $this.data(), typeof option == 'object' && option) + if (!data) $this.data('oc.fileUpload', (data = new FileUpload(this, options))) + if (typeof option == 'string') data[option].call($this) + }) + } + + $.fn.fileUploader.Constructor = FileUpload + + // FILEUPLOAD NO CONFLICT + // ================= + + $.fn.fileUploader.noConflict = function () { + $.fn.fileUpload = old + return this + } + + // FILEUPLOAD DATA-API + // =============== + $(document).render(function () { + $('[data-control="fileupload"]').fileUploader() + }) + +}(window.jQuery); + + +var uploadDropZones = {}; + +$(document).on("uploadstarted", function(event) { + var frm = $(event.uploader.dropzone.element).parents('form'); + frm.find(':submit').prop('disabled', true); +}); + +var martin; +$(document).on("uploadfinished", function(event) { + var frm = $(event.uploader.dropzone.element).parents('form'); + frm.find(':submit').prop('disabled', false); + var dz = $(event.uploader.dropzone.element).data('unique-id'); + uploadDropZones[dz] = event; +}); \ No newline at end of file diff --git a/plugins/martin/forms/assets/less/uploader.base.less b/plugins/martin/forms/assets/less/uploader.base.less new file mode 100644 index 0000000..8ff530d --- /dev/null +++ b/plugins/martin/forms/assets/less/uploader.base.less @@ -0,0 +1,354 @@ +.uploader-object-active() { + background: @uploader-object-active-bg !important; + + i, p.size, p.error, .upload-remove-button { + color: #ecf0f1; + } + + h4 { + color: white; + } + + .icon-container { + border-right-color: @uploader-object-active-bg !important; + } + + &.is-error { + background: @uploader-object-error-bg !important; + } +} + +.uploader-progress-bar() { + display: block; + width: 100%; + overflow: hidden; + height: @uploader-progress-bar-height; + background-color: @uploader-progress-bar-bg; + border-radius: 2px; + box-shadow: inset 0 1px 2px rgba(0,0,0,.1); + + .upload-progress { + float: left; + width: 0%; + height: 100%; + line-height: @uploader-progress-bar-height; + color: @uploader-progress-bar-color; + background-color: #5fb6f5; + box-shadow: none; + transition: width .6s ease; + } +} + +.uploader-block-button() { + display: block; + float: left; + border: 2px dotted rgba(0,0,0,.1); + position: relative; + outline: none; + + .upload-button-icon { + position: absolute; + width: 22px; + height: 22px; + top: 50%; + left: 50%; + margin-top: -11px; + margin-left: -11px; + + &:before { + content: "+"; + text-align: center; + display: block; + font-size: 22px; + height: 22px; + width: 22px; + line-height: 22px; + color: rgba(0,0,0,.1); + font-weight: 700; + } + } + + &:hover { + border: 2px dotted rgba(0,0,0,.2); + + .upload-button-icon:before { + color: #5cb85c; + color: rgba(0,0,0,.2); + } + } + + &:focus { + border: 2px solid rgba(0,0,0,.3); + background: transparent; + + .upload-button-icon:before { + color: #5cb85c; + color: rgba(0,0,0,.2); + } + } +} + +.uploader-small-loader() { + width: 20px; + height: 20px; + margin-top: -10px; + margin-left: -10px; + background-size: 20px 20px; +} + +.uploader-vertical-align() { + position: absolute; + top: 50%; + margin-top: -44px; + height: 88px; +} + +// +// Shared +// + +.responsiv-uploader-fileupload { + + // Clearfix + &:after { + content: ""; + display: table; + clear: both; + } + + // + // Uploaded item + // + + .upload-object { + + border-radius: 3px; + position: relative; + outline: none; + overflow: hidden; + display: inline-block; + vertical-align: top; + + img { + width: 100%; + height: 100%; + } + + .icon-container { + display: table; + opacity: .6; + + i { + color: #95a5a6; + display: inline-block; + } + + div { + display: table-cell; + text-align: center; + vertical-align: middle; + } + } + + .icon-container.image { + > div.icon-wrapper { + display: none; + } + } + + h4 { + font-weight: 600; + font-size: 13px; + color: #2b3e50; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + line-height: 150%; + margin: 15px 0 5px 0; + padding-right: 0; + transition: padding 0.1s; + + position: relative; + + a { + position: absolute; + right: 0; + top: 0; + display: none; + font-weight: 400; + } + } + + p.size, p.error { + font-size: 12px; + color: #95a5a6; + strong { font-weight: 400; } + } + + p.error { + display: none; + color: #ab2a1c; + } + + .meta {} + + .info h4 a, + .meta a.upload-remove-button { + color: #2b3e50; + display: none; + font-size: 24px; + line-height: 16px; + text-decoration: none; + } + + } + + // + // Loading State + // + + .upload-object { + .icon-container { + position: relative; + } + + .icon-container:after { + background-image: url('../../../../../modules/system/assets/ui/images/loader-transparent.svg'); + position: absolute; + content: ' '; + width: 40px; + height: 40px; + left: 50%; + top: 50%; + margin-top: -20px; + margin-left: -20px; + display: block; + background-size: 40px 40px; + background-position: 50% 50%; + animation: spin 1s linear infinite; + } + + &.is-success { + .icon-container { + opacity: 1; + } + .icon-container:after { + opacity: 0; + transition: opacity .3s ease; + } + } + + &.is-loading { + .icon-container { + opacity: .6; + } + .icon-container:after { + opacity: 1; + transition: opacity .3s ease; + } + } + } + + // + // Success state + // + + .upload-object.is-success { + cursor: pointer; + + .progress-bar { + opacity: 0; + transition: opacity .3s ease; + } + + &:hover { + h4 a, + .meta .upload-remove-button { display: block; } + } + } + + // + // Error State + // + + .upload-object.is-error { + cursor: pointer; + + .progress-bar { + opacity: 0; + transition: opacity .3s ease; + } + + .icon-container { + opacity: 1; + > img, > i { + opacity: .5; + } + } + + .info h4 { + color: #ab2a1c; + } + + p.error { + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .info .upload-remove-button, + .meta .upload-remove-button { + display: block; + } + } + + // + // Preview mode + // + + &.is-preview { + .upload-button, + .upload-remove-button { + display: none !important; + } + } +} + +// +// Media +// + +@media (max-width: 1024px) { + .responsiv-uploader-fileupload { + .upload-object.is-success { + h4 a, + .meta .upload-remove-button { display: block !important; } + } + } +} + +// +// Spin animation +// + +@-moz-keyframes spin { + 0% { -moz-transform: rotate(0deg); } + 100% { -moz-transform: rotate(359deg); } +} +@-webkit-keyframes spin { + 0% { -webkit-transform: rotate(0deg); } + 100% { -webkit-transform: rotate(359deg); } +} +@-o-keyframes spin { + 0% { -o-transform: rotate(0deg); } + 100% { -o-transform: rotate(359deg); } +} +@-ms-keyframes spin { + 0% { -ms-transform: rotate(0deg); } + 100% { -ms-transform: rotate(359deg); } +} +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(359deg); } +} diff --git a/plugins/martin/forms/assets/less/uploader.filemulti.less b/plugins/martin/forms/assets/less/uploader.filemulti.less new file mode 100644 index 0000000..a237cd2 --- /dev/null +++ b/plugins/martin/forms/assets/less/uploader.filemulti.less @@ -0,0 +1,163 @@ +// +// Multi File +// + +.responsiv-uploader-fileupload.style-file-multi { + .upload-button { + margin-bottom: 10px; + } + + .upload-files-container { + border: 1px solid @uploader-list-border-color; + border-radius: 3px; + border-bottom: none; + display: none; + } + + &.is-populated .upload-files-container { + display: block; + } + + .upload-object { + display: block; + width: 100%; + border-bottom: 1px solid @uploader-list-border-color; + padding-left: 10px; + + &:nth-child(even) { + background-color: @uploader-list-accent-bg; + } + + .icon-container { + position: absolute; + top: 0; + left: 5px; + width: 35px; + padding: 11px 7px; + } + + .info { + margin-left: 35px; + margin-right: 15%; + + h4, p { + margin: 0; + padding: 11px 0; + font-size: 12px; + font-weight: normal; + line-height: 150%; + color: #666666; + } + + h4 { + padding-right: 15px; + + a { + padding: 10px 0; + right: 15px; + } + } + + p.size { + position: absolute; + top: 0; + right: 0; + width: 15%; + display: none; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + p.error { + color: #ab2a1c; + padding-top: 0; + } + } + + .progress-bar { + .uploader-progress-bar(); + position: absolute; + top: 18px; + left: 0; + } + + .meta { + position: absolute; + top: 0; + right: 0; + margin-right: 15px; + width: 15%; + + .upload-remove-button { + position: absolute; + top: -9px; + right: 0; + bottom: auto; + line-height: 150%; + padding: 10px 0; + z-index: 100; + } + } + + .icon-container:after { + .uploader-small-loader(); + } + + // + // Success + // + + &.is-success { + .info p.size { display: block; } + } + + // + // Hover + // + + &:hover { + .uploader-object-active(); + h4 { padding-right: 35px; } + } + } +} + +// +// Media +// + +@media (max-width: @screen-md-max) { + .responsiv-uploader-fileupload.style-file-multi { + .info { + margin-right: 20% !important; + p.size { + width: 20% !important; + } + } + + .meta { + width: 20% !important; + } + } +} + +@media (max-width: @screen-sm-max) { + .responsiv-uploader-fileupload.style-file-multi { + .upload-object { + h4 { padding-right: 35px !important; } + } + + .info { + margin-right: 25% !important; + p.size { + width: 25% !important; + padding-right: 35px !important; + } + } + + .meta { + width: 25% !important; + } + } +} \ No newline at end of file diff --git a/plugins/martin/forms/assets/less/uploader.filesingle.less b/plugins/martin/forms/assets/less/uploader.filesingle.less new file mode 100644 index 0000000..4a769bd --- /dev/null +++ b/plugins/martin/forms/assets/less/uploader.filesingle.less @@ -0,0 +1,112 @@ +// +// Single File +// + +.responsiv-uploader-fileupload.style-file-single { + background-color: #fff; + border: 1px solid #e0e0e0; + overflow: hidden; + position: relative; + padding-right: 11px; + + .upload-button { + .uploader-vertical-align(); + right: 0; + margin-right: 0; + } + + .upload-empty-message { + padding: 10px 0 10px 11px; + font-size: 13px; + } + + &.is-populated { + .upload-button, + .upload-empty-message { + display: none; + } + } + + .upload-object { + display: block; + width: 100%; + padding: 8px 0 10px 0; + + .icon-container { + position: absolute; + top: 0; + left: 0; + width: 35px; + padding: 0 5px; + margin: 8px 0 0 7px; + text-align: center; + } + + .info { + margin-left: 54px; + margin-right: 15%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + h4, p { + display: inline; + margin: 0; + padding: 0; + font-size: 12px; + line-height: 150%; + color: #666666; + } + + p.size { + font-weight: normal; + &:before { + content: " - "; + } + } + + p.error { + color: #ab2a1c; + padding-top: 0; + } + } + + .progress-bar { + .uploader-progress-bar(); + position: absolute; + top: 50%; + margin-top: -2px; + right: 5px; + } + + .meta { + .uploader-vertical-align(); + right: 0; + width: 15%; + .upload-remove-button { + position: absolute; + top: 50%; + right: 0; + height: 20px; + line-height: 20px; + margin-top: -10px; + margin-right: 10px; + z-index: 100; + } + } + + .icon-container:after { + .uploader-small-loader(); + } + + &.is-error { + .info { + p.size { display: none; } + p.error:before { + content: " - "; + } + } + } + } + +} \ No newline at end of file diff --git a/plugins/martin/forms/assets/less/uploader.imagemulti.less b/plugins/martin/forms/assets/less/uploader.imagemulti.less new file mode 100644 index 0000000..6607398 --- /dev/null +++ b/plugins/martin/forms/assets/less/uploader.imagemulti.less @@ -0,0 +1,117 @@ +// +// Multi Image +// + +.responsiv-uploader-fileupload.style-image-multi { + .upload-button, + .upload-object { + margin: 0 10px 10px 0; + } + + .upload-object { + background: #fff; + border: 1px solid #ecf0f1; + width: 260px; + + .progress-bar { + .uploader-progress-bar(); + position: absolute; + bottom: 10px; + left: 0; + } + + .icon-container { + border-right: 1px solid #f6f8f9; + float: left; + display: inline-block; + overflow: hidden; + width: 75px; + height: 75px; + + i { + font-size: 35px; + } + + &.image img { + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; + width: auto; + } + } + + .info { + margin-left: 90px; + + h4 { + padding-right: 15px; + a { + right: 15px; + } + } + } + + .meta { + position: absolute; + bottom: 0; + left: 0; + right: 0; + margin: 0 15px 0 90px; + } + + &.upload-placeholder { + height: 75px; + background-color: transparent; + &:after { opacity: 0; } + } + + &:hover { + .uploader-object-active(); + h4 { padding-right: 35px; } + } + + &.is-error { + h4 { padding-right: 35px; } + + .info { + p.size { display: none; } + p.error { padding-bottom: 11px; } + } + } + } + + &.is-preview { + .upload-files-container { + margin-left: 0; + } + } +} + +// +// Media +// + +@media (max-width: 1280px) { + .responsiv-uploader-fileupload.style-image-multi { + .upload-object { + width: 230px; + } + } +} + +@media (max-width: 1024px) { + .responsiv-uploader-fileupload.style-image-multi { + .upload-button { + width: 100%; + } + + .upload-files-container { + margin-left: 0; + } + + .upload-object { + margin-right: 0; + display: block; + width: auto; + } + } +} \ No newline at end of file diff --git a/plugins/martin/forms/assets/less/uploader.imagesingle.less b/plugins/martin/forms/assets/less/uploader.imagesingle.less new file mode 100644 index 0000000..5af618d --- /dev/null +++ b/plugins/martin/forms/assets/less/uploader.imagesingle.less @@ -0,0 +1,91 @@ +// +// Single Image +// + +.responsiv-uploader-fileupload.style-image-single { + &.is-populated { + .upload-button { + display: none; + } + } + + .upload-button { + .uploader-block-button(); + min-height: 100px; + min-width: 100px; + } + + .upload-object { + padding-bottom: 66px; + + .icon-container { + border: 1px solid #f6f8f9; + background: rgba(255,255,255,.5); + + &.image img { + border-radius: 3px; + + // Img responsive + display: block; + max-width: 100%; + height: auto; + + // This is needed when the image is very large and + // being processed by dropzone on the client-side + // the image has no height or width. + min-height: 100px; + min-width: 100px; + } + } + + .progress-bar { + .uploader-progress-bar(); + position: absolute; + bottom: 10px; + left: 0; + } + + .info { + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 66px; + } + + .meta { + position: absolute; + bottom: 65px; + left: 0; + right: 0; + margin: 0 15px; + } + + &:hover { + h4 { padding-right: 20px; } + } + + &.is-error { + h4 { padding-right: 20px; } + + .info { + p.size { display: none; } + p.error { padding-bottom: 11px; } + } + } + } + +} + + +// +// Media +// + +@media (max-width: 1024px) { + .responsiv-uploader-fileupload.style-image-single { + .upload-object { + h4 { padding-right: 20px !important; } + } + } +} \ No newline at end of file diff --git a/plugins/martin/forms/assets/less/uploader.less b/plugins/martin/forms/assets/less/uploader.less new file mode 100644 index 0000000..384bca3 --- /dev/null +++ b/plugins/martin/forms/assets/less/uploader.less @@ -0,0 +1,19 @@ +@uploader-progress-bar-height: 5px; +@uploader-progress-bar-color: #fff; +@uploader-progress-bar-bg: #f5f5f5; +@uploader-inactive-icon: #808b93; +@uploader-object-active-bg: #4da7e8; +@uploader-object-error-bg: #ab2a1c; +@uploader-list-accent-bg: #f5f5f5; +@uploader-list-border-color: #eeeeee; +@uploader-inline-button-color: #333333; + +@screen-xs-max: 767px; +@screen-sm-max: 991px; +@screen-md-max: 1199px; + +@import "uploader.base.less"; +@import "uploader.imagemulti.less"; +@import "uploader.imagesingle.less"; +@import "uploader.filemulti.less"; +@import "uploader.filesingle.less"; diff --git a/plugins/martin/forms/assets/vendor/dropzone/dropzone.js b/plugins/martin/forms/assets/vendor/dropzone/dropzone.js new file mode 100644 index 0000000..cd7855f --- /dev/null +++ b/plugins/martin/forms/assets/vendor/dropzone/dropzone.js @@ -0,0 +1,1752 @@ + +/* + * + * More info at [www.dropzonejs.com](http://www.dropzonejs.com) + * + * Copyright (c) 2012, Matias Meno + * + * 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. + * + */ + +(function() { + var Dropzone, Emitter, camelize, contentLoaded, detectVerticalSquash, drawImageIOSFix, noop, without, + __slice = [].slice, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + noop = function() {}; + + Emitter = (function() { + function Emitter() {} + + Emitter.prototype.addEventListener = Emitter.prototype.on; + + Emitter.prototype.on = function(event, fn) { + this._callbacks = this._callbacks || {}; + if (!this._callbacks[event]) { + this._callbacks[event] = []; + } + this._callbacks[event].push(fn); + return this; + }; + + Emitter.prototype.emit = function() { + var args, callback, callbacks, event, _i, _len; + event = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + this._callbacks = this._callbacks || {}; + callbacks = this._callbacks[event]; + if (callbacks) { + for (_i = 0, _len = callbacks.length; _i < _len; _i++) { + callback = callbacks[_i]; + callback.apply(this, args); + } + } + return this; + }; + + Emitter.prototype.removeListener = Emitter.prototype.off; + + Emitter.prototype.removeAllListeners = Emitter.prototype.off; + + Emitter.prototype.removeEventListener = Emitter.prototype.off; + + Emitter.prototype.off = function(event, fn) { + var callback, callbacks, i, _i, _len; + if (!this._callbacks || arguments.length === 0) { + this._callbacks = {}; + return this; + } + callbacks = this._callbacks[event]; + if (!callbacks) { + return this; + } + if (arguments.length === 1) { + delete this._callbacks[event]; + return this; + } + for (i = _i = 0, _len = callbacks.length; _i < _len; i = ++_i) { + callback = callbacks[i]; + if (callback === fn) { + callbacks.splice(i, 1); + break; + } + } + return this; + }; + + return Emitter; + + })(); + + Dropzone = (function(_super) { + var extend, resolveOption; + + __extends(Dropzone, _super); + + Dropzone.prototype.Emitter = Emitter; + + + /* + This is a list of all available events you can register on a dropzone object. + + You can register an event handler like this: + + dropzone.on("dragEnter", function() { }); + */ + + Dropzone.prototype.events = ["drop", "dragstart", "dragend", "dragenter", "dragover", "dragleave", "addedfile", "addedfiles", "removedfile", "thumbnail", "error", "errormultiple", "processing", "processingmultiple", "uploadprogress", "totaluploadprogress", "sending", "sendingmultiple", "success", "successmultiple", "canceled", "canceledmultiple", "complete", "completemultiple", "reset", "maxfilesexceeded", "maxfilesreached", "queuecomplete"]; + + Dropzone.prototype.defaultOptions = { + url: null, + method: "post", + withCredentials: false, + parallelUploads: 2, + uploadMultiple: false, + maxFilesize: 256, + paramName: "file", + createImageThumbnails: true, + maxThumbnailFilesize: 10, + thumbnailWidth: 120, + thumbnailHeight: 120, + filesizeBase: 1000, + maxFiles: null, + params: {}, + clickable: true, + ignoreHiddenFiles: true, + acceptedFiles: null, + acceptedMimeTypes: null, + autoProcessQueue: true, + autoQueue: true, + addRemoveLinks: false, + previewsContainer: null, + hiddenInputContainer: "body", + capture: null, + dictDefaultMessage: "Drop files here to upload", + dictFallbackMessage: "Your browser does not support drag'n'drop file uploads.", + dictFallbackText: "Please use the fallback form below to upload your files like in the olden days.", + dictFileTooBig: "File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.", + dictInvalidFileType: "You can't upload files of this type.", + dictResponseError: "Server responded with {{statusCode}} code.", + dictCancelUpload: "Cancel upload", + dictCancelUploadConfirmation: "Are you sure you want to cancel this upload?", + dictRemoveFile: "Remove file", + dictRemoveFileConfirmation: null, + dictMaxFilesExceeded: "You can not upload any more files.", + accept: function(file, done) { + return done(); + }, + init: function() { + return noop; + }, + forceFallback: false, + fallback: function() { + var child, messageElement, span, _i, _len, _ref; + this.element.className = "" + this.element.className + " dz-browser-not-supported"; + _ref = this.element.getElementsByTagName("div"); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + child = _ref[_i]; + if (/(^| )dz-message($| )/.test(child.className)) { + messageElement = child; + child.className = "dz-message"; + continue; + } + } + if (!messageElement) { + messageElement = Dropzone.createElement("
"); + this.element.appendChild(messageElement); + } + span = messageElement.getElementsByTagName("span")[0]; + if (span) { + if (span.textContent != null) { + span.textContent = this.options.dictFallbackMessage; + } else if (span.innerText != null) { + span.innerText = this.options.dictFallbackMessage; + } + } + return this.element.appendChild(this.getFallbackForm()); + }, + resize: function(file) { + var info, srcRatio, trgRatio; + info = { + srcX: 0, + srcY: 0, + srcWidth: file.width, + srcHeight: file.height + }; + srcRatio = file.width / file.height; + info.optWidth = this.options.thumbnailWidth; + info.optHeight = this.options.thumbnailHeight; + if ((info.optWidth == null) && (info.optHeight == null)) { + info.optWidth = info.srcWidth; + info.optHeight = info.srcHeight; + } else if (info.optWidth == null) { + info.optWidth = srcRatio * info.optHeight; + } else if (info.optHeight == null) { + info.optHeight = (1 / srcRatio) * info.optWidth; + } + trgRatio = info.optWidth / info.optHeight; + if (file.height < info.optHeight || file.width < info.optWidth) { + info.trgHeight = info.srcHeight; + info.trgWidth = info.srcWidth; + } else { + if (srcRatio > trgRatio) { + info.srcHeight = file.height; + info.srcWidth = info.srcHeight * trgRatio; + } else { + info.srcWidth = file.width; + info.srcHeight = info.srcWidth / trgRatio; + } + } + info.srcX = (file.width - info.srcWidth) / 2; + info.srcY = (file.height - info.srcHeight) / 2; + return info; + }, + + /* + Those functions register themselves to the events on init and handle all + the user interface specific stuff. Overwriting them won't break the upload + but can break the way it's displayed. + You can overwrite them if you don't like the default behavior. If you just + want to add an additional event handler, register it on the dropzone object + and don't overwrite those options. + */ + drop: function(e) { + return this.element.classList.remove("dz-drag-hover"); + }, + dragstart: noop, + dragend: function(e) { + return this.element.classList.remove("dz-drag-hover"); + }, + dragenter: function(e) { + return this.element.classList.add("dz-drag-hover"); + }, + dragover: function(e) { + return this.element.classList.add("dz-drag-hover"); + }, + dragleave: function(e) { + return this.element.classList.remove("dz-drag-hover"); + }, + paste: noop, + reset: function() { + return this.element.classList.remove("dz-started"); + }, + addedfile: function(file) { + var node, removeFileEvent, removeLink, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _results; + if (this.element === this.previewsContainer) { + this.element.classList.add("dz-started"); + } + if (this.previewsContainer) { + file.previewElement = Dropzone.createElement(this.options.previewTemplate.trim()); + file.previewTemplate = file.previewElement; + this.previewsContainer.appendChild(file.previewElement); + _ref = file.previewElement.querySelectorAll("[data-dz-name]"); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + node = _ref[_i]; + node.textContent = file.name; + } + _ref1 = file.previewElement.querySelectorAll("[data-dz-size]"); + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + node = _ref1[_j]; + node.innerHTML = this.filesize(file.size); + } + if (this.options.addRemoveLinks) { + file._removeLink = Dropzone.createElement("" + this.options.dictRemoveFile + ""); + file.previewElement.appendChild(file._removeLink); + } + removeFileEvent = (function(_this) { + return function(e) { + e.preventDefault(); + e.stopPropagation(); + if (file.status === Dropzone.UPLOADING) { + return Dropzone.confirm(_this.options.dictCancelUploadConfirmation, function() { + return _this.removeFile(file); + }); + } else { + if (_this.options.dictRemoveFileConfirmation) { + return Dropzone.confirm(_this.options.dictRemoveFileConfirmation, function() { + return _this.removeFile(file); + }); + } else { + return _this.removeFile(file); + } + } + }; + })(this); + _ref2 = file.previewElement.querySelectorAll("[data-dz-remove]"); + _results = []; + for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { + removeLink = _ref2[_k]; + _results.push(removeLink.addEventListener("click", removeFileEvent)); + } + return _results; + } + }, + removedfile: function(file) { + var _ref; + if (file.previewElement) { + if ((_ref = file.previewElement) != null) { + _ref.parentNode.removeChild(file.previewElement); + } + } + return this._updateMaxFilesReachedClass(); + }, + thumbnail: function(file, dataUrl) { + var thumbnailElement, _i, _len, _ref; + if (file.previewElement) { + file.previewElement.classList.remove("dz-file-preview"); + _ref = file.previewElement.querySelectorAll("[data-dz-thumbnail]"); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + thumbnailElement = _ref[_i]; + thumbnailElement.alt = file.name; + thumbnailElement.src = dataUrl; + } + return setTimeout(((function(_this) { + return function() { + return file.previewElement.classList.add("dz-image-preview"); + }; + })(this)), 1); + } + }, + error: function(file, message) { + var node, _i, _len, _ref, _results; + if (file.previewElement) { + file.previewElement.classList.add("dz-error"); + if (typeof message !== "String" && message.error) { + message = message.error; + } + _ref = file.previewElement.querySelectorAll("[data-dz-errormessage]"); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + node = _ref[_i]; + _results.push(node.textContent = message); + } + return _results; + } + }, + errormultiple: noop, + processing: function(file) { + if (file.previewElement) { + file.previewElement.classList.add("dz-processing"); + if (file._removeLink) { + return file._removeLink.textContent = this.options.dictCancelUpload; + } + } + }, + processingmultiple: noop, + uploadprogress: function(file, progress, bytesSent) { + var node, _i, _len, _ref, _results; + if (file.previewElement) { + _ref = file.previewElement.querySelectorAll("[data-dz-uploadprogress]"); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + node = _ref[_i]; + if (node.nodeName === 'PROGRESS') { + _results.push(node.value = progress); + } else { + _results.push(node.style.width = "" + progress + "%"); + } + } + return _results; + } + }, + totaluploadprogress: noop, + sending: noop, + sendingmultiple: noop, + success: function(file) { + if (file.previewElement) { + return file.previewElement.classList.add("dz-success"); + } + }, + successmultiple: noop, + canceled: function(file) { + return this.emit("error", file, "Upload canceled."); + }, + canceledmultiple: noop, + complete: function(file) { + if (file._removeLink) { + file._removeLink.textContent = this.options.dictRemoveFile; + } + if (file.previewElement) { + return file.previewElement.classList.add("dz-complete"); + } + }, + completemultiple: noop, + maxfilesexceeded: noop, + maxfilesreached: noop, + queuecomplete: noop, + addedfiles: noop, + previewTemplate: "
\n
\n
\n
\n
\n
\n
\n
\n
\n \n Check\n \n \n \n \n \n
\n
\n \n Error\n \n \n \n \n \n \n \n
\n
" + }; + + extend = function() { + var key, object, objects, target, val, _i, _len; + target = arguments[0], objects = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + for (_i = 0, _len = objects.length; _i < _len; _i++) { + object = objects[_i]; + for (key in object) { + val = object[key]; + target[key] = val; + } + } + return target; + }; + + function Dropzone(element, options) { + var elementOptions, fallback, _ref; + this.element = element; + this.version = Dropzone.version; + this.defaultOptions.previewTemplate = this.defaultOptions.previewTemplate.replace(/\n*/g, ""); + this.clickableElements = []; + this.listeners = []; + this.files = []; + if (typeof this.element === "string") { + this.element = document.querySelector(this.element); + } + if (!(this.element && (this.element.nodeType != null))) { + throw new Error("Invalid dropzone element."); + } + if (this.element.dropzone) { + throw new Error("Dropzone already attached."); + } + Dropzone.instances.push(this); + this.element.dropzone = this; + elementOptions = (_ref = Dropzone.optionsForElement(this.element)) != null ? _ref : {}; + this.options = extend({}, this.defaultOptions, elementOptions, options != null ? options : {}); + if (this.options.forceFallback || !Dropzone.isBrowserSupported()) { + return this.options.fallback.call(this); + } + if (this.options.url == null) { + this.options.url = this.element.getAttribute("action"); + } + if (!this.options.url) { + throw new Error("No URL provided."); + } + if (this.options.acceptedFiles && this.options.acceptedMimeTypes) { + throw new Error("You can't provide both 'acceptedFiles' and 'acceptedMimeTypes'. 'acceptedMimeTypes' is deprecated."); + } + if (this.options.acceptedMimeTypes) { + this.options.acceptedFiles = this.options.acceptedMimeTypes; + delete this.options.acceptedMimeTypes; + } + this.options.method = this.options.method.toUpperCase(); + if ((fallback = this.getExistingFallback()) && fallback.parentNode) { + fallback.parentNode.removeChild(fallback); + } + if (this.options.previewsContainer !== false) { + if (this.options.previewsContainer) { + this.previewsContainer = Dropzone.getElement(this.options.previewsContainer, "previewsContainer"); + } else { + this.previewsContainer = this.element; + } + } + if (this.options.clickable) { + if (this.options.clickable === true) { + this.clickableElements = [this.element]; + } else { + this.clickableElements = Dropzone.getElements(this.options.clickable, "clickable"); + } + } + this.init(); + } + + Dropzone.prototype.getAcceptedFiles = function() { + var file, _i, _len, _ref, _results; + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.accepted) { + _results.push(file); + } + } + return _results; + }; + + Dropzone.prototype.getRejectedFiles = function() { + var file, _i, _len, _ref, _results; + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (!file.accepted) { + _results.push(file); + } + } + return _results; + }; + + Dropzone.prototype.getFilesWithStatus = function(status) { + var file, _i, _len, _ref, _results; + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.status === status) { + _results.push(file); + } + } + return _results; + }; + + Dropzone.prototype.getQueuedFiles = function() { + return this.getFilesWithStatus(Dropzone.QUEUED); + }; + + Dropzone.prototype.getUploadingFiles = function() { + return this.getFilesWithStatus(Dropzone.UPLOADING); + }; + + Dropzone.prototype.getAddedFiles = function() { + return this.getFilesWithStatus(Dropzone.ADDED); + }; + + Dropzone.prototype.getActiveFiles = function() { + var file, _i, _len, _ref, _results; + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.status === Dropzone.UPLOADING || file.status === Dropzone.QUEUED) { + _results.push(file); + } + } + return _results; + }; + + Dropzone.prototype.init = function() { + var eventName, noPropagation, setupHiddenFileInput, _i, _len, _ref, _ref1; + if (this.element.tagName === "form") { + this.element.setAttribute("enctype", "multipart/form-data"); + } + if (this.element.classList.contains("dropzone") && !this.element.querySelector(".dz-message")) { + this.element.appendChild(Dropzone.createElement("
" + this.options.dictDefaultMessage + "
")); + } + if (this.clickableElements.length) { + setupHiddenFileInput = (function(_this) { + return function() { + if (_this.hiddenFileInput) { + _this.hiddenFileInput.parentNode.removeChild(_this.hiddenFileInput); + } + _this.hiddenFileInput = document.createElement("input"); + _this.hiddenFileInput.setAttribute("type", "file"); + if ((_this.options.maxFiles == null) || _this.options.maxFiles > 1) { + _this.hiddenFileInput.setAttribute("multiple", "multiple"); + } + _this.hiddenFileInput.className = "dz-hidden-input"; + if (_this.options.acceptedFiles != null) { + _this.hiddenFileInput.setAttribute("accept", _this.options.acceptedFiles); + } + if (_this.options.capture != null) { + _this.hiddenFileInput.setAttribute("capture", _this.options.capture); + } + _this.hiddenFileInput.style.visibility = "hidden"; + _this.hiddenFileInput.style.position = "absolute"; + _this.hiddenFileInput.style.top = "0"; + _this.hiddenFileInput.style.left = "0"; + _this.hiddenFileInput.style.height = "0"; + _this.hiddenFileInput.style.width = "0"; + document.querySelector(_this.options.hiddenInputContainer).appendChild(_this.hiddenFileInput); + return _this.hiddenFileInput.addEventListener("change", function() { + var file, files, _i, _len; + files = _this.hiddenFileInput.files; + if (files.length) { + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + _this.addFile(file); + } + } + _this.emit("addedfiles", files); + return setupHiddenFileInput(); + }); + }; + })(this); + setupHiddenFileInput(); + } + this.URL = (_ref = window.URL) != null ? _ref : window.webkitURL; + _ref1 = this.events; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + eventName = _ref1[_i]; + this.on(eventName, this.options[eventName]); + } + this.on("uploadprogress", (function(_this) { + return function() { + return _this.updateTotalUploadProgress(); + }; + })(this)); + this.on("removedfile", (function(_this) { + return function() { + return _this.updateTotalUploadProgress(); + }; + })(this)); + this.on("canceled", (function(_this) { + return function(file) { + return _this.emit("complete", file); + }; + })(this)); + this.on("complete", (function(_this) { + return function(file) { + if (_this.getAddedFiles().length === 0 && _this.getUploadingFiles().length === 0 && _this.getQueuedFiles().length === 0) { + return setTimeout((function() { + return _this.emit("queuecomplete"); + }), 0); + } + }; + })(this)); + noPropagation = function(e) { + e.stopPropagation(); + if (e.preventDefault) { + return e.preventDefault(); + } else { + return e.returnValue = false; + } + }; + this.listeners = [ + { + element: this.element, + events: { + "dragstart": (function(_this) { + return function(e) { + return _this.emit("dragstart", e); + }; + })(this), + "dragenter": (function(_this) { + return function(e) { + noPropagation(e); + return _this.emit("dragenter", e); + }; + })(this), + "dragover": (function(_this) { + return function(e) { + var efct; + try { + efct = e.dataTransfer.effectAllowed; + } catch (_error) {} + e.dataTransfer.dropEffect = 'move' === efct || 'linkMove' === efct ? 'move' : 'copy'; + noPropagation(e); + return _this.emit("dragover", e); + }; + })(this), + "dragleave": (function(_this) { + return function(e) { + return _this.emit("dragleave", e); + }; + })(this), + "drop": (function(_this) { + return function(e) { + noPropagation(e); + return _this.drop(e); + }; + })(this), + "dragend": (function(_this) { + return function(e) { + return _this.emit("dragend", e); + }; + })(this) + } + } + ]; + this.clickableElements.forEach((function(_this) { + return function(clickableElement) { + return _this.listeners.push({ + element: clickableElement, + events: { + "click": function(evt) { + if ((clickableElement !== _this.element) || (evt.target === _this.element || Dropzone.elementInside(evt.target, _this.element.querySelector(".dz-message")))) { + _this.hiddenFileInput.click(); + } + return true; + } + } + }); + }; + })(this)); + this.enable(); + return this.options.init.call(this); + }; + + Dropzone.prototype.destroy = function() { + var _ref; + this.disable(); + this.removeAllFiles(true); + if ((_ref = this.hiddenFileInput) != null ? _ref.parentNode : void 0) { + this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput); + this.hiddenFileInput = null; + } + delete this.element.dropzone; + return Dropzone.instances.splice(Dropzone.instances.indexOf(this), 1); + }; + + Dropzone.prototype.updateTotalUploadProgress = function() { + var activeFiles, file, totalBytes, totalBytesSent, totalUploadProgress, _i, _len, _ref; + totalBytesSent = 0; + totalBytes = 0; + activeFiles = this.getActiveFiles(); + if (activeFiles.length) { + _ref = this.getActiveFiles(); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + totalBytesSent += file.upload.bytesSent; + totalBytes += file.upload.total; + } + totalUploadProgress = 100 * totalBytesSent / totalBytes; + } else { + totalUploadProgress = 100; + } + return this.emit("totaluploadprogress", totalUploadProgress, totalBytes, totalBytesSent); + }; + + Dropzone.prototype._getParamName = function(n) { + if (typeof this.options.paramName === "function") { + return this.options.paramName(n); + } else { + return "" + this.options.paramName + (this.options.uploadMultiple ? "[" + n + "]" : ""); + } + }; + + Dropzone.prototype.getFallbackForm = function() { + var existingFallback, fields, fieldsString, form; + if (existingFallback = this.getExistingFallback()) { + return existingFallback; + } + fieldsString = "
"; + if (this.options.dictFallbackText) { + fieldsString += "

" + this.options.dictFallbackText + "

"; + } + fieldsString += "
"; + fields = Dropzone.createElement(fieldsString); + if (this.element.tagName !== "FORM") { + form = Dropzone.createElement("
"); + form.appendChild(fields); + } else { + this.element.setAttribute("enctype", "multipart/form-data"); + this.element.setAttribute("method", this.options.method); + } + return form != null ? form : fields; + }; + + Dropzone.prototype.getExistingFallback = function() { + var fallback, getFallback, tagName, _i, _len, _ref; + getFallback = function(elements) { + var el, _i, _len; + for (_i = 0, _len = elements.length; _i < _len; _i++) { + el = elements[_i]; + if (/(^| )fallback($| )/.test(el.className)) { + return el; + } + } + }; + _ref = ["div", "form"]; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + tagName = _ref[_i]; + if (fallback = getFallback(this.element.getElementsByTagName(tagName))) { + return fallback; + } + } + }; + + Dropzone.prototype.setupEventListeners = function() { + var elementListeners, event, listener, _i, _len, _ref, _results; + _ref = this.listeners; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + elementListeners = _ref[_i]; + _results.push((function() { + var _ref1, _results1; + _ref1 = elementListeners.events; + _results1 = []; + for (event in _ref1) { + listener = _ref1[event]; + _results1.push(elementListeners.element.addEventListener(event, listener, false)); + } + return _results1; + })()); + } + return _results; + }; + + Dropzone.prototype.removeEventListeners = function() { + var elementListeners, event, listener, _i, _len, _ref, _results; + _ref = this.listeners; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + elementListeners = _ref[_i]; + _results.push((function() { + var _ref1, _results1; + _ref1 = elementListeners.events; + _results1 = []; + for (event in _ref1) { + listener = _ref1[event]; + _results1.push(elementListeners.element.removeEventListener(event, listener, false)); + } + return _results1; + })()); + } + return _results; + }; + + Dropzone.prototype.disable = function() { + var file, _i, _len, _ref, _results; + this.clickableElements.forEach(function(element) { + return element.classList.remove("dz-clickable"); + }); + this.removeEventListeners(); + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + _results.push(this.cancelUpload(file)); + } + return _results; + }; + + Dropzone.prototype.enable = function() { + this.clickableElements.forEach(function(element) { + return element.classList.add("dz-clickable"); + }); + return this.setupEventListeners(); + }; + + Dropzone.prototype.filesize = function(size) { + var cutoff, i, selectedSize, selectedUnit, unit, units, _i, _len; + selectedSize = 0; + selectedUnit = "b"; + if (size > 0) { + units = ['TB', 'GB', 'MB', 'KB', 'b']; + for (i = _i = 0, _len = units.length; _i < _len; i = ++_i) { + unit = units[i]; + cutoff = Math.pow(this.options.filesizeBase, 4 - i) / 10; + if (size >= cutoff) { + selectedSize = size / Math.pow(this.options.filesizeBase, 4 - i); + selectedUnit = unit; + break; + } + } + selectedSize = Math.round(10 * selectedSize) / 10; + } + return "" + selectedSize + " " + selectedUnit; + }; + + Dropzone.prototype._updateMaxFilesReachedClass = function() { + if ((this.options.maxFiles != null) && this.getAcceptedFiles().length >= this.options.maxFiles) { + if (this.getAcceptedFiles().length === this.options.maxFiles) { + this.emit('maxfilesreached', this.files); + } + return this.element.classList.add("dz-max-files-reached"); + } else { + return this.element.classList.remove("dz-max-files-reached"); + } + }; + + Dropzone.prototype.drop = function(e) { + var files, items; + if (!e.dataTransfer) { + return; + } + this.emit("drop", e); + files = e.dataTransfer.files; + this.emit("addedfiles", files); + if (files.length) { + items = e.dataTransfer.items; + if (items && items.length && (items[0].webkitGetAsEntry != null)) { + this._addFilesFromItems(items); + } else { + this.handleFiles(files); + } + } + }; + + Dropzone.prototype.paste = function(e) { + var items, _ref; + if ((e != null ? (_ref = e.clipboardData) != null ? _ref.items : void 0 : void 0) == null) { + return; + } + this.emit("paste", e); + items = e.clipboardData.items; + if (items.length) { + return this._addFilesFromItems(items); + } + }; + + Dropzone.prototype.handleFiles = function(files) { + var file, _i, _len, _results; + _results = []; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + _results.push(this.addFile(file)); + } + return _results; + }; + + Dropzone.prototype._addFilesFromItems = function(items) { + var entry, item, _i, _len, _results; + _results = []; + for (_i = 0, _len = items.length; _i < _len; _i++) { + item = items[_i]; + if ((item.webkitGetAsEntry != null) && (entry = item.webkitGetAsEntry())) { + if (entry.isFile) { + _results.push(this.addFile(item.getAsFile())); + } else if (entry.isDirectory) { + _results.push(this._addFilesFromDirectory(entry, entry.name)); + } else { + _results.push(void 0); + } + } else if (item.getAsFile != null) { + if ((item.kind == null) || item.kind === "file") { + _results.push(this.addFile(item.getAsFile())); + } else { + _results.push(void 0); + } + } else { + _results.push(void 0); + } + } + return _results; + }; + + Dropzone.prototype._addFilesFromDirectory = function(directory, path) { + var dirReader, entriesReader; + dirReader = directory.createReader(); + entriesReader = (function(_this) { + return function(entries) { + var entry, _i, _len; + for (_i = 0, _len = entries.length; _i < _len; _i++) { + entry = entries[_i]; + if (entry.isFile) { + entry.file(function(file) { + if (_this.options.ignoreHiddenFiles && file.name.substring(0, 1) === '.') { + return; + } + file.fullPath = "" + path + "/" + file.name; + return _this.addFile(file); + }); + } else if (entry.isDirectory) { + _this._addFilesFromDirectory(entry, "" + path + "/" + entry.name); + } + } + }; + })(this); + return dirReader.readEntries(entriesReader, function(error) { + return typeof console !== "undefined" && console !== null ? typeof console.log === "function" ? console.log(error) : void 0 : void 0; + }); + }; + + Dropzone.prototype.accept = function(file, done) { + if (file.size > this.options.maxFilesize * 1024 * 1024) { + return done(this.options.dictFileTooBig.replace("{{filesize}}", Math.round(file.size / 1024 / 10.24) / 100).replace("{{maxFilesize}}", this.options.maxFilesize)); + } else if (!Dropzone.isValidFile(file, this.options.acceptedFiles)) { + return done(this.options.dictInvalidFileType); + } else if ((this.options.maxFiles != null) && this.getAcceptedFiles().length >= this.options.maxFiles) { + done(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}", this.options.maxFiles)); + return this.emit("maxfilesexceeded", file); + } else { + return this.options.accept.call(this, file, done); + } + }; + + Dropzone.prototype.addFile = function(file) { + file.upload = { + progress: 0, + total: file.size, + bytesSent: 0 + }; + this.files.push(file); + file.status = Dropzone.ADDED; + this.emit("addedfile", file); + this._enqueueThumbnail(file); + return this.accept(file, (function(_this) { + return function(error) { + if (error) { + file.accepted = false; + _this._errorProcessing([file], error); + } else { + file.accepted = true; + if (_this.options.autoQueue) { + _this.enqueueFile(file); + } + } + return _this._updateMaxFilesReachedClass(); + }; + })(this)); + }; + + Dropzone.prototype.enqueueFiles = function(files) { + var file, _i, _len; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + this.enqueueFile(file); + } + return null; + }; + + Dropzone.prototype.enqueueFile = function(file) { + if (file.status === Dropzone.ADDED && file.accepted === true) { + file.status = Dropzone.QUEUED; + if (this.options.autoProcessQueue) { + return setTimeout(((function(_this) { + return function() { + return _this.processQueue(); + }; + })(this)), 0); + } + } else { + throw new Error("This file can't be queued because it has already been processed or was rejected."); + } + }; + + Dropzone.prototype._thumbnailQueue = []; + + Dropzone.prototype._processingThumbnail = false; + + Dropzone.prototype._enqueueThumbnail = function(file) { + if (this.options.createImageThumbnails && file.type.match(/image.*/) && file.size <= this.options.maxThumbnailFilesize * 1024 * 1024) { + this._thumbnailQueue.push(file); + return setTimeout(((function(_this) { + return function() { + return _this._processThumbnailQueue(); + }; + })(this)), 0); + } + }; + + Dropzone.prototype._processThumbnailQueue = function() { + if (this._processingThumbnail || this._thumbnailQueue.length === 0) { + return; + } + this._processingThumbnail = true; + return this.createThumbnail(this._thumbnailQueue.shift(), (function(_this) { + return function() { + _this._processingThumbnail = false; + return _this._processThumbnailQueue(); + }; + })(this)); + }; + + Dropzone.prototype.removeFile = function(file) { + if (file.status === Dropzone.UPLOADING) { + this.cancelUpload(file); + } + this.files = without(this.files, file); + this.emit("removedfile", file); + if (this.files.length === 0) { + return this.emit("reset"); + } + }; + + Dropzone.prototype.removeAllFiles = function(cancelIfNecessary) { + var file, _i, _len, _ref; + if (cancelIfNecessary == null) { + cancelIfNecessary = false; + } + _ref = this.files.slice(); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.status !== Dropzone.UPLOADING || cancelIfNecessary) { + this.removeFile(file); + } + } + return null; + }; + + Dropzone.prototype.createThumbnail = function(file, callback) { + var fileReader; + fileReader = new FileReader; + fileReader.onload = (function(_this) { + return function() { + if (file.type === "image/svg+xml") { + _this.emit("thumbnail", file, fileReader.result); + if (callback != null) { + callback(); + } + return; + } + return _this.createThumbnailFromUrl(file, fileReader.result, callback); + }; + })(this); + return fileReader.readAsDataURL(file); + }; + + Dropzone.prototype.createThumbnailFromUrl = function(file, imageUrl, callback, crossOrigin) { + var img; + img = document.createElement("img"); + if (crossOrigin) { + img.crossOrigin = crossOrigin; + } + img.onload = (function(_this) { + return function() { + var canvas, ctx, resizeInfo, thumbnail, _ref, _ref1, _ref2, _ref3; + file.width = img.width; + file.height = img.height; + resizeInfo = _this.options.resize.call(_this, file); + if (resizeInfo.trgWidth == null) { + resizeInfo.trgWidth = resizeInfo.optWidth; + } + if (resizeInfo.trgHeight == null) { + resizeInfo.trgHeight = resizeInfo.optHeight; + } + canvas = document.createElement("canvas"); + ctx = canvas.getContext("2d"); + canvas.width = resizeInfo.trgWidth; + canvas.height = resizeInfo.trgHeight; + drawImageIOSFix(ctx, img, (_ref = resizeInfo.srcX) != null ? _ref : 0, (_ref1 = resizeInfo.srcY) != null ? _ref1 : 0, resizeInfo.srcWidth, resizeInfo.srcHeight, (_ref2 = resizeInfo.trgX) != null ? _ref2 : 0, (_ref3 = resizeInfo.trgY) != null ? _ref3 : 0, resizeInfo.trgWidth, resizeInfo.trgHeight); + thumbnail = canvas.toDataURL("image/png"); + _this.emit("thumbnail", file, thumbnail); + if (callback != null) { + return callback(); + } + }; + })(this); + if (callback != null) { + img.onerror = callback; + } + return img.src = imageUrl; + }; + + Dropzone.prototype.processQueue = function() { + var i, parallelUploads, processingLength, queuedFiles; + parallelUploads = this.options.parallelUploads; + processingLength = this.getUploadingFiles().length; + i = processingLength; + if (processingLength >= parallelUploads) { + return; + } + queuedFiles = this.getQueuedFiles(); + if (!(queuedFiles.length > 0)) { + return; + } + if (this.options.uploadMultiple) { + return this.processFiles(queuedFiles.slice(0, parallelUploads - processingLength)); + } else { + while (i < parallelUploads) { + if (!queuedFiles.length) { + return; + } + this.processFile(queuedFiles.shift()); + i++; + } + } + }; + + Dropzone.prototype.processFile = function(file) { + return this.processFiles([file]); + }; + + Dropzone.prototype.processFiles = function(files) { + var file, _i, _len; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + file.processing = true; + file.status = Dropzone.UPLOADING; + this.emit("processing", file); + } + if (this.options.uploadMultiple) { + this.emit("processingmultiple", files); + } + return this.uploadFiles(files); + }; + + Dropzone.prototype._getFilesWithXhr = function(xhr) { + var file, files; + return files = (function() { + var _i, _len, _ref, _results; + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.xhr === xhr) { + _results.push(file); + } + } + return _results; + }).call(this); + }; + + Dropzone.prototype.cancelUpload = function(file) { + var groupedFile, groupedFiles, _i, _j, _len, _len1, _ref; + if (file.status === Dropzone.UPLOADING) { + groupedFiles = this._getFilesWithXhr(file.xhr); + for (_i = 0, _len = groupedFiles.length; _i < _len; _i++) { + groupedFile = groupedFiles[_i]; + groupedFile.status = Dropzone.CANCELED; + } + file.xhr.abort(); + for (_j = 0, _len1 = groupedFiles.length; _j < _len1; _j++) { + groupedFile = groupedFiles[_j]; + this.emit("canceled", groupedFile); + } + if (this.options.uploadMultiple) { + this.emit("canceledmultiple", groupedFiles); + } + } else if ((_ref = file.status) === Dropzone.ADDED || _ref === Dropzone.QUEUED) { + file.status = Dropzone.CANCELED; + this.emit("canceled", file); + if (this.options.uploadMultiple) { + this.emit("canceledmultiple", [file]); + } + } + if (this.options.autoProcessQueue) { + return this.processQueue(); + } + }; + + resolveOption = function() { + var args, option; + option = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + if (typeof option === 'function') { + return option.apply(this, args); + } + return option; + }; + + Dropzone.prototype.uploadFile = function(file) { + return this.uploadFiles([file]); + }; + + Dropzone.prototype.uploadFiles = function(files) { + var file, formData, handleError, headerName, headerValue, headers, i, input, inputName, inputType, key, method, option, progressObj, response, updateProgress, url, value, xhr, _i, _j, _k, _l, _len, _len1, _len2, _len3, _m, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; + xhr = new XMLHttpRequest(); + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + file.xhr = xhr; + } + method = resolveOption(this.options.method, files); + url = resolveOption(this.options.url, files); + xhr.open(method, url, true); + xhr.withCredentials = !!this.options.withCredentials; + response = null; + handleError = (function(_this) { + return function() { + var _j, _len1, _results; + _results = []; + for (_j = 0, _len1 = files.length; _j < _len1; _j++) { + file = files[_j]; + _results.push(_this._errorProcessing(files, response || _this.options.dictResponseError.replace("{{statusCode}}", xhr.status), xhr)); + } + return _results; + }; + })(this); + updateProgress = (function(_this) { + return function(e) { + var allFilesFinished, progress, _j, _k, _l, _len1, _len2, _len3, _results; + if (e != null) { + progress = 100 * e.loaded / e.total; + for (_j = 0, _len1 = files.length; _j < _len1; _j++) { + file = files[_j]; + file.upload = { + progress: progress, + total: e.total, + bytesSent: e.loaded + }; + } + } else { + allFilesFinished = true; + progress = 100; + for (_k = 0, _len2 = files.length; _k < _len2; _k++) { + file = files[_k]; + if (!(file.upload.progress === 100 && file.upload.bytesSent === file.upload.total)) { + allFilesFinished = false; + } + file.upload.progress = progress; + file.upload.bytesSent = file.upload.total; + } + if (allFilesFinished) { + return; + } + } + _results = []; + for (_l = 0, _len3 = files.length; _l < _len3; _l++) { + file = files[_l]; + _results.push(_this.emit("uploadprogress", file, progress, file.upload.bytesSent)); + } + return _results; + }; + })(this); + xhr.onload = (function(_this) { + return function(e) { + var _ref; + if (files[0].status === Dropzone.CANCELED) { + return; + } + if (xhr.readyState !== 4) { + return; + } + response = xhr.responseText; + if (xhr.getResponseHeader("content-type") && ~xhr.getResponseHeader("content-type").indexOf("application/json")) { + try { + response = JSON.parse(response); + } catch (_error) { + e = _error; + response = "Invalid JSON response from server."; + } + } + updateProgress(); + if (!((200 <= (_ref = xhr.status) && _ref < 300))) { + return handleError(); + } else { + return _this._finished(files, response, e); + } + }; + })(this); + xhr.onerror = (function(_this) { + return function() { + if (files[0].status === Dropzone.CANCELED) { + return; + } + return handleError(); + }; + })(this); + progressObj = (_ref = xhr.upload) != null ? _ref : xhr; + progressObj.onprogress = updateProgress; + headers = { + "Accept": "application/json", + "Cache-Control": "no-cache", + "X-Requested-With": "XMLHttpRequest" + }; + if (this.options.headers) { + extend(headers, this.options.headers); + } + for (headerName in headers) { + headerValue = headers[headerName]; + if (headerValue) { + xhr.setRequestHeader(headerName, headerValue); + } + } + formData = new FormData(); + if (this.options.params) { + _ref1 = this.options.params; + for (key in _ref1) { + value = _ref1[key]; + formData.append(key, value); + } + } + for (_j = 0, _len1 = files.length; _j < _len1; _j++) { + file = files[_j]; + this.emit("sending", file, xhr, formData); + } + if (this.options.uploadMultiple) { + this.emit("sendingmultiple", files, xhr, formData); + } + if (this.element.tagName === "FORM") { + _ref2 = this.element.querySelectorAll("input, textarea, select, button"); + for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { + input = _ref2[_k]; + inputName = input.getAttribute("name"); + inputType = input.getAttribute("type"); + if (input.tagName === "SELECT" && input.hasAttribute("multiple")) { + _ref3 = input.options; + for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { + option = _ref3[_l]; + if (option.selected) { + formData.append(inputName, option.value); + } + } + } else if (!inputType || ((_ref4 = inputType.toLowerCase()) !== "checkbox" && _ref4 !== "radio") || input.checked) { + formData.append(inputName, input.value); + } + } + } + for (i = _m = 0, _ref5 = files.length - 1; 0 <= _ref5 ? _m <= _ref5 : _m >= _ref5; i = 0 <= _ref5 ? ++_m : --_m) { + formData.append(this._getParamName(i), files[i], files[i].name); + } + return this.submitRequest(xhr, formData, files); + }; + + Dropzone.prototype.submitRequest = function(xhr, formData, files) { + return xhr.send(formData); + }; + + Dropzone.prototype._finished = function(files, responseText, e) { + var file, _i, _len; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + file.status = Dropzone.SUCCESS; + this.emit("success", file, responseText, e); + this.emit("complete", file); + } + if (this.options.uploadMultiple) { + this.emit("successmultiple", files, responseText, e); + this.emit("completemultiple", files); + } + if (this.options.autoProcessQueue) { + return this.processQueue(); + } + }; + + Dropzone.prototype._errorProcessing = function(files, message, xhr) { + var file, _i, _len; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + file.status = Dropzone.ERROR; + this.emit("error", file, message, xhr); + this.emit("complete", file); + } + if (this.options.uploadMultiple) { + this.emit("errormultiple", files, message, xhr); + this.emit("completemultiple", files); + } + if (this.options.autoProcessQueue) { + return this.processQueue(); + } + }; + + return Dropzone; + + })(Emitter); + + Dropzone.version = "4.2.0"; + + Dropzone.options = {}; + + Dropzone.optionsForElement = function(element) { + if (element.getAttribute("id")) { + return Dropzone.options[camelize(element.getAttribute("id"))]; + } else { + return void 0; + } + }; + + Dropzone.instances = []; + + Dropzone.forElement = function(element) { + if (typeof element === "string") { + element = document.querySelector(element); + } + if ((element != null ? element.dropzone : void 0) == null) { + throw new Error("No Dropzone found for given element. This is probably because you're trying to access it before Dropzone had the time to initialize. Use the `init` option to setup any additional observers on your Dropzone."); + } + return element.dropzone; + }; + + Dropzone.autoDiscover = true; + + Dropzone.discover = function() { + var checkElements, dropzone, dropzones, _i, _len, _results; + if (document.querySelectorAll) { + dropzones = document.querySelectorAll(".dropzone"); + } else { + dropzones = []; + checkElements = function(elements) { + var el, _i, _len, _results; + _results = []; + for (_i = 0, _len = elements.length; _i < _len; _i++) { + el = elements[_i]; + if (/(^| )dropzone($| )/.test(el.className)) { + _results.push(dropzones.push(el)); + } else { + _results.push(void 0); + } + } + return _results; + }; + checkElements(document.getElementsByTagName("div")); + checkElements(document.getElementsByTagName("form")); + } + _results = []; + for (_i = 0, _len = dropzones.length; _i < _len; _i++) { + dropzone = dropzones[_i]; + if (Dropzone.optionsForElement(dropzone) !== false) { + _results.push(new Dropzone(dropzone)); + } else { + _results.push(void 0); + } + } + return _results; + }; + + Dropzone.blacklistedBrowsers = [/opera.*Macintosh.*version\/12/i]; + + Dropzone.isBrowserSupported = function() { + var capableBrowser, regex, _i, _len, _ref; + capableBrowser = true; + if (window.File && window.FileReader && window.FileList && window.Blob && window.FormData && document.querySelector) { + if (!("classList" in document.createElement("a"))) { + capableBrowser = false; + } else { + _ref = Dropzone.blacklistedBrowsers; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + regex = _ref[_i]; + if (regex.test(navigator.userAgent)) { + capableBrowser = false; + continue; + } + } + } + } else { + capableBrowser = false; + } + return capableBrowser; + }; + + without = function(list, rejectedItem) { + var item, _i, _len, _results; + _results = []; + for (_i = 0, _len = list.length; _i < _len; _i++) { + item = list[_i]; + if (item !== rejectedItem) { + _results.push(item); + } + } + return _results; + }; + + camelize = function(str) { + return str.replace(/[\-_](\w)/g, function(match) { + return match.charAt(1).toUpperCase(); + }); + }; + + Dropzone.createElement = function(string) { + var div; + div = document.createElement("div"); + div.innerHTML = string; + return div.childNodes[0]; + }; + + Dropzone.elementInside = function(element, container) { + if (element === container) { + return true; + } + while (element = element.parentNode) { + if (element === container) { + return true; + } + } + return false; + }; + + Dropzone.getElement = function(el, name) { + var element; + if (typeof el === "string") { + element = document.querySelector(el); + } else if (el.nodeType != null) { + element = el; + } + if (element == null) { + throw new Error("Invalid `" + name + "` option provided. Please provide a CSS selector or a plain HTML element."); + } + return element; + }; + + Dropzone.getElements = function(els, name) { + var e, el, elements, _i, _j, _len, _len1, _ref; + if (els instanceof Array) { + elements = []; + try { + for (_i = 0, _len = els.length; _i < _len; _i++) { + el = els[_i]; + elements.push(this.getElement(el, name)); + } + } catch (_error) { + e = _error; + elements = null; + } + } else if (typeof els === "string") { + elements = []; + _ref = document.querySelectorAll(els); + for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { + el = _ref[_j]; + elements.push(el); + } + } else if (els.nodeType != null) { + elements = [els]; + } + if (!((elements != null) && elements.length)) { + throw new Error("Invalid `" + name + "` option provided. Please provide a CSS selector, a plain HTML element or a list of those."); + } + return elements; + }; + + Dropzone.confirm = function(question, accepted, rejected) { + if (window.confirm(question)) { + return accepted(); + } else if (rejected != null) { + return rejected(); + } + }; + + Dropzone.isValidFile = function(file, acceptedFiles) { + var baseMimeType, mimeType, validType, _i, _len; + if (!acceptedFiles) { + return true; + } + acceptedFiles = acceptedFiles.split(","); + mimeType = file.type; + baseMimeType = mimeType.replace(/\/.*$/, ""); + for (_i = 0, _len = acceptedFiles.length; _i < _len; _i++) { + validType = acceptedFiles[_i]; + validType = validType.trim(); + if (validType.charAt(0) === ".") { + if (file.name.toLowerCase().indexOf(validType.toLowerCase(), file.name.length - validType.length) !== -1) { + return true; + } + } else if (/\/\*$/.test(validType)) { + if (baseMimeType === validType.replace(/\/.*$/, "")) { + return true; + } + } else { + if (mimeType === validType) { + return true; + } + } + } + return false; + }; + + if (typeof jQuery !== "undefined" && jQuery !== null) { + jQuery.fn.dropzone = function(options) { + return this.each(function() { + return new Dropzone(this, options); + }); + }; + } + + if (typeof module !== "undefined" && module !== null) { + module.exports = Dropzone; + } else { + window.Dropzone = Dropzone; + } + + Dropzone.ADDED = "added"; + + Dropzone.QUEUED = "queued"; + + Dropzone.ACCEPTED = Dropzone.QUEUED; + + Dropzone.UPLOADING = "uploading"; + + Dropzone.PROCESSING = Dropzone.UPLOADING; + + Dropzone.CANCELED = "canceled"; + + Dropzone.ERROR = "error"; + + Dropzone.SUCCESS = "success"; + + + /* + + Bugfix for iOS 6 and 7 + Source: http://stackoverflow.com/questions/11929099/html5-canvas-drawimage-ratio-bug-ios + based on the work of https://github.com/stomita/ios-imagefile-megapixel + */ + + detectVerticalSquash = function(img) { + var alpha, canvas, ctx, data, ey, ih, iw, py, ratio, sy; + iw = img.naturalWidth; + ih = img.naturalHeight; + canvas = document.createElement("canvas"); + canvas.width = 1; + canvas.height = ih; + ctx = canvas.getContext("2d"); + ctx.drawImage(img, 0, 0); + data = ctx.getImageData(0, 0, 1, ih).data; + sy = 0; + ey = ih; + py = ih; + while (py > sy) { + alpha = data[(py - 1) * 4 + 3]; + if (alpha === 0) { + ey = py; + } else { + sy = py; + } + py = (ey + sy) >> 1; + } + ratio = py / ih; + if (ratio === 0) { + return 1; + } else { + return ratio; + } + }; + + drawImageIOSFix = function(ctx, img, sx, sy, sw, sh, dx, dy, dw, dh) { + var vertSquashRatio; + vertSquashRatio = detectVerticalSquash(img); + return ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh / vertSquashRatio); + }; + + + /* + * contentloaded.js + * + * Author: Diego Perini (diego.perini at gmail.com) + * Summary: cross-browser wrapper for DOMContentLoaded + * Updated: 20101020 + * License: MIT + * Version: 1.2 + * + * URL: + * http://javascript.nwbox.com/ContentLoaded/ + * http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE + */ + + contentLoaded = function(win, fn) { + var add, doc, done, init, poll, pre, rem, root, top; + done = false; + top = true; + doc = win.document; + root = doc.documentElement; + add = (doc.addEventListener ? "addEventListener" : "attachEvent"); + rem = (doc.addEventListener ? "removeEventListener" : "detachEvent"); + pre = (doc.addEventListener ? "" : "on"); + init = function(e) { + if (e.type === "readystatechange" && doc.readyState !== "complete") { + return; + } + (e.type === "load" ? win : doc)[rem](pre + e.type, init, false); + if (!done && (done = true)) { + return fn.call(win, e.type || e); + } + }; + poll = function() { + var e; + try { + root.doScroll("left"); + } catch (_error) { + e = _error; + setTimeout(poll, 50); + return; + } + return init("poll"); + }; + if (doc.readyState !== "complete") { + if (doc.createEventObject && root.doScroll) { + try { + top = !win.frameElement; + } catch (_error) {} + if (top) { + poll(); + } + } + doc[add](pre + "DOMContentLoaded", init, false); + doc[add](pre + "readystatechange", init, false); + return win[add](pre + "load", init, false); + } + }; + + Dropzone._autoDiscoverFunction = function() { + if (Dropzone.autoDiscover) { + return Dropzone.discover(); + } + }; + + contentLoaded(window, Dropzone._autoDiscoverFunction); + +}).call(this); diff --git a/plugins/martin/forms/classes/BackendHelpers.php b/plugins/martin/forms/classes/BackendHelpers.php new file mode 100644 index 0000000..f461dc0 --- /dev/null +++ b/plugins/martin/forms/classes/BackendHelpers.php @@ -0,0 +1,87 @@ + $URL) { + if ($user->hasAccess($permission)) { + return Backend::url($URL); + } + } + return Backend::url($urls[$default]); + } + + /** + * Check if Translator plugin is installed + * + * @return boolean + */ + public static function isTranslatePlugin() :bool { + return class_exists('\RainLab\Translate\Classes\Translator') && class_exists('\RainLab\Translate\Models\Message'); + } + + /** + * Render an array|object as HTML list (UL > LI) + * + * @param mixed $data List items + * + * @return string + */ + public static function array2ul($data) :string { + $return = ''; + foreach ($data as $index => $item) { + if (!is_string($item)) { + $return .= '
  • ' . htmlspecialchars($index, ENT_QUOTES) . '
      ' . self::array2ul($item) . "
  • "; + } else { + $return .= '
  • '; + if (is_object($data)) { + $return .= htmlspecialchars($index, ENT_QUOTES) . ' - '; + } + $return .= htmlspecialchars($item, ENT_QUOTES) .'
  • '; + } + } + return $return; + } + + /** + * Anonymize an IPv4 address + * (credits: https://github.com/geertw/php-ip-anonymizer) + * + * @param string $address IPv4 address + * + * @return string Anonymized address + */ + public static function anonymizeIPv4(string $address) :string { + return inet_ntop(inet_pton($address) & inet_pton("255.255.255.0")); + } + + /** + * Extract string from curly braces + * + * @param string $pattern Pattern to replace + * @param string $replacement Replacement string + * @param string $subject Strings to replace + * + * @return string + */ + public static function replaceToken(string $pattern, string $replacement = null, string $subject) :string { + $pattern = '/{{\s*('.$pattern.')\s*}}/'; + return preg_replace($pattern, $replacement, $subject); + } + +} + +?> \ No newline at end of file diff --git a/plugins/martin/forms/classes/GDPR.php b/plugins/martin/forms/classes/GDPR.php new file mode 100644 index 0000000..455dec2 --- /dev/null +++ b/plugins/martin/forms/classes/GDPR.php @@ -0,0 +1,37 @@ +subDays($gdpr_days); + $rows = Record::whereDate('created_at', '<', $days)->forceDelete(); + return $rows; + } + + Flash::error(e(trans('martin.forms::lang.classes.GDPR.alert_invalid_gdpr'))); + + } + +} + +?> \ No newline at end of file diff --git a/plugins/martin/forms/classes/MagicForm.php b/plugins/martin/forms/classes/MagicForm.php new file mode 100644 index 0000000..4fa1a99 --- /dev/null +++ b/plugins/martin/forms/classes/MagicForm.php @@ -0,0 +1,308 @@ +page['recaptcha_enabled'] = $this->isReCaptchaEnabled(); + $this->page['recaptcha_misconfigured'] = $this->isReCaptchaMisconfigured(); + + if ($this->isReCaptchaEnabled()) { + $this->loadReCaptcha(); + } + + if ($this->isReCaptchaMisconfigured()) { + $this->page['recaptcha_warn'] = Lang::get('martin.forms::lang.components.shared.recaptcha_warn'); + } + + if ($this->property('inline_errors') == 'display') { + $this->addJs('assets/js/inline-errors.js'); + } + + } + + public function settings() { + return [ + 'recaptcha_site_key' => Settings::get('recaptcha_site_key'), + 'recaptcha_secret_key' => Settings::get('recaptcha_secret_key'), + ]; + } + + public function onFormSubmit() { + + // FLASH PARTIAL + $flash_partial = $this->property('messages_partial', '@flash.htm'); + + // CSRF CHECK + if (Config::get('cms.enableCsrfProtection') && (Session::token() != post('_token'))) { + throw new AjaxException(['#' . $this->alias . '_forms_flash' => $this->renderPartial($flash_partial, [ + 'status' => 'error', + 'type' => 'danger', + 'content' => Lang::get('martin.forms::lang.components.shared.csrf_error'), + ])]); + } + + // LOAD TRANSLATOR PLUGIN + if (BackendHelpers::isTranslatePlugin()) { + $translator = \RainLab\Translate\Classes\Translator::instance(); + $translator->loadLocaleFromSession(); + $locale = $translator->getLocale(); + \RainLab\Translate\Models\Message::setContext($locale); + } + + // FILTER ALLOWED FIELDS + $allow = $this->property('allowed_fields'); + if (is_array($allow) && !empty($allow)) { + foreach ($allow as $field) { + $post[$field] = post($field); + } + if ($this->isReCaptchaEnabled()) { + $post['g-recaptcha-response'] = post('g-recaptcha-response'); + } + } else { + $post = post(); + } + + // SANITIZE FORM DATA + if ($this->property('sanitize_data') == 'htmlspecialchars') { + $post = $this->array_map_recursive(function ($value) { + return htmlspecialchars($value, ENT_QUOTES); + }, $post); + } + + // VALIDATION PARAMETERS + $rules = (array)$this->property('rules'); + $msgs = (array)$this->property('rules_messages'); + $custom_attributes = (array)$this->property('custom_attributes'); + + // TRANSLATE CUSTOM ERROR MESSAGES + if (BackendHelpers::isTranslatePlugin()) { + foreach ($msgs as $rule => $msg) { + $msgs[$rule] = \RainLab\Translate\Models\Message::trans($msg); + } + } + + // ADD reCAPTCHA VALIDATION + if ($this->isReCaptchaEnabled() && $this->property('recaptcha_size') != 'invisible') { + $rules['g-recaptcha-response'] = 'required'; + } + + // DO FORM VALIDATION + $validator = Validator::make($post, $rules, $msgs, $custom_attributes); + + // NICE reCAPTCHA FIELD NAME + if ($this->isReCaptchaEnabled()) { + $fields_names = ['g-recaptcha-response' => 'reCAPTCHA']; + $validator->setAttributeNames(array_merge($fields_names, $custom_attributes)); + } + + // VALIDATE ALL + CAPTCHA EXISTS + if ($validator->fails()) { + + // GET DEFAULT ERROR MESSAGE + $message = $this->property('messages_errors'); + + // LOOK FOR TRANSLATION + if (BackendHelpers::isTranslatePlugin()) { + $message = \RainLab\Translate\Models\Message::trans($message); + } + + // THROW ERRORS + if ($this->property('inline_errors') == 'display') { + throw new ValidationException($validator); + } else { + throw new AjaxException($this->_exceptionResponse($validator, [ + 'status' => 'error', + 'type' => 'danger', + 'title' => $message, + 'list' => $validator->messages()->all(), + 'errors' => json_encode($validator->messages()->messages()), + 'jscript' => $this->property('js_on_error'), + ])); + } + + } + + // IF FIRST VALIDATION IS OK, VALIDATE CAPTCHA vs GOOGLE + // (this prevents to resolve captcha after every form error) + if ($this->isReCaptchaEnabled()) { + + // PREPARE RECAPTCHA VALIDATION + $rules = ['g-recaptcha-response' => 'recaptcha']; + $err_msg = ['g-recaptcha-response.recaptcha' => Lang::get('martin.forms::lang.validation.recaptcha_error')]; + + // DO SECOND VALIDATION + $validator = Validator::make($post, $rules, $err_msg); + + // VALIDATE ALL + CAPTCHA EXISTS + if ($validator->fails()) { + + // THROW ERRORS + if ($this->property('inline_errors') == 'display') { + throw new ValidationException($validator); + } else { + throw new AjaxException($this->_exceptionResponse($validator, [ + 'status' => 'error', + 'type' => 'danger', + 'content' => Lang::get('martin.forms::lang.validation.recaptcha_error'), + 'errors' => json_encode($validator->messages()->messages()), + 'jscript' => $this->property('js_on_error'), + ])); + } + + } + + } + + // REMOVE EXTRA FIELDS FROM STORED DATA + unset($post['_token'], $post['g-recaptcha-response'], $post['_session_key'], $post['_uploader']); + + // FIRE BEFORE SAVE EVENT + Event::fire('martin.forms.beforeSaveRecord', [&$post, $this]); + + if (count($custom_attributes)) { + $post = collect($post)->mapWithKeys(function ($val, $key) use ($custom_attributes) { + return [array_get($custom_attributes, $key, $key) => $val]; + })->all(); + } + + $record = new Record; + $record->ip = $this->getIP(); + $record->created_at = date('Y-m-d H:i:s'); + + // SAVE RECORD TO DATABASE + if (! $this->property('skip_database')) { + $record->form_data = json_encode($post, JSON_UNESCAPED_UNICODE); + if ($this->property('group') != '') { + $record->group = $this->property('group'); + } + $record->save(null, post('_session_key')); + } + + // SEND NOTIFICATION EMAIL + if ($this->property('mail_enabled')) { + SendMail::sendNotification($this->getProperties(), $post, $record, $record->files); + } + + // SEND AUTORESPONSE EMAIL + if ($this->property('mail_resp_enabled')) { + SendMail::sendAutoResponse($this->getProperties(), $post, $record); + } + + // FIRE AFTER SAVE EVENT + Event::fire('martin.forms.afterSaveRecord', [&$post, $this, $record]); + + // CHECK FOR REDIRECT + if ($this->property('redirect')) { + return Redirect::to($this->property('redirect')); + } + + // GET DEFAULT SUCCESS MESSAGE + $message = $this->property('messages_success'); + + // LOOK FOR TRANSLATION + if (BackendHelpers::isTranslatePlugin()) { + $message = \RainLab\Translate\Models\Message::trans($message); + } + + // DISPLAY SUCCESS MESSAGE + return ['#' . $this->alias . '_forms_flash' => $this->renderPartial($flash_partial, [ + 'status' => 'success', + 'type' => 'success', + 'content' => $message, + 'jscript' => $this->prepareJavaScript(), + ])]; + + } + + private function _exceptionResponse($validator, $params) { + + // FLASH PARTIAL + $flash_partial = $this->property('messages_partial', '@flash.htm'); + + // EXCEPTION RESPONSE + $response = ['#' . $this->alias . '_forms_flash' => $this->renderPartial($flash_partial, $params)]; + + // INCLUDE ERROR FIELDS IF REQUIRED + if ($this->property('inline_errors') != 'disabled') { + $response['error_fields'] = $validator->messages(); + } + + return $response; + + } + + private function prepareJavaScript() + { + $code = false; + + /* SUCCESS JS */ + if ($this->property('js_on_success') != '') { + $code .= $this->property('js_on_success'); + } + + /* RECAPTCHA JS */ + if ($this->isReCaptchaEnabled()) { + $code .= $this->renderPartial('@js/recaptcha.htm'); + } + + /* RESET FORM JS */ + if ($this->property('reset_form')) { + $params = ['id' => '#' . $this->alias . '_forms_flash']; + $code .= $this->renderPartial('@js/reset-form.htm', $params); + } + + /* RESET UPLOAD FORM */ + if ($this->property('reset_form') && $this->property('uploader_enable')) { + $params = ['id' => $this->alias]; + $code .= $this->renderPartial('@js/reset-uploader.htm', $params); + } + + return $code; + } + + private function getIP() + { + if ($this->property('anonymize_ip') == 'full') { + return '(Not stored)'; + } + + $ip = Request::getClientIp(); + + if ($this->property('anonymize_ip') == 'partial') { + return BackendHelpers::anonymizeIPv4($ip); + } + + return $ip; + } + + private function array_map_recursive($callback, $array) + { + $func = function ($item) use (&$func, &$callback) { + return is_array($item) ? array_map($func, $item) : call_user_func($callback, $item); + }; + + return array_map($func, $array); + } +} diff --git a/plugins/martin/forms/classes/ReCaptcha.php b/plugins/martin/forms/classes/ReCaptcha.php new file mode 100644 index 0000000..8aba6a8 --- /dev/null +++ b/plugins/martin/forms/classes/ReCaptcha.php @@ -0,0 +1,52 @@ +translator = Translator::instance(); + } + } + + private function isReCaptchaEnabled() { + return ($this->property('recaptcha_enabled') && Settings::get('recaptcha_site_key') != '' && Settings::get('recaptcha_secret_key') != ''); + } + + private function isReCaptchaMisconfigured() { + return ($this->property('recaptcha_enabled') && (Settings::get('recaptcha_site_key') == '' || Settings::get('recaptcha_secret_key') == '')); + } + + private function getReCaptchaLang($lang='') { + if (BackendHelpers::isTranslatePlugin()) { + $lang = '&hl=' . $this->activeLocale = $this->translator->getLocale(); + } else { + $lang = '&hl=' . $this->activeLocale = app()->getLocale(); + } + return $lang; + } + + private function loadReCaptcha() { + $this->addJs('https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit'.$this->getReCaptchaLang(), ['async', 'defer']); + $this->addJs('assets/js/recaptcha.js'); + } + +} + +?> diff --git a/plugins/martin/forms/classes/ReCaptchaValidator.php b/plugins/martin/forms/classes/ReCaptchaValidator.php new file mode 100644 index 0000000..36654ef --- /dev/null +++ b/plugins/martin/forms/classes/ReCaptchaValidator.php @@ -0,0 +1,21 @@ + \ No newline at end of file diff --git a/plugins/martin/forms/classes/SendMail.php b/plugins/martin/forms/classes/SendMail.php new file mode 100644 index 0000000..b7f2c50 --- /dev/null +++ b/plugins/martin/forms/classes/SendMail.php @@ -0,0 +1,146 @@ + $record->id, + 'data' => $post, + 'ip' => $record->ip, + 'date' => $record->created_at + ]; + + // CHECK FOR CUSTOM SUBJECT + if (isset($properties['mail_subject'])) { + + // set date format + $dateFormat = $properties['emails_date_format'] ?? 'Y-m-d'; + + // REPLACE RECORD TOKENS IN SUBJECT + $properties['mail_subject'] = BackendHelpers::replaceToken('record.id', $data['id'], $properties['mail_subject']); + $properties['mail_subject'] = BackendHelpers::replaceToken('record.ip', $data['ip'], $properties['mail_subject']); + $properties['mail_subject'] = BackendHelpers::replaceToken('record.date', date($dateFormat), $properties['mail_subject']); + + // REPLACE FORM FIELDS TOKENS IN SUBJECT + foreach ($data['data'] as $key => $value) { + if (!is_array($value)) { + $properties['mail_subject'] = BackendHelpers::replaceToken('form.'.$key, $value, $properties['mail_subject']); + } + } + + // SET CUSTOM SUBJECT + $data['subject'] = $properties['mail_subject']; + + } + + // SEND NOTIFICATION EMAIL + Mail::sendTo($properties['mail_recipients'], $template, $data, function ($message) use ($properties, $post, $files) { + + // SEND BLIND CARBON COPY + if (isset($properties['mail_bcc']) && is_array($properties['mail_bcc'])) { + $message->bcc($properties['mail_bcc']); + } + + // USE CUSTOM SUBJECT + if (isset($properties['mail_subject'])) { + $message->subject($properties['mail_subject']); + } + + // ADD REPLY TO ADDRESS + if (isset($properties['mail_replyto']) && isset($post[$properties['mail_replyto']])) { + $message->replyTo($post[$properties['mail_replyto']]); + } + + // ADD UPLOADS + if (isset($properties['mail_uploads']) && $properties['mail_uploads'] && !empty($files)) { + foreach ($files as $file) { + $message->attach($file->getLocalPath(), ['as' => $file->getFilename()]); + } + } + + }); + + } + + } + + public static function sendAutoResponse($properties, $post, $record) { + + $data = [ + 'id' => $record->id, + 'data' => $post, + 'ip' => $record->ip, + 'date' => $record->created_at + ]; + + // CHECK FOR CUSTOM SUBJECT + if (isset($properties['mail_resp_subject'])) { + + // set date format + $dateFormat = $properties['emails_date_format'] ?? 'Y-m-d'; + + // REPLACE RECORD TOKENS IN SUBJECT + $properties['mail_resp_subject'] = BackendHelpers::replaceToken('record.id', $data['id'], $properties['mail_resp_subject']); + $properties['mail_resp_subject'] = BackendHelpers::replaceToken('record.ip', $data['ip'], $properties['mail_resp_subject']); + $properties['mail_resp_subject'] = BackendHelpers::replaceToken('record.date', date($dateFormat), $properties['mail_resp_subject']); + + // REPLACE FORM FIELDS TOKENS IN SUBJECT + foreach ($data['data'] as $key => $value) { + if (!is_array($value)) { + $properties['mail_resp_subject'] = BackendHelpers::replaceToken('form.'.$key, $value, $properties['mail_resp_subject']); + } + } + } + + $response = isset($properties['mail_resp_field']) ? $properties['mail_resp_field'] : null; + $to = isset($post[$response]) ? $post[$response] : null; + $from = isset($properties['mail_resp_from']) ? $properties['mail_resp_from'] : null; + $subject = isset($properties['mail_resp_subject']) ? $properties['mail_resp_subject'] : null; + + if (filter_var($to, FILTER_VALIDATE_EMAIL) && filter_var($from, FILTER_VALIDATE_EMAIL)) { + + // CUSTOM TEMPLATE + $template = isset($properties['mail_resp_template']) && $properties['mail_resp_template'] != '' && MailTemplate::findOrMakeTemplate($properties['mail_resp_template']) ? $properties['mail_resp_template'] : 'martin.forms::mail.autoresponse'; + + Mail::sendTo($to, $template, [ + 'id' => $record->id, + 'data' => $post, + 'ip' => $record->ip, + 'date' => $record->created_at + ], function ($message) use ($from, $subject) { + $message->from($from); + if (isset($subject)) { + $message->subject($subject); + } + } + ); + + } + + } + +} + +?> diff --git a/plugins/martin/forms/classes/SharedProperties.php b/plugins/martin/forms/classes/SharedProperties.php new file mode 100644 index 0000000..31e1710 --- /dev/null +++ b/plugins/martin/forms/classes/SharedProperties.php @@ -0,0 +1,259 @@ + [ + 'title' => 'martin.forms::lang.components.shared.group.title', + 'description' => 'martin.forms::lang.components.shared.group.description', + 'type' => 'string', + 'showExternalParam' => false, + ], + 'rules' => [ + 'title' => 'martin.forms::lang.components.shared.rules.title', + 'description' => 'martin.forms::lang.components.shared.rules.description', + 'type' => 'dictionary', + 'group' => 'martin.forms::lang.components.shared.group_validation', + 'showExternalParam' => false, + ], + 'rules_messages' => [ + 'title' => 'martin.forms::lang.components.shared.rules_messages.title', + 'description' => 'martin.forms::lang.components.shared.rules_messages.description', + 'type' => 'dictionary', + 'group' => 'martin.forms::lang.components.shared.group_validation', + 'showExternalParam' => false, + ], + 'custom_attributes' => [ + 'title' => 'martin.forms::lang.components.shared.custom_attributes.title', + 'description' => 'martin.forms::lang.components.shared.custom_attributes.description', + 'type' => 'dictionary', + 'group' => 'martin.forms::lang.components.shared.group_validation', + 'showExternalParam' => false, + ], + 'messages_success' => [ + 'title' => 'martin.forms::lang.components.shared.messages_success.title', + 'description' => 'martin.forms::lang.components.shared.messages_success.description', + 'type' => 'string', + 'group' => 'martin.forms::lang.components.shared.group_messages', + 'default' => Lang::get('martin.forms::lang.components.shared.messages_success.default'), + 'showExternalParam' => false, + 'validation' => ['required' => ['message' => Lang::get('martin.forms::lang.components.shared.validation_req')]] + ], + 'messages_errors' => [ + 'title' => 'martin.forms::lang.components.shared.messages_errors.title', + 'description' => 'martin.forms::lang.components.shared.messages_errors.description', + 'type' => 'string', + 'group' => 'martin.forms::lang.components.shared.group_messages', + 'default' => Lang::get('martin.forms::lang.components.shared.messages_errors.default'), + 'showExternalParam' => false, + 'validation' => ['required' => ['message' => Lang::get('martin.forms::lang.components.shared.validation_req')]] + ], + 'messages_partial' => [ + 'title' => 'martin.forms::lang.components.shared.messages_partial.title', + 'description' => 'martin.forms::lang.components.shared.messages_partial.description', + 'type' => 'string', + 'group' => 'martin.forms::lang.components.shared.group_messages', + 'showExternalParam' => false + ], + 'mail_enabled' => [ + 'title' => 'martin.forms::lang.components.shared.mail_enabled.title', + 'description' => 'martin.forms::lang.components.shared.mail_enabled.description', + 'type' => 'checkbox', + 'group' => 'martin.forms::lang.components.shared.group_mail', + 'showExternalParam' => false + ], + 'mail_subject' => [ + 'title' => 'martin.forms::lang.components.shared.mail_subject.title', + 'description' => 'martin.forms::lang.components.shared.mail_subject.description', + 'type' => 'string', + 'group' => 'martin.forms::lang.components.shared.group_mail', + 'showExternalParam' => false + ], + 'mail_recipients' => [ + 'title' => 'martin.forms::lang.components.shared.mail_recipients.title', + 'description' => 'martin.forms::lang.components.shared.mail_recipients.description', + 'type' => 'stringList', + 'group' => 'martin.forms::lang.components.shared.group_mail', + 'showExternalParam' => false + ], + 'mail_bcc' => [ + 'title' => 'martin.forms::lang.components.shared.mail_bcc.title', + 'description' => 'martin.forms::lang.components.shared.mail_bcc.description', + 'type' => 'stringList', + 'group' => 'martin.forms::lang.components.shared.group_mail', + 'showExternalParam' => false + ], + 'mail_replyto' => [ + 'title' => 'martin.forms::lang.components.shared.mail_replyto.title', + 'description' => 'martin.forms::lang.components.shared.mail_replyto.description', + 'type' => 'string', + 'group' => 'martin.forms::lang.components.shared.group_mail', + 'showExternalParam' => false + ], + 'mail_template' => [ + 'title' => 'martin.forms::lang.components.shared.mail_template.title', + 'description' => 'martin.forms::lang.components.shared.mail_template.description', + 'type' => 'string', + 'group' => 'martin.forms::lang.components.shared.group_mail', + 'showExternalParam' => false + ], + 'mail_resp_enabled' => [ + 'title' => 'martin.forms::lang.components.shared.mail_resp_enabled.title', + 'description' => 'martin.forms::lang.components.shared.mail_resp_enabled.description', + 'type' => 'checkbox', + 'group' => 'martin.forms::lang.components.shared.group_mail_resp', + 'showExternalParam' => false + ], + 'mail_resp_field' => [ + 'title' => 'martin.forms::lang.components.shared.mail_resp_field.title', + 'description' => 'martin.forms::lang.components.shared.mail_resp_field.description', + 'type' => 'string', + 'group' => 'martin.forms::lang.components.shared.group_mail_resp', + 'showExternalParam' => false + ], + 'mail_resp_from' => [ + 'title' => 'martin.forms::lang.components.shared.mail_resp_from.title', + 'description' => 'martin.forms::lang.components.shared.mail_resp_from.description', + 'type' => 'string', + 'group' => 'martin.forms::lang.components.shared.group_mail_resp', + 'showExternalParam' => false + ], + 'mail_resp_subject' => [ + 'title' => 'martin.forms::lang.components.shared.mail_resp_subject.title', + 'description' => 'martin.forms::lang.components.shared.mail_resp_subject.description', + 'type' => 'string', + 'group' => 'martin.forms::lang.components.shared.group_mail_resp', + 'showExternalParam' => false + ], + 'mail_resp_template' => [ + 'title' => 'martin.forms::lang.components.shared.mail_template.title', + 'description' => 'martin.forms::lang.components.shared.mail_template.description', + 'type' => 'string', + 'group' => 'martin.forms::lang.components.shared.group_mail_resp', + 'showExternalParam' => false + ], + 'reset_form' => [ + 'title' => 'martin.forms::lang.components.shared.reset_form.title', + 'description' => 'martin.forms::lang.components.shared.reset_form.description', + 'type' => 'checkbox', + 'group' => 'martin.forms::lang.components.shared.group_settings', + 'showExternalParam' => false + ], + 'redirect' => [ + 'title' => 'martin.forms::lang.components.shared.redirect.title', + 'description' => 'martin.forms::lang.components.shared.redirect.description', + 'type' => 'string', + 'group' => 'martin.forms::lang.components.shared.group_settings', + 'showExternalParam' => false + ], + 'inline_errors' => [ + 'title' => 'martin.forms::lang.components.shared.inline_errors.title', + 'description' => 'martin.forms::lang.components.shared.inline_errors.description', + 'type' => 'dropdown', + 'options' => ['disabled' => 'martin.forms::lang.components.shared.inline_errors.disabled', 'display' => 'martin.forms::lang.components.shared.inline_errors.display', 'variable' => 'martin.forms::lang.components.shared.inline_errors.variable'], + 'default' => 'disabled', + 'group' => 'martin.forms::lang.components.shared.group_settings', + 'showExternalParam' => false + ], + 'js_on_success' => [ + 'title' => 'martin.forms::lang.components.shared.js_on_success.title', + 'description' => 'martin.forms::lang.components.shared.js_on_success.description', + 'type' => 'text', + 'group' => 'martin.forms::lang.components.shared.group_settings', + 'showExternalParam' => false + ], + 'js_on_error' => [ + 'title' => 'martin.forms::lang.components.shared.js_on_error.title', + 'description' => 'martin.forms::lang.components.shared.js_on_error.description', + 'type' => 'text', + 'group' => 'martin.forms::lang.components.shared.group_settings', + 'showExternalParam' => false + ], + 'allowed_fields' => [ + 'title' => 'martin.forms::lang.components.shared.allowed_fields.title', + 'description' => 'martin.forms::lang.components.shared.allowed_fields.description', + 'type' => 'stringList', + 'group' => 'martin.forms::lang.components.shared.group_security', + 'showExternalParam' => false + ], + 'sanitize_data' => [ + 'title' => 'martin.forms::lang.components.shared.sanitize_data.title', + 'description' => 'martin.forms::lang.components.shared.sanitize_data.description', + 'type' => 'dropdown', + 'options' => ['disabled' => 'martin.forms::lang.components.shared.sanitize_data.disabled', 'htmlspecialchars' => 'martin.forms::lang.components.shared.sanitize_data.htmlspecialchars'], + 'default' => 'disabled', + 'group' => 'martin.forms::lang.components.shared.group_security', + 'showExternalParam' => false + ], + 'anonymize_ip' => [ + 'title' => 'martin.forms::lang.components.shared.anonymize_ip.title', + 'description' => 'martin.forms::lang.components.shared.anonymize_ip.description', + 'type' => 'dropdown', + 'options' => ['disabled' => 'martin.forms::lang.components.shared.anonymize_ip.disabled', 'partial' => 'martin.forms::lang.components.shared.anonymize_ip.partial', 'full' => 'martin.forms::lang.components.shared.anonymize_ip.full'], + 'default' => 'disabled', + 'group' => 'martin.forms::lang.components.shared.group_security', + 'showExternalParam' => false + ], + 'recaptcha_enabled' => [ + 'title' => 'martin.forms::lang.components.shared.recaptcha_enabled.title', + 'description' => 'martin.forms::lang.components.shared.recaptcha_enabled.description', + 'type' => 'checkbox', + 'group' => 'martin.forms::lang.components.shared.group_recaptcha', + 'showExternalParam' => false + ], + 'recaptcha_theme' => [ + 'title' => 'martin.forms::lang.components.shared.recaptcha_theme.title', + 'description' => 'martin.forms::lang.components.shared.recaptcha_theme.description', + 'type' => 'dropdown', + 'options' => ['light' => 'martin.forms::lang.components.shared.recaptcha_theme.light', 'dark' => 'martin.forms::lang.components.shared.recaptcha_theme.dark'], + 'default' => 'light', + 'group' => 'martin.forms::lang.components.shared.group_recaptcha', + 'showExternalParam' => false + ], + 'recaptcha_type' => [ + 'title' => 'martin.forms::lang.components.shared.recaptcha_type.title', + 'description' => 'martin.forms::lang.components.shared.recaptcha_type.description', + 'type' => 'dropdown', + 'options' => ['image' => 'martin.forms::lang.components.shared.recaptcha_type.image', 'audio' => 'martin.forms::lang.components.shared.recaptcha_type.audio'], + 'default' => 'image', + 'group' => 'martin.forms::lang.components.shared.group_recaptcha', + 'showExternalParam' => false + ], + 'recaptcha_size' => [ + 'title' => 'martin.forms::lang.components.shared.recaptcha_size.title', + 'description' => 'martin.forms::lang.components.shared.recaptcha_size.description', + 'type' => 'dropdown', + 'options' => [ + 'normal' => 'martin.forms::lang.components.shared.recaptcha_size.normal', + 'compact' => 'martin.forms::lang.components.shared.recaptcha_size.compact', + 'invisible' => 'martin.forms::lang.components.shared.recaptcha_size.invisible', + ], + 'default' => 'normal', + 'group' => 'martin.forms::lang.components.shared.group_recaptcha', + 'showExternalParam' => false + ], + 'skip_database' => [ + 'title' => 'martin.forms::lang.components.shared.skip_database.title', + 'description' => 'martin.forms::lang.components.shared.skip_database.description', + 'type' => 'checkbox', + 'group' => 'martin.forms::lang.components.shared.group_advanced', + 'showExternalParam' => false + ], + 'emails_date_format' => [ + 'title' => 'martin.forms::lang.components.shared.emails_date_format.title', + 'description' => 'martin.forms::lang.components.shared.emails_date_format.description', + 'default' => 'Y-m-d', + 'group' => 'martin.forms::lang.components.shared.group_advanced', + 'showExternalParam' => false + ], + ]; + } + +} + +?> diff --git a/plugins/martin/forms/classes/UnreadRecords.php b/plugins/martin/forms/classes/UnreadRecords.php new file mode 100644 index 0000000..c514a90 --- /dev/null +++ b/plugins/martin/forms/classes/UnreadRecords.php @@ -0,0 +1,16 @@ +count(); + return ($unread > 0) ? $unread : null; + } + +} + +?> \ No newline at end of file diff --git a/plugins/martin/forms/components/EmptyForm.php b/plugins/martin/forms/components/EmptyForm.php new file mode 100644 index 0000000..ba94fea --- /dev/null +++ b/plugins/martin/forms/components/EmptyForm.php @@ -0,0 +1,18 @@ + 'martin.forms::lang.components.empty_form.name', + 'description' => 'martin.forms::lang.components.empty_form.description', + ]; + } + + } + +?> \ No newline at end of file diff --git a/plugins/martin/forms/components/GenericForm.php b/plugins/martin/forms/components/GenericForm.php new file mode 100644 index 0000000..8a0ffe7 --- /dev/null +++ b/plugins/martin/forms/components/GenericForm.php @@ -0,0 +1,18 @@ + 'martin.forms::lang.components.generic_form.name', + 'description' => 'martin.forms::lang.components.generic_form.description', + ]; + } + + } + +?> \ No newline at end of file diff --git a/plugins/martin/forms/components/UploadForm.php b/plugins/martin/forms/components/UploadForm.php new file mode 100644 index 0000000..c210d9d --- /dev/null +++ b/plugins/martin/forms/components/UploadForm.php @@ -0,0 +1,104 @@ + 'martin.forms::lang.components.upload_form.name', + 'description' => 'martin.forms::lang.components.upload_form.description', + ]; + } + + public function init() { + parent::init(); + $this->fileTypes = $this->processFileTypes(true); + $this->maxSize = $this->property('maxSize'); + $this->placeholderText = $this->property('placeholderText'); + $this->removeText = $this->property('removeText'); + $this->setProperty('deferredBinding', 1); + $this->bindModel('files', new Record); + } + + public function onRun() { + parent::onRun(); + $this->addCss('assets/css/uploader.css'); + $this->addJs('assets/vendor/dropzone/dropzone.js'); + $this->addJs('assets/js/uploader.js'); + $this->isMulti = $this->property('uploader_multi'); + if($result = $this->checkUploadAction()) { return $result; } + } + + public function defineProperties() { + $local = [ + 'mail_uploads' => [ + 'title' => 'martin.forms::lang.components.shared.mail_uploads.title', + 'description' => 'martin.forms::lang.components.shared.mail_uploads.description', + 'type' => 'checkbox', + 'default' => false, + 'group' => 'martin.forms::lang.components.shared.group_mail', + 'showExternalParam' => false + ], + 'uploader_enable' => [ + 'title' => 'martin.forms::lang.components.shared.uploader_enable.title', + 'description' => 'martin.forms::lang.components.shared.uploader_enable.description', + 'default' => false, + 'type' => 'checkbox', + 'group' => 'martin.forms::lang.components.shared.group_uploader', + 'showExternalParam' => false, + ], + 'uploader_multi' => [ + 'title' => 'martin.forms::lang.components.shared.uploader_multi.title', + 'description' => 'martin.forms::lang.components.shared.uploader_multi.description', + 'default' => true, + 'type' => 'checkbox', + 'group' => 'martin.forms::lang.components.shared.group_uploader', + 'showExternalParam' => false, + ], + 'placeholderText' => [ + 'title' => 'martin.forms::lang.components.shared.uploader_pholder.title', + 'description' => 'martin.forms::lang.components.shared.uploader_pholder.description', + 'default' => Lang::get('martin.forms::lang.components.shared.uploader_pholder.default'), + 'type' => 'string', + 'group' => 'martin.forms::lang.components.shared.group_uploader', + 'showExternalParam' => false, + ], + 'removeText' => [ + 'title' => 'martin.forms::lang.components.shared.uploader_remFile.title', + 'description' => 'martin.forms::lang.components.shared.uploader_remFile.description', + 'default' => Lang::get('martin.forms::lang.components.shared.uploader_remFile.default'), + 'type' => 'string', + 'group' => 'martin.forms::lang.components.shared.group_uploader', + 'showExternalParam' => false, + ], + 'maxSize' => [ + 'title' => 'martin.forms::lang.components.shared.uploader_maxsize.title', + 'description' => 'martin.forms::lang.components.shared.uploader_maxsize.description', + 'default' => '5', + 'type' => 'string', + 'group' => 'martin.forms::lang.components.shared.group_uploader', + 'showExternalParam' => false, + ], + 'fileTypes' => [ + 'title' => 'martin.forms::lang.components.shared.uploader_types.title', + 'description' => 'martin.forms::lang.components.shared.uploader_types.description', + 'default' => Definitions::get('defaultExtensions'), + 'type' => 'stringList', + 'group' => 'martin.forms::lang.components.shared.group_uploader', + 'showExternalParam' => false, + ], + ]; + return array_merge(parent::defineProperties(), $local); + } + + } + +?> \ No newline at end of file diff --git a/plugins/martin/forms/components/emptyform/default.htm b/plugins/martin/forms/components/emptyform/default.htm new file mode 100644 index 0000000..9354444 --- /dev/null +++ b/plugins/martin/forms/components/emptyform/default.htm @@ -0,0 +1,12 @@ +

    Here goes your custom form

    +

    Override HTML by creating a new partial called default.htm (more info here)

    +

    You can copy/paste this basic template:

    +
    +    <form data-request="{{ __SELF__ }}::onFormSubmit">
    +        {{ form_token() }}
    +        <div id="{{ __SELF__ }}_forms_flash"></div>
    +        <!-- YOUR FORM FIELDS -->
    +        {% partial '@recaptcha' %}
    +        <!-- SUBMIT BUTTON -->
    +    </form>
    +
    \ No newline at end of file diff --git a/plugins/martin/forms/components/genericform/default.htm b/plugins/martin/forms/components/genericform/default.htm new file mode 100644 index 0000000..4911953 --- /dev/null +++ b/plugins/martin/forms/components/genericform/default.htm @@ -0,0 +1,32 @@ +
    + + {{ form_token() }} + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + +
    + +
    + {% partial '@recaptcha' %} +
    + + + +
    \ No newline at end of file diff --git a/plugins/martin/forms/components/partials/flash.htm b/plugins/martin/forms/components/partials/flash.htm new file mode 100644 index 0000000..1b89b06 --- /dev/null +++ b/plugins/martin/forms/components/partials/flash.htm @@ -0,0 +1,33 @@ +{% spaceless %} + + + + {% if jscript %} + + {% endif %} + +{% endspaceless %} diff --git a/plugins/martin/forms/components/partials/js/recaptcha.htm b/plugins/martin/forms/components/partials/js/recaptcha.htm new file mode 100644 index 0000000..9dd3d4c --- /dev/null +++ b/plugins/martin/forms/components/partials/js/recaptcha.htm @@ -0,0 +1 @@ +resetReCaptcha('{{ __SELF__ }}'); \ No newline at end of file diff --git a/plugins/martin/forms/components/partials/js/reset-form.htm b/plugins/martin/forms/components/partials/js/reset-form.htm new file mode 100644 index 0000000..d051d44 --- /dev/null +++ b/plugins/martin/forms/components/partials/js/reset-form.htm @@ -0,0 +1 @@ +$('{{ id }}').parents('form')[0].reset(); \ No newline at end of file diff --git a/plugins/martin/forms/components/partials/js/reset-uploader.htm b/plugins/martin/forms/components/partials/js/reset-uploader.htm new file mode 100644 index 0000000..a5a6505 --- /dev/null +++ b/plugins/martin/forms/components/partials/js/reset-uploader.htm @@ -0,0 +1,2 @@ +var dz = uploadDropZones['{{ id }}']; +if(dz) { dz.uploader.dropzone.removeAllFiles(); dz.uploader.evalIsPopulated(); } \ No newline at end of file diff --git a/plugins/martin/forms/components/partials/recaptcha.htm b/plugins/martin/forms/components/partials/recaptcha.htm new file mode 100644 index 0000000..b677c50 --- /dev/null +++ b/plugins/martin/forms/components/partials/recaptcha.htm @@ -0,0 +1,5 @@ +{% if (recaptcha_enabled) %} +
    +{% elseif (recaptcha_misconfigured) %} +
    {{ recaptcha_warn }}
    +{% endif %} \ No newline at end of file diff --git a/plugins/martin/forms/components/uploadform/default.htm b/plugins/martin/forms/components/uploadform/default.htm new file mode 100644 index 0000000..d6c66d8 --- /dev/null +++ b/plugins/martin/forms/components/uploadform/default.htm @@ -0,0 +1,51 @@ +{{ form_ajax(__SELF__ ~ '::onFormSubmit') }} + +
    + +
    +

    Apply for online job

    +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + +
    +

    Upload your resume

    + {% partial '@file-upload' %} +
    + +
    + +
    + {% partial '@recaptcha' %} +
    + + {{ form_submit() }} + +{{ form_close() }} \ No newline at end of file diff --git a/plugins/martin/forms/components/uploadform/file-multi.htm b/plugins/martin/forms/components/uploadform/file-multi.htm new file mode 100644 index 0000000..c17f5ae --- /dev/null +++ b/plugins/martin/forms/components/uploadform/file-multi.htm @@ -0,0 +1,71 @@ +
    + + + + + + + + +
    + {% for file in __SELF__.fileList %} +
    +
    + {% if file.isImage %} + + {% else %} + + {% endif %} +
    +
    +

    + {{ file.title ?: file.file_name }} +

    +

    {{ file.sizeToString }}

    +
    +
    + × +
    +
    + {% endfor %} +
    +
    + + + diff --git a/plugins/martin/forms/components/uploadform/file-single.htm b/plugins/martin/forms/components/uploadform/file-single.htm new file mode 100644 index 0000000..ee1fe7f --- /dev/null +++ b/plugins/martin/forms/components/uploadform/file-single.htm @@ -0,0 +1,79 @@ +{% set file = __SELF__.singleFile %} + +
    + + + + + + + + +
    + {% if file %} +
    +
    + {% if file.isImage %} + + {% else %} + + {% endif %} +
    +
    +

    + {{ file.title ?: file.file_name }} +

    +

    {{ file.sizeToString }}

    +
    +
    + × +
    +
    + {% endif %} +
    + + +
    + {{ __SELF__.placeholderText }} +
    + +
    + + + diff --git a/plugins/martin/forms/components/uploadform/file-upload.htm b/plugins/martin/forms/components/uploadform/file-upload.htm new file mode 100644 index 0000000..e0b4b28 --- /dev/null +++ b/plugins/martin/forms/components/uploadform/file-upload.htm @@ -0,0 +1,21 @@ +{% if __SELF__.property('uploader_enable') == 0 %} + +
    +
    Warning
    +
    Uploads are disabled.
    +
    You need to explicitly enable this option on the component (this is a security measure).
    +
    + +{% else %} + + {% if __SELF__.isMulti %} + + {% partial __SELF__ ~ '::file-multi' %} + + {% else %} + + {% partial __SELF__ ~ '::file-single' %} + + {% endif %} + +{% endif %} \ No newline at end of file diff --git a/plugins/martin/forms/composer.json b/plugins/martin/forms/composer.json new file mode 100644 index 0000000..0611747 --- /dev/null +++ b/plugins/martin/forms/composer.json @@ -0,0 +1,8 @@ +{ + "name": "martin/forms-plugin", + "type": "october-plugin", + "description": "None", + "require": { + "composer/installers": "~1.0" + } +} \ No newline at end of file diff --git a/plugins/martin/forms/controllers/Exports.php b/plugins/martin/forms/controllers/Exports.php new file mode 100644 index 0000000..f7ed90b --- /dev/null +++ b/plugins/martin/forms/controllers/Exports.php @@ -0,0 +1,121 @@ +pageTitle = e(trans('martin.forms::lang.controllers.exports.title')); + $this->create('frontend'); + } + + public function csv() { + + $records = Record::orderBy('created_at'); + + // FILTER GROUPS + if (!empty($groups = post('Record.filter_groups'))) { + $records->whereIn('group', $groups); + } + + // FILTER DATE + if (!empty($date_after = post('Record.filter_date_after'))) { + $records->whereDate('created_at', '>=', $date_after); + } + + // FILTER DATE + if (!empty($date_before = post('Record.filter_date_before'))) { + $records->whereDate('created_at', '<=', $date_before); + } + + // FILTER DELETED + if ($deleted = post('Record.options_deleted')) { + $records->withTrashed(); + } + + // CREATE CSV + $csv = CsvWriter::createFromFileObject(new SplTempFileObject()); + + // CHANGE DELIMTER + if (post('Record.options_delimiter')) { + $csv->setDelimiter(';'); + } + + // SET UTF-8 Output + if (post('Record.options_utf')) { + $csv->setOutputBOM(AbstractCsv::BOM_UTF8); + } + + // CSV HEADERS + $headers = []; + + // METADATA HEADERS + if (post('Record.options_metadata')) { + $meta_headers = [ + e(trans('martin.forms::lang.controllers.records.columns.id')), + e(trans('martin.forms::lang.controllers.records.columns.group')), + e(trans('martin.forms::lang.controllers.records.columns.ip')), + e(trans('martin.forms::lang.controllers.records.columns.created_at')), + ]; + $headers = array_merge($meta_headers, $headers); + } + + // ADD STORED FIELDS AS HEADER ROW IN CSV + $filteredRecords = $records->get(); + $recordsArray = $filteredRecords->toArray(); + $record = $filteredRecords->first(); + $headers = array_merge($headers, array_keys($record->form_data_arr)); + + // ADD HEADERS + $csv->insertOne($headers); + + // WRITE CSV LINES + foreach ($records->get()->toArray() as $row) { + + $data = (array) json_decode($row['form_data']); + + // IF DATA IS ARRAY CONVERT TO JSON STRING + foreach ($data as $field => $value) { + if (is_array($value) || is_object($value)) { + $data[$field] = json_encode($value); + } + } + + // ADD METADATA IF NEEDED + if (post('Record.options_metadata')) { + array_unshift($data, $row['id'], $row['group'], $row['ip'], $row['created_at']); + } + + $csv->insertOne($data); + + } + + // RETURN CSV + $csv->output('records.csv'); + exit(); + + } + +} + +?> diff --git a/plugins/martin/forms/controllers/Records.php b/plugins/martin/forms/controllers/Records.php new file mode 100644 index 0000000..c683a47 --- /dev/null +++ b/plugins/martin/forms/controllers/Records.php @@ -0,0 +1,107 @@ +unread = false; + $record->save(); + $this->addCss('/plugins/martin/forms/assets/css/records.css'); + $this->pageTitle = e(trans('martin.forms::lang.controllers.records.view_title')); + $this->vars['record'] = $record; + } + + public function onDelete() { + if (($checkedIds = post('checked')) && is_array($checkedIds) && count($checkedIds)) { + Record::whereIn('id',$checkedIds)->delete(); + } + $counter = UnreadRecords::getTotal(); + return [ + 'counter' => ($counter != null) ? $counter : 0, + 'list' => $this->listRefresh() + ]; + } + + public function onDeleteSingle() { + $id = post('id' ); + $record = Record::find($id); + if($record) { + $record->delete(); + Flash::success(e(trans('martin.forms::lang.controllers.records.deleted'))); + } else { + Flash::error(e(trans('martin.forms::lang.controllers.records.error'))); + } + return Redirect::to(Backend::url('martin/forms/records')); + } + + public function download($record_id, $file_id) { + $record = Record::findOrFail($record_id); + $file = $record->files->find($file_id); + if(!$file) { App::abort(404, Lang::get('backend::lang.import_export.file_not_found_error')); } + return response()->download($file->getLocalPath(), $file->getFilename()); + exit(); + } + + public function listInjectRowClass($record, $definition = null) { + if ($record->unread) { + return 'new'; + } + } + + public function onReadState() { + if (($checkedIds = post('checked')) && is_array($checkedIds) && count($checkedIds)) { + $unread = (post('state') == 'read') ? 0 : 1; + Record::whereIn('id',$checkedIds)->update(['unread' => $unread]); + } + $counter = UnreadRecords::getTotal(); + return [ + 'counter' => ($counter != null) ? $counter : 0, + 'list' => $this->listRefresh() + ]; + } + + public function onGDPRClean() { + if ($this->user->hasPermission(['martin.forms.gdpr_cleanup'])) { + GDPR::cleanRecords(); + Flash::success(e(trans('martin.forms::lang.controllers.records.alerts.gdpr_success'))); + } else { + Flash::error(e(trans('martin.forms::lang.controllers.records.alerts.gdpr_perms'))); + } + $counter = UnreadRecords::getTotal(); + return [ + 'counter' => ($counter != null) ? $counter : 0, + 'list' => $this->listRefresh() + ]; + } + + } + +?> \ No newline at end of file diff --git a/plugins/martin/forms/controllers/exports/config_form.yaml b/plugins/martin/forms/controllers/exports/config_form.yaml new file mode 100644 index 0000000..eb53dde --- /dev/null +++ b/plugins/martin/forms/controllers/exports/config_form.yaml @@ -0,0 +1,2 @@ +form : $/martin/forms/models/export/fields.yaml +modelClass: Martin\Forms\Models\Record \ No newline at end of file diff --git a/plugins/martin/forms/controllers/exports/index.htm b/plugins/martin/forms/controllers/exports/index.htm new file mode 100644 index 0000000..e12c469 --- /dev/null +++ b/plugins/martin/forms/controllers/exports/index.htm @@ -0,0 +1,16 @@ + +
      +
    • +
    • +
    + + + Backend::url('martin/forms/exports/csv/')]) ?> + + formRender() ?> + +
    + 'btn btn-primary']) ?> +
    + + \ No newline at end of file diff --git a/plugins/martin/forms/controllers/records/config_filter.yaml b/plugins/martin/forms/controllers/records/config_filter.yaml new file mode 100644 index 0000000..4130510 --- /dev/null +++ b/plugins/martin/forms/controllers/records/config_filter.yaml @@ -0,0 +1,13 @@ +scopes: + + group: + label : martin.forms::lang.controllers.records.columns.group + type : group + modelClass: Martin\Forms\Models\Record + options : filterGroups + conditions: "`group` in (:filtered)" + + created_at: + label : martin.forms::lang.controllers.records.columns.created_at + type : daterange + conditions: created_at >= ':after' AND created_at <= ':before' \ No newline at end of file diff --git a/plugins/martin/forms/controllers/records/config_list.yaml b/plugins/martin/forms/controllers/records/config_list.yaml new file mode 100644 index 0000000..6efedf4 --- /dev/null +++ b/plugins/martin/forms/controllers/records/config_list.yaml @@ -0,0 +1,19 @@ +list : $/martin/forms/models/record/columns.yaml +modelClass : Martin\Forms\Models\Record +title : martin.forms::lang.controllers.records.title +recordUrl : martin/forms/records/view/:id +noRecordsMessage: backend::lang.list.no_records +recordsPerPage : 20 +showSetup : true +showSorting : true +showCheckboxes : true +filter : config_filter.yaml + +defaultSort: + column : created_at + direction: desc + +toolbar: + buttons: partials/list_toolbar + search : + prompt: backend::lang.list.search_prompt \ No newline at end of file diff --git a/plugins/martin/forms/controllers/records/index.htm b/plugins/martin/forms/controllers/records/index.htm new file mode 100644 index 0000000..498d5dc --- /dev/null +++ b/plugins/martin/forms/controllers/records/index.htm @@ -0,0 +1 @@ +listRender() ?> \ No newline at end of file diff --git a/plugins/martin/forms/controllers/records/partials/_action_button.htm b/plugins/martin/forms/controllers/records/partials/_action_button.htm new file mode 100644 index 0000000..fa9dc53 --- /dev/null +++ b/plugins/martin/forms/controllers/records/partials/_action_button.htm @@ -0,0 +1,19 @@ + \ No newline at end of file diff --git a/plugins/martin/forms/controllers/records/partials/_list_toolbar.htm b/plugins/martin/forms/controllers/records/partials/_list_toolbar.htm new file mode 100644 index 0000000..86fc91b --- /dev/null +++ b/plugins/martin/forms/controllers/records/partials/_list_toolbar.htm @@ -0,0 +1,76 @@ +
    + + makePartial('$/martin/forms/controllers/records/partials/_action_button.htm', [ + 'type' => 'danger', + 'icon' => 'oc-icon-trash', + 'label' => e(trans('backend::lang.list.delete_selected')), + 'onclick' => "$(this).data('request-data', { checked: $('.control-list').listWidget('getChecked') })", + 'needs_selected' => true, + 'request' => 'onDelete', + 'request_confirm' => e(trans('backend::lang.list.delete_selected_confirm')), + 'request_success' => " + $('#records-toolbar').find('button').prop('disabled', true); + $.oc.flashMsg({ + 'text': '" . e(trans('backend::lang.list.delete_selected_success')) . "', + 'class': 'success', + 'interval': 3 + }); + $.oc.sideNav.setCounter('forms/records', data.counter); + $('#Lists').html(data.list['#Lists']); + ", + ]); + ?> + +
    + makePartial('$/martin/forms/controllers/records/partials/_action_button.htm', [ + 'type' => 'default', + 'icon' => 'oc-icon-eye-slash', + 'label' => e(trans('martin.forms::lang.controllers.records.buttons.unread')), + 'onclick' => "$(this).data('request-data', { state: 'unread', checked: $('.control-list').listWidget('getChecked') })", + 'needs_selected' => true, + 'request' => 'onReadState', + 'request_confirm' => '', + 'request_success' => " + $('#records-toolbar').find('button').prop('disabled', true); + $.oc.sideNav.setCounter('forms/records', data.counter); + $('#Lists').html(data.list['#Lists']); + ", + ]); + + echo $this->makePartial('$/martin/forms/controllers/records/partials/_action_button.htm', [ + 'type' => 'default', + 'icon' => 'oc-icon-eye', + 'label' => e(trans('martin.forms::lang.controllers.records.buttons.read')), + 'onclick' => "$(this).data('request-data', { state: 'read', checked: $('.control-list').listWidget('getChecked') })", + 'needs_selected' => true, + 'request' => 'onReadState', + 'request_confirm' => '', + 'request_success' => " + $('#records-toolbar').find('button').prop('disabled', true); + $.oc.sideNav.setCounter('forms/records', data.counter); + $('#Lists').html(data.list['#Lists']); + ", + ]); + ?> +
    + + makePartial('$/martin/forms/controllers/records/partials/_action_button.htm', [ + 'type' => 'danger', + 'icon' => 'oc-icon-history', + 'label' => e(trans('martin.forms::lang.controllers.records.buttons.gdpr_clean')), + 'request' => 'onGDPRClean', + 'request_confirm' => e(trans('martin.forms::lang.controllers.records.alerts.gdpr_confirm')), + 'request_success' => " + $('#records-toolbar').find('button').blur(); + $.oc.sideNav.setCounter('forms/records', data.counter); + $('#Lists').html(data.list['#Lists']); + ", + ]); + } + ?> + +
    \ No newline at end of file diff --git a/plugins/martin/forms/controllers/records/partials/_view_toolbar.htm b/plugins/martin/forms/controllers/records/partials/_view_toolbar.htm new file mode 100644 index 0000000..d8bf808 --- /dev/null +++ b/plugins/martin/forms/controllers/records/partials/_view_toolbar.htm @@ -0,0 +1,13 @@ +
    +
    +
    + + +
    +
    +
    \ No newline at end of file diff --git a/plugins/martin/forms/controllers/records/view.htm b/plugins/martin/forms/controllers/records/view.htm new file mode 100644 index 0000000..e8a8f2e --- /dev/null +++ b/plugins/martin/forms/controllers/records/view.htm @@ -0,0 +1,50 @@ + +
      +
    • +
    • Record #id ?>
    • +
    + + +makePartial('partials/view_toolbar') ?> + +

    Record #id ?>

    + + +form_data_arr as $label => $value): ?> + + + + + +files) > 0): ?> + + + + + +
    : + +
      + +
    + + + +
    Attached Files: + +
    + + \ No newline at end of file diff --git a/plugins/martin/forms/lang/de/lang.php b/plugins/martin/forms/lang/de/lang.php new file mode 100644 index 0000000..244ae1d --- /dev/null +++ b/plugins/martin/forms/lang/de/lang.php @@ -0,0 +1,177 @@ + [ + 'name' => 'Magic Forms', + 'description' => 'einfaches Erstellen von AJAX Formularen' + ], + + 'menu' => [ + 'label' => 'Magic Forms', + 'records' => ['label' => 'Records'], + 'exports' => ['label' => 'Export'], + 'settings' => 'Konfigurieren der plugin parameter', + ], + + 'controllers' => [ + 'records' => [ + 'title' => 'Einträge anzeigen', + 'view_title' => 'Details zum Eintrag', + 'error' => 'Eintrag nicht gefunden', + 'deleted' => 'Eintrag erfolgreich gelöscht', + 'columns' => [ + 'id' => 'Eintrag ID', + 'group' => 'Gruppe', + 'ip' => 'IP Adresse', + 'form_data' => 'Gespeicherte Felder', + 'files' => 'Anhänge', + 'created_at' => 'Erstellt', + ], + 'buttons' => [ + 'read' => 'Als gelesen markieren', + 'unread' => 'Als ungelesen markieren', + 'gdpr_clean' => 'DSVGO Aufräumung', + ], + 'alerts' => [ + 'gdpr_confirm' => "Sind sie sicher dass Sie alte Einträge aufräumen wollen?\nDiese Aktion kann nicht wiederrufen werden!", + 'gdpr_success' => 'DSVGO Aufräumung wurde erfolgreich ausgeführt', + 'gdpr_perms' => 'Sie haben keine Berechtigung für diese Funktion.', + ], + ], + 'exports' => [ + 'title' => 'Einträge exportieren', + 'breadcrumb' => 'Exportieren', + 'filter_section' => '1. Einträge filtern', + 'filter_type' => 'Alle Einträge exportieren', + 'filter_groups' => 'Gruppen', + 'filter_date_after' => 'Nach dem Datum', + 'filter_date_before' => 'Vor dem Datum', + 'options_section' => '2. Extra Optionen', + 'options_metadata' => 'Inklusive Metadaten', + 'options_metadata_com' => 'Exportiere die Einträge mit Metadaten (Eintrag ID, Gruppe, IP, Erstellungsdatum)', + 'options_deleted' => 'Inklusive gelöschter Einträge', + 'options_delimiter' => 'Alternatives Trennzeichen benutzen', + 'options_delimiter_com' => 'Semikolon als Trennzeichen', + 'options_utf' => 'Enkodierung in UTF8', + 'options_utf_com' => 'Enkodierung Ihrer CSV-Datei im UTF-8 Format für die Unterstützung von Umlauten und Sonderzeichen.', + ], + ], + + 'components' => [ + 'generic_form' => [ + 'name' => 'Generisches AJAX Formular', + 'description' => 'Mit Standardeinstellungen rendered ein generisches Formular; Überschreib Komponenten HTML mit deinen eigenen Feldern.', + ], + 'upload_form' => [ + 'name' => 'Upload AJAX Formular [BETA]', + 'description' => 'Zeigt wie man Dateiupload in deinem Formular implementiert.', + ], + 'empty_form' => [ + 'name' => 'Leeres AJAX Formular', + 'description' => 'Erstellt eine leere Vorlage für dein individuelles Formular; Überschreib Komponenten HTML.', + ], + 'shared' => [ + 'csrf_error' => 'Formular Seitzung abgelaufen! Bitte die Seite neuladen.', + 'recaptcha_warn' => 'Warnung: reCAPTCHA ist nicht ordentlich konfiguriert. Bitte, gehe zum Backend > Einstellungen > CMS > Magic Forms um reCAPTCHA zu kofigurieren.', + 'group_validation' => 'Formular Validierung', + 'group_messages' => 'Flash Benachrichtung', + 'group_mail' => 'Benachrichtigungen Einstellungen', + 'group_mail_resp' => 'Automatische Antwort Einstellungen', + 'group_settings' => 'Weitere Einstellungen', + 'group_security' => 'Sicherheit', + 'group_recaptcha' => 'reCAPTCHA Einstellungen', + 'group_advanced' => 'Fortgeschrittene Einstellungen', + 'group_uploader' => 'Uploader Einstellungen', + 'validation_req' => 'Die Eigenschaft wird benötigt', + 'group' => ['title' => 'Gruppe' , 'description' => 'Organisiere deine Formulare durch eigene Gruppennamen. Diese Option ist für das Exportieren der Daten sehr praktischt.'], + 'rules' => ['title' => 'Regeln' , 'description' => 'Erstelle eigene Regeln Mithilfe der Laravel Validierungsfunktion'], + 'rules_messages' => ['title' => 'Regeln Benachrichtigungen' , 'description' => 'Erstelle eigene Benachrichtigungen Mithilfe der Laravel Validierungsfunktion'], + 'custom_attributes' => ['title' => 'Eigene Attribute' , 'description' => 'Erstelle eigene Attribute Mithilfe der Laravel Validierungsfunktion'], + 'messages_success' => ['title' => 'Erfolg' , 'description' => 'Nachricht bei erfolgreicher Übermittlung des Formulars', 'default' => 'Your form was successfully submitted' ], + 'messages_errors' => ['title' => 'Fehler' , 'description' => 'Nachricht bei Fehlern während der Eingabe der Formulardaten' , 'default' => 'There were errors with your submission'], + 'messages_partial' => ['title' => 'Benutze eigene Partial' , 'description' => 'Überschreibe Flash-Nachrichten mit deinem eigenem Partial in deinem Theme'], + 'mail_enabled' => ['title' => 'Sende Benachrichtigungen' , 'description' => 'Sende eine Benachrichtigungsmail nach jeder erfolgreichen Übertragung.'], + 'mail_subject' => ['title' => 'Betreff' , 'description' => 'Überschreibe die standard E-Mail Betreffszeile'], + 'mail_recipients' => ['title' => 'Empfänger' , 'description' => 'E-Mail Empfänger eintragen (Füge eine E-Mail Adresse pro Zeile ein)'], + 'mail_bcc' => ['title' => 'BCC' , 'description' => 'Sende blind carbon copy (BCC) zur folgenden Empfängern (Füge eine E-Mail Adresse per Zeile ein)'], + 'mail_replyto' => ['title' => 'Empfänger E-Mail Feld', 'description' => 'Formular Feld das die E-Mail Adresse enthält. Diese Adresse wird als Empfänger Adresse benutzt.'], + 'mail_template' => ['title' => 'E-Mail Vorlage' , 'description' => 'Benutze eigene E-Mail Vorlagen. Gebe einen Vorlagen Code ein wie z.B. "martin.forms::mail.notification" (Kann im Einstellungen der E-Mail Vorlagen im Backend gefunden werden). Leer lassen für Standardvorlage.'], + 'mail_uploads' => ['title' => 'Sende Uploads' , 'description' => 'Sende Uploads als Anhang'], + 'mail_resp_enabled' => ['title' => 'Sende Auto-Antwort' , 'description' => 'Sende eine automatische Antwort E-Mail zu der Person die dein Formular ausgfüllt.'], + 'mail_resp_field' => ['title' => 'E-Mail Feld' , 'description' => 'Formular Feld das die E-Mail Adresse enthät für die automatische Antwort'], + 'mail_resp_from' => ['title' => 'Absender Adresse' , 'description' => 'Absender E-Mail Adresse was benutzt werden soll (z.B. noreply@yourcompany.com)'], + 'mail_resp_subject' => ['title' => 'Betreff' , 'description' => 'Überschreibe standard E-Mail Betreffszeile'], + 'reset_form' => ['title' => 'Resete Formular' , 'description' => 'Resete das Formular nach erfolgreichen Übertragung'], + 'redirect' => ['title' => 'Umleitung bei Erfolg', 'description' => 'URL-Umleitung nach erfoglreichen Übertragung'], + 'inline_errors' => ['title' => 'Inline Fehler' , 'description' => 'Zeige Inline-Fehler an. Die Funktion braucht Extra-Code. Siehe in der Dokumentation für mehr Informationen.', 'disabled' => 'Deaktiviert', 'display' => 'Zeige Fehler', 'variable' => 'JS Variable'], + 'js_on_success' => ['title' => 'JS bei Erfolg' , 'description' => 'Führe eigenen JavaScript-Code aus wenn das Formular erfolgreich übertragen wurde. Keine Script Tags benutzen!'], + 'js_on_error' => ['title' => 'JS bei Fehlern' , 'description' => 'Führe eigenen JavaScript-Code aus wenn das Formular nicht validiert werden kann. Keine Script Tags benutzen!'], + 'allowed_fields' => ['title' => 'Erlaubte Felder' , 'description' => 'Festlegen welche Felder gefiltert und gespeichert werden sollen. (Füge ein Feld per Zeile ein)'], + 'anonymize_ip' => ['title' => 'IP-Anonymisierung' , 'description' => 'IP-Adresse nicht speichern.', 'full' => 'Voll', 'partial' => 'Teilanonymisierung', 'disabled' => 'Deaktiviert'], + 'sanitize_data' => ['title' => 'Bereinigung der Formulardaten' , 'description' => 'Bereinige die Formulardaten und speichere das Ergebnis in der Datenbank', 'disabled' => 'deaktiviert', 'htmlspecialchars' => 'Benutzer htmlspecialchars'], + 'recaptcha_enabled' => ['title' => 'Aktiviere reCAPTCHA' , 'description' => 'reCAPTCHA-Widget in deinem Formular einfügen'], + 'recaptcha_theme' => ['title' => 'Theme' , 'description' => 'Farbschema des Widgets', 'light' => 'Hell' , 'dark' => 'Dunkel'], + 'recaptcha_type' => ['title' => 'Typ' , 'description' => 'reCAPTCHA Typ festlegen' , 'image' => 'Bild' , 'audio' => 'Audio'], + 'recaptcha_size' => [ + 'title' => 'Größe', + 'description' => 'Die Größe des Widgets', + 'normal' => 'Normal', + 'compact' => 'Kompakt', + 'invisible' => 'Unsichtbar', + ], + 'skip_database' => ['title' => 'Überspringe DB' , 'description' => 'Keine Speicherung der Formulardaten in der Datenbank. Nützlich wenn man Events mit einem eigenem Plugin nutzen möchte.'], + 'emails_date_format' => ['title' => 'Datum Formatierung in E-Mails', 'description' => 'Ändere die Datumformatierung die dann in E-Mails verwendet wird.'], + 'uploader_enable' => ['title' => 'Erlaube Uploads' , 'description' => 'Aktiviere Datei-Upload. Diese Option muss aufgrund der Sicherheitseinstellungen explizit aktiviert werden.'], + 'uploader_multi' => ['title' => 'Merhfachdateien' , 'description' => 'Erlaube Mehrfachdateien-Uploads'], + 'uploader_pholder' => ['title' => 'Platzhalter Text' , 'description' => 'Platzhalter Text der angezeigt wird so lange keine Datei hochgeladen wurde', 'default' => 'Click or drag files to upload'], + 'uploader_maxsize' => ['title' => 'Dateigröße Limitierung' , 'description' => 'Die maximale Dateigröße die hochgeladen werden kann in Megabytes'], + 'uploader_types' => ['title' => 'Erlaubte Datei-Typen' , 'description' => 'Erlaubte Dateiendungen oder ein Stern (*) für alle Typen (Füge eine Dateiendung per Zeile ein)'], + 'uploader_remFile' => ['title' => 'Popup Text Datei entfernen' , 'description' => 'Text Platzhalter für die Abfrage, wenn eine Datei vor dem Upload entfernt wird', 'default' => 'Are you sure ?'], + ] + ], + + 'settings' => [ + 'tabs' => ['general' => 'Allgemein', 'recaptcha' => 'reCAPTCHA', 'gdpr' => 'DSVGO'], + 'section_flash_messages' => 'Flash Nachrichten', + 'global_messages_success' => ['label' => 'Globale Erfolgsnachricht', 'comment' => '(Diese Einstellung kann aus der Komponente heraus überschrieben werden)', 'default' => 'Your form was successfully submitted'], + 'global_messages_errors' => ['label' => 'Globale Fehlernachricht' , 'comment' => '(Diese Einstellung kann aus der Komponente heraus überschrieben werden)', 'default' => 'There were errors with your submission'], + 'plugin_help' => 'Die Plugindokumentation erreichst Du über die GitHub repo:', + 'global_hide_button' => 'Navigationsobjekt vestecken', + 'global_hide_button_desc' => 'Praktisch, wenn Du eigene Events mit einem eigenem Plugin nutzen möchtest.', + 'section_recaptcha' => 'reCAPTCHA Einstellungen', + 'recaptcha_site_key' => 'Site key', + 'recaptcha_secret_key' => 'Secret key', + 'gdpr_help_title' => 'Information', + 'gdpr_help_comment' => 'Das neue EU-DSVGO Gesetz in Europa besagt dass die Einträge nicht mehr unendlich aufbewahrt werden dürfen. Diese müssen je nach nach Bedarf automatisiert gelöscht werden.', + 'gdpr_enable' => 'Aktiviere EU-DSVGO konforme Aufräumung', + 'gdpr_days' => 'Behalte die Einträge für die Anzahl an: X Tagen', + ], + + 'permissions' => [ + 'tab' => 'Magic Forms', + 'access_records' => 'Zugriff auf gespeicherte Formular-Einsendedaten', + 'access_exports' => 'Zugriff für das Exportieren der gespeicherten Formular-Einsendedaten', + 'access_settings' => 'Zugriff auf Modul-Konfiguration', + 'gdpr_cleanup' => 'Führe EU-DSVGO Aufräumung der Datenbank durch.', + ], + + 'mails' => [ + 'form_notification' => ['description' => 'Benachrichtung nach erfolgreicher Einsendung der Formulardaten.'], + 'form_autoresponse' => ['description' => 'Automatische Antwort nach erfolgreicher Einsendung der Formulardaten.'], + ], + + 'validation' => [ + 'recaptcha_error' => 'Kann das reCAPTCHA Feld nicht validieren.' + ], + + 'classes' => [ + 'GDPR' => [ + 'alert_gdpr_disabled' => 'EU-DSVGO Einstellungen sind deaktiviert.', + 'alert_invalid_gdpr' => 'Ungültige Einstellung des EU-DSVGO Aufräumvorgangs Intervals nach X Tagen.', + ] + ] + + ]; + +?> diff --git a/plugins/martin/forms/lang/en/lang.php b/plugins/martin/forms/lang/en/lang.php new file mode 100644 index 0000000..ffea75b --- /dev/null +++ b/plugins/martin/forms/lang/en/lang.php @@ -0,0 +1,177 @@ + [ + 'name' => 'Magic Forms', + 'description' => 'Create easy AJAX forms' + ], + + 'menu' => [ + 'label' => 'Magic Forms', + 'records' => ['label' => 'Records'], + 'exports' => ['label' => 'Export'], + 'settings' => 'Configure plugin parameters', + ], + + 'controllers' => [ + 'records' => [ + 'title' => 'View Records', + 'view_title' => 'Record Details', + 'error' => 'Record not found', + 'deleted' => 'Record deleted successfully', + 'columns' => [ + 'id' => 'Record ID', + 'group' => 'Group', + 'ip' => 'IP Address', + 'form_data' => 'Stored Fields', + 'files' => 'Attached Files', + 'created_at' => 'Created', + ], + 'buttons' => [ + 'read' => 'Mark as Read', + 'unread' => 'Mark as Unread', + 'gdpr_clean' => 'GDPR Clean', + ], + 'alerts' => [ + 'gdpr_confirm' => "Are you sure you want to clean old records?\nThis action cannot be undone!", + 'gdpr_success' => 'GDPR cleanup was executed successfully', + 'gdpr_perms' => 'You don\'t have permission to this feature', + ], + ], + 'exports' => [ + 'title' => 'Export Records', + 'breadcrumb' => 'Export', + 'filter_section' => '1. Filter records', + 'filter_type' => 'Export all records', + 'filter_groups' => 'Groups', + 'filter_date_after' => 'Date after', + 'filter_date_before' => 'Date before', + 'options_section' => '2. Extra options', + 'options_metadata' => 'Include metadata', + 'options_metadata_com' => 'Export records with metadata (Record ID, group, IP, created date)', + 'options_deleted' => 'Include deleted records', + 'options_delimiter' => 'Use alternative delimiter', + 'options_delimiter_com' => 'Use semicolon as delimiter', + 'options_utf' => 'Encode in UTF8', + 'options_utf_com' => 'Encode your csv in UTF-8 to support non standard characters', + ], + ], + + 'components' => [ + 'generic_form' => [ + 'name' => 'Generic AJAX Form', + 'description' => 'By default renders a generic form; override component HTML with your custom fields.', + ], + 'upload_form' => [ + 'name' => 'Upload AJAX Form [BETA]', + 'description' => 'Shows how to implement file uploads on your form.', + ], + 'empty_form' => [ + 'name' => 'Empty AJAX Form', + 'description' => 'Create a empty template for your custom form; override component HTML.', + ], + 'shared' => [ + 'csrf_error' => 'Form session expired! Please refresh the page.', + 'recaptcha_warn' => 'Warning: reCAPTCHA is not properly configured. Please, goto Backend > Settings > CMS > Magic Forms and configure.', + 'group_validation' => 'Form Validation', + 'group_messages' => 'Flash Messages', + 'group_mail' => 'Notifications Settings', + 'group_mail_resp' => 'Auto-Response Settings', + 'group_settings' => 'More Settings', + 'group_security' => 'Security', + 'group_recaptcha' => 'reCAPTCHA Settings', + 'group_advanced' => 'Advanced Settings', + 'group_uploader' => 'Uploader Settings', + 'validation_req' => 'The property is required', + 'group' => ['title' => 'Group' , 'description' => 'Organize your forms with a custom group name. This option is useful when exporting data.'], + 'rules' => ['title' => 'Rules' , 'description' => 'Set your own rules using Laravel validation'], + 'rules_messages' => ['title' => 'Rules Messages' , 'description' => 'Use your own rules messages using Laravel validation'], + 'custom_attributes' => ['title' => 'Custom Attributes' , 'description' => 'Use your own custom attributes using Laravel validation'], + 'messages_success' => ['title' => 'Success' , 'description' => 'Message when the form is successfully submited', 'default' => 'Your form was successfully submitted' ], + 'messages_errors' => ['title' => 'Errors' , 'description' => 'Message when the form contains errors' , 'default' => 'There were errors with your submission'], + 'messages_partial' => ['title' => 'Use Custom Partial' , 'description' => 'Override flash messages with your custom partial inside your theme'], + 'mail_enabled' => ['title' => 'Send Notifications' , 'description' => 'Send mail notifications on every form submited'], + 'mail_subject' => ['title' => 'Subject' , 'description' => 'Override default email subject'], + 'mail_recipients' => ['title' => 'Recipients' , 'description' => 'Specify email recipients (add one address per line)'], + 'mail_bcc' => ['title' => 'BCC' , 'description' => 'Send blind carbon copy to email recipients (add one address per line)'], + 'mail_replyto' => ['title' => 'ReplyTo Email Field', 'description' => 'Form field containing the email address of sender to be used as "ReplyTo"'], + 'mail_template' => ['title' => 'Mail Template' , 'description' => 'Use custom mail template. Specify template code like "martin.forms::mail.notification" (found on Settings, Mail templates). Leave empty to use default.'], + 'mail_uploads' => ['title' => 'Send Uploads' , 'description' => 'Send uploads as attachements'], + 'mail_resp_enabled' => ['title' => 'Send Auto-Response' , 'description' => 'Send an auto-response email to the person submitting the form'], + 'mail_resp_field' => ['title' => 'Email Field' , 'description' => 'Form field containing the email address of the recipient of auto-response'], + 'mail_resp_from' => ['title' => 'Sender Address' , 'description' => 'Email address of auto-response email sender (e.g. noreply@yourcompany.com)'], + 'mail_resp_subject' => ['title' => 'Subject' , 'description' => 'Override default email subject'], + 'reset_form' => ['title' => 'Reset Form' , 'description' => 'Reset form after successfully submit'], + 'redirect' => ['title' => 'Redirect on Success', 'description' => 'Redirect to URL on successfully submit.'], + 'inline_errors' => ['title' => 'Inline errors' , 'description' => 'Display inline errors. This requires extra code, check documentation for more info.', 'disabled' => 'Disabled', 'display' => 'Display errors', 'variable' => 'JS variable'], + 'js_on_success' => ['title' => 'JS on Success' , 'description' => 'Execute custom JavaScript code when the form was successfully submitted. Don\'t use script tags.'], + 'js_on_error' => ['title' => 'JS on Error' , 'description' => 'Execute custom JavaScript code when the form doesn\'t validate. Don\'t use script tags.'], + 'allowed_fields' => ['title' => 'Allowed Fields' , 'description' => 'Specify which fields should be filtered and stored (add one field name per line)'], + 'anonymize_ip' => ['title' => 'Anonymize IP' , 'description' => 'Don\'t store IP address', 'full' => 'Full', 'partial' => 'Partial', 'disabled' => 'Disabled'], + 'sanitize_data' => ['title' => 'Sanitize form data' , 'description' => 'Sanitize form data and save result on database', 'disabled' => 'Disabled', 'htmlspecialchars' => 'Use htmlspecialchars'], + 'recaptcha_enabled' => ['title' => 'Enable reCAPTCHA' , 'description' => 'Insert the reCAPTCHA widget on your form'], + 'recaptcha_theme' => ['title' => 'Theme' , 'description' => 'The color theme of the widget', 'light' => 'Light' , 'dark' => 'Dark'], + 'recaptcha_type' => ['title' => 'Type' , 'description' => 'The type of CAPTCHA to serve' , 'image' => 'Image' , 'audio' => 'Audio'], + 'recaptcha_size' => [ + 'title' => 'Size', + 'description' => 'The size of the widget', + 'normal' => 'Normal', + 'compact' => 'Compact', + 'invisible' => 'Invisible', + ], + 'skip_database' => ['title' => 'Skip DB' , 'description' => 'Don\'t store this form on database. Useful if you want to use events with your custom plugin.'], + 'emails_date_format' => ['title' => 'Date format on emails', 'description' => 'Set custom format for dates used on emails subjects.'], + 'uploader_enable' => ['title' => 'Allow uploads' , 'description' => 'Enable files uploading. You need to explicitly enable this option as a security measure.'], + 'uploader_multi' => ['title' => 'Multiple files' , 'description' => 'Allow multipe files uploads'], + 'uploader_pholder' => ['title' => 'Placeholder text' , 'description' => 'Wording to display when no file is uploaded', 'default' => 'Click or drag files to upload'], + 'uploader_maxsize' => ['title' => 'File size limit' , 'description' => 'The maximum file size that can be uploaded in megabytes'], + 'uploader_types' => ['title' => 'Allowed file types' , 'description' => 'Allowed file extensions or star (*) for all types (add one extension per line)'], + 'uploader_remFile' => ['title' => 'Remove Popup text' , 'description' => 'Wording to display in the popup when you remove file', 'default' => 'Are you sure ?'], + ] + ], + + 'settings' => [ + 'tabs' => ['general' => 'General', 'recaptcha' => 'reCAPTCHA', 'gdpr' => 'GDPR'], + 'section_flash_messages' => 'Flash Messages', + 'global_messages_success' => ['label' => 'Global Success Message', 'comment' => '(This setting can be overridden from the component)', 'default' => 'Your form was successfully submitted'], + 'global_messages_errors' => ['label' => 'Global Errors Message' , 'comment' => '(This setting can be overridden from the component)', 'default' => 'There were errors with your submission'], + 'plugin_help' => 'You can access plugin documentation at GitHub repo:', + 'global_hide_button' => 'Hide navigation item', + 'global_hide_button_desc' => 'Useful if you want to use events with your custom plugin.', + 'section_recaptcha' => 'reCAPTCHA Settings', + 'recaptcha_site_key' => 'Site key', + 'recaptcha_secret_key' => 'Secret key', + 'gdpr_help_title' => 'Information', + 'gdpr_help_comment' => 'New GDPR law in Europe, you can\'t keep records undefinitely, need to clear them after a certain period of time depending on your needs', + 'gdpr_enable' => 'Enable GDPR', + 'gdpr_days' => 'Keep records for a maximum of X days', + ], + + 'permissions' => [ + 'tab' => 'Magic Forms', + 'access_records' => 'Access stored forms data', + 'access_exports' => 'Access to export stored data', + 'access_settings' => 'Access module configuration', + 'gdpr_cleanup' => 'Perform GDPR database cleanup', + ], + + 'mails' => [ + 'form_notification' => ['description' => 'Notify when a form is submited'], + 'form_autoresponse' => ['description' => 'Auto-Response when a form is submited'], + ], + + 'validation' => [ + 'recaptcha_error' => 'Cannot validate reCAPTCHA field' + ], + + 'classes' => [ + 'GDPR' => [ + 'alert_gdpr_disabled' => 'GDPR options are disabled', + 'alert_invalid_gdpr' => 'Invalid GDPR days setting value', + ] + ] + + ]; + +?> diff --git a/plugins/martin/forms/lang/fr/lang.php b/plugins/martin/forms/lang/fr/lang.php new file mode 100644 index 0000000..997a4df --- /dev/null +++ b/plugins/martin/forms/lang/fr/lang.php @@ -0,0 +1,153 @@ + [ + 'name' => 'Magic Forms', + 'description' => 'Créer des formulaires AJAX facilement' + ], + 'menu' => [ + 'label' => 'Magic Forms', + 'records' => ['label' => 'Enregistrements'], + 'exports' => ['label' => 'Export'], + 'settings' => 'Configurer kes parameters du plugin', + ], + 'controllers' => [ + 'records' => [ + 'title' => 'Voir les enregistrements', + 'view_title' => 'Détails de l\'enregistrement', + 'error' => 'Enregistrement non trouvé', + 'deleted' => 'Enregistrement supprimé avec succès', + 'columns' => [ + 'id' => 'Enregistrement N°', + 'group' => 'Groupe', + 'ip' => 'Adresse IP', + 'form_data' => 'Champs stockés', + 'files' => 'Fichiers attachés', + 'created_at' => 'Créer le', + ], + 'buttons' => [ + 'read' => 'Marquer comme lu', + 'unread' => 'Marquer comme non lu', + 'gdpr_clean' => 'Nettoyage RGPD', + ], + 'alerts' => [ + 'gdpr_confirm' => "Êtes-vous sûr de vouloir nettoyer les anciens enregistrements?\nCette action ne peut pas être annulée!", + 'gdpr_success' => 'Le nettoyage RGPD a été exécuté avec succès', + 'gdpr_perms' => 'Vous n\'avez pas l\'autorisation pour utiliser cette fonctionnalité', + ], + ], + 'exports' => [ + 'title' => 'Exporter les enregistrements', + 'breadcrumb' => 'Exporter', + 'filter_section' => '1. Filtrer les enregistrements', + 'filter_type' => 'Exporter tous les enregistrements', + 'filter_groups' => 'Groupes', + 'filter_date_after' => 'Date de début', + 'filter_date_before' => 'Date de fin', + 'options_section' => '2. Options supplémentaires', + 'options_metadata' => 'Inclure les metadonnées', + 'options_metadata_com' => 'Exporter les enregistrements avec les metadonnées (n°, groupe, IP, date de création)', + 'options_deleted' => 'Inclure les enregistrements supprimés', + ], + ], + 'components' => [ + 'generic_form' => [ + 'name' => 'Formulaire générique AJAX', + 'description' => 'Rendu d\'un formulaire générique; remplacer le composant HTML par vos propres champs personnalisés.', + ], + 'upload_form' => [ + 'name' => 'Téléchargements AJAX [BETA]', + 'description' => 'Montre comment implémenter des téléchargements de fichiers sur votre formulaire.', + ], + 'empty_form' => [ + 'name' => 'Formulaire AJAX vide', + 'description' => 'Créer un modèle vide pour votre formulaire personnalisé; remplacer le composant HTML.', + ], + 'shared' => [ + 'csrf_error' => 'La session du formulaire a expirée ! Veuillez actualiser la page.', + 'recaptcha_warn' => 'Avertissement: reCAPTCHA n\'est pas correctement configuré. Àllez dans Backend > Paramètres > CMS > Magic Forms et configurez svp.', + 'group_validation' => 'Validation du formulaire', + 'group_messages' => 'Messages Flash ', + 'group_mail' => 'Notifications', + 'group_mail_resp' => 'Réponse automatique', + 'group_settings' => 'Plus de réglages', + 'group_security' => 'Securité', + 'group_recaptcha' => 'reCAPTCHA', + 'group_uploader' => 'Transfert de fichiers', + 'validation_req' => 'La propriété est requise', + 'group' => ['title' => 'Groupe' , 'description' => 'Organisez vos formulaires avec un nom de groupe personnalisé. Cette option est utile lorsque vous exportez des données.'], + 'rules' => ['title' => 'Règles' , 'description' => 'Définissez vos propres règles en utilisant la validation de Laravel'], + 'rules_messages' => ['title' => 'Messages de règles' , 'description' => 'Utilisez vos propres messages de règles en utilisant la validation de Laravel'], + 'messages_success' => ['title' => 'Succès' , 'description' => 'Message lorsque le formulaire est soumis avec succès' , 'default' => 'Votre formulaire a été envoyé avec succès' ], + 'messages_errors' => ['title' => 'Erreurs' , 'description' => 'Message lorsque le formulaire contient des erreurs' , 'default' => 'Il y a eu des erreurs dans votre formulaire'], + 'messages_partial' => ['title' => 'Utiliser un modèle partiel personnalisé', 'description' => 'Modifier le message flash avec un modèle partiel personnalisé de votre thème'], + 'mail_enabled' => ['title' => 'Envoyer des notifications' , 'description' => 'Envoyer des notifications par mail sur chaque formulaire envoyé'], + 'mail_subject' => ['title' => 'Sujet' , 'description' => 'Remplacer le sujet par défaut du courrier électronique'], + 'mail_recipients' => ['title' => 'Destinataires' , 'description' => 'Spécifier les destinataires des e-mails (ajouter une adresse par ligne)'], + 'mail_bcc' => ['title' => 'CCI' , 'description' => 'Envoyer une copie carbone invisible aux destinataires des e-mails (ajouter une adresse par ligne)'], + 'mail_replyto' => ['title' => 'Champ du email de réponse (ReplyTo)' , 'description' => 'Champ de formulaire contenant l\'adresse e-mail de l\'expéditeur à utiliser comme "ReplyTo"'], + 'mail_uploads' => ['title' => 'Envoyer les téléchargements' , 'description' => 'Envoi des fichiers téléchargés en pièce jointe'], + 'mail_template' => ['title' => 'Modèle e-mail' , 'description' => 'Utiliser un modèle e-mail personnalisé. Spécifiez un code de modèle tel que "martin.forms::mail.notification" (situé dans Paramètres, Modèles des e-mails). Laissez vide pour utiliser les paramètres par défaut.'], + 'mail_uploads' => ['title' => 'Envoyer les téléchargements' , 'description' => 'Envoyer les téléchargements en pièces jointes'], + 'mail_resp_enabled' => ['title' => 'Envoyer une réponse automatique' , 'description' => 'Envoyer un e-mail d\'auto-réponse à la personne qui soumet le formulaire'], + 'mail_resp_field' => ['title' => 'Champ email' , 'description' => 'Champ de formulaire contenant l\'adresse e-mail du destinataire de réponse automatique '], + 'mail_resp_from' => ['title' => 'Adresse de l\'expéditeur' , 'description' => 'Adresse e-mail de l\'expéditeur du courrier électronique de réponse automatique (par exemple nepasrepondre@votreentreprise.com)'], + 'mail_resp_subject' => ['title' => 'Sujet' , 'description' => 'Remplacer le sujet par défaut du courrier électronique'], + 'reset_form' => ['title' => 'Réinitialiser le formulaire' , 'description' => 'Réinitialiser le formulaire après l\'envoi réussi'], + 'redirect' => ['title' => 'Redirection envoi réussi' , 'description' => 'Rediriger vers une URL spécifique lors de l\'envoi réussi. Remarque: doit être une URL valide commençant par http ou https ou la redirection sera ignorée.'], + 'inline_errors' => ['title' => 'Erreurs sur la même ligne' , 'description' => 'Afficher les erreurs sur la même ligne. Cela nécéssite du code supplémentaire, consultez la documentation pour plus d\'informations. ', 'disabled' => 'Désactivé', 'display' => 'Afficher les erreurs', 'variable' => 'Variable Javascript'], + 'js_on_success' => ['title' => 'JS au succès' , 'description' => 'Exécutez du code JavaScript personnalisé lorsque le formulaire a été soumis avec succès. Ne pas utiliser les balises script'], + 'js_on_error' => ['title' => 'JS si erreur' , 'description' => 'Exécutez du code JavaScript personnalisé lorsque le formulaire ne se valide pas. Ne pas utiliser les balises script.'], + 'allowed_fields' => ['title' => 'Allowed Fields' , 'description' => 'Spécifiez quels champs doivent être filtrés et stockés (ajoutez un nom de champ par ligne).'], + 'anonymize_ip' => ['title' => 'Anonymiser IP' , 'description' => 'Ne pas enregistrer les adresses IP', 'full' => 'Complet', 'partial' => 'Partiel', 'disabled' => 'Désactivé'], + 'sanitize_data' => ['title' => 'Désinfecter les données de formulaire' , 'description' => 'Désinfectez les données de formulaire et enregistrez le résultat dans la base de données', 'disabled' => 'Désactivé', 'htmlspecialchars' => 'Utilisez htmlspecialchars'], + 'recaptcha_enabled' => ['title' => 'Activer reCAPTCHA' , 'description' => 'Insère le widget reCAPTCHA sur votre formulaire'], + 'recaptcha_theme' => ['title' => 'Thème' , 'description' => 'Le thème de couleur du widget' , 'light' => 'Léger' , 'dark' => 'Sombre'], + 'recaptcha_type' => ['title' => 'Type' , 'description' => 'Le type de CAPTCHA à servir' , 'image' => 'Image' , 'audio' => 'Audio'], + 'recaptcha_size' => ['title' => 'Taille' , 'description' => 'La taille du widget' , 'normal' => 'Normale', 'compact' => 'Compacte'], + 'skip_database' => ['title' => 'Ignore la BDD' , 'description' => 'Ne pas stocker ce formulaire dans la base de données. Utile si vous souhaitez utiliser des évènements avec votre plugin personnalisé.'], + 'uploader_enable' => ['title' => 'Autoriser les téléchargements' , 'description' => 'Activer le téléchargement des fichiers. Vous devez activer cette option explicitement comme mesure de sécurité.'], + 'uploader_multi' => ['title' => 'Fichiers multiples' , 'description' => 'Autoriser plusieurs téléchargements de fichiers'], + 'uploader_pholder' => ['title' => 'Texte de remplacement' , 'description' => 'Texte à afficher quand aucun fichier n\'est téléchargé', 'default' => 'Cliquez pour choisir ou faites glisser les fichiers à télécharger'], + 'uploader_maxsize' => ['title' => 'Limite de taille de fichier' , 'description' => 'Taille maximale du fichier pouvant être téléchargée en mégaoctets'], + 'uploader_types' => ['title' => 'Types de fichiers autorisés' , 'description' => 'Extensions de fichiers autorisées ou étoile (*) pour tous les types (ajoutez une extension par ligne)'], + 'uploader_remFile' => ['title' => 'Texte de Suppression' , 'description' => 'Texte à afficher dans la popup lors de la suppression d\'un fichier', 'default' => 'Êtes-vous sûr ?'], + ] + ], + 'settings' => [ + 'tabs' => ['general' => 'Général', 'recaptcha' => 'reCAPTCHA', 'gdpr' => 'RGPD'], + 'section_flash_messages' => 'Messages Flash', + 'global_messages_success' => ['label' => 'Message de réussite', 'comment' => '(Ce paramètre peut être remplacé par le composant)', 'default' => 'Votre formulaire a été envoyé avec succès'], + 'global_messages_errors' => ['label' => 'Global Errors Message' , 'comment' => '(Ce paramètre peut être remplacé par le composant)', 'default' => 'Il y a eu des erreurs dans votre formulaire'], + 'plugin_help' => 'Vous pouvez accéder à la documentation du plugin sur GitHub :', + 'global_hide_button' => 'Masquer l\'élément de navigation', + 'global_hide_button_desc' => 'Utile si vous souhaitez utiliser des événements avec votre plugin personnalisé.', + 'section_recaptcha' => 'Paramètres reCAPTCHA', + 'recaptcha_site_key' => 'Clé du site', + 'recaptcha_secret_key' => 'Clé secrète', + 'gdpr_help_title' => 'Information', + 'gdpr_help_comment' => 'Nouvelle loi RGPD/GDPR en Europe : vous ne pouvez pas conserver les enregistrements indéfiniment, vous devez les effacer au bout d\'un certain temps en fonction de vos besoins.', + 'gdpr_enable' => 'Activer RGPD', + 'gdpr_days' => 'Garder les enregistrements pour un maximum de X jours', + ], + 'permissions' => [ + 'tab' => 'Magic Forms', + 'access_records' => 'Accéder aux données des formulaires stockés', + 'access_exports' => 'Accès aux exports des formulaires stockés', + 'access_settings' => 'Accès à la configuration du module', + 'gdpr_cleanup' => 'Accès au nettoyage de la base de donnée RGPD', + ], + 'mails' => [ + 'form_notification' => ['description' => 'Notifier quand un formulaire est envoyé'], + 'form_autoresponse' => ['description' => 'Auto-Réponse lorsqu\'un formulaire est envoyé'], + ], + 'validation' => [ + 'recaptcha_error' => 'Impossible de valider le champ reCAPTCHA' + ], + 'classes' => [ + 'GDPR' => [ + 'alert_gdpr_disabled' => 'LEs options RGPD sont désactivés', + 'alert_invalid_gdpr' => 'Valeur de réglage des jours RGPD invalide', + ] + ] + ]; +?> diff --git a/plugins/martin/forms/lang/pt-br/lang.php b/plugins/martin/forms/lang/pt-br/lang.php new file mode 100644 index 0000000..7329b96 --- /dev/null +++ b/plugins/martin/forms/lang/pt-br/lang.php @@ -0,0 +1,116 @@ + [ + 'name' => 'Magic Forms', + 'description' => 'Crie formulários fácilmente com AJAX' + ], + 'menu' => [ + 'label' => 'Formulários Mágico', + 'records' => ['label' => 'Registros'], + 'exports' => ['label' => 'Exportar'], + 'settings' => 'Configurar parâmetros do plug-in', + ], + 'controllers' => [ + 'records' => [ + 'title' => 'Ver registos', + 'view_title' => 'Detalhes do registro', + 'error' => 'Registro não encontrado', + 'deleted' => 'Registro excluído com sucesso', + 'columns' => [ + 'id' => 'ID do registro', + 'group' => 'Grupo', + 'ip' => 'Endereço IP', + 'form_data' => 'Campos armazenados', + 'files' => 'Arquivos anexados', + 'created_at' => 'Criado', + ], + ], + 'exports' => [ + 'title' => 'Exportar registros', + 'breadcrumb' => 'Exportar', + 'filter_section' => '1. Filtrar registos', + 'filter_type' => 'Exportar todos os registros', + 'filter_groups' => 'Grupos', + 'filter_date_after' => 'Data após', + 'filter_date_before' => 'Data anterior', + 'options_section' => '2. Opções extras', + 'options_metadata' => 'Incluir metadados', + 'options_metadata_com' => 'Exportar registros com metadados (ID de registro, grupo, IP, data criação)', + 'options_deleted' => 'Incluir registros excluídos', + ], + ], + 'components' => [ + 'generic_form' => [ + 'name' => 'Formulário AJAX Genérico', + 'description' => 'Por padrão renderiza um formulário genérico; Substitua o componente HTML com seus campos personalizados.', + ], + 'upload_form' => [ + 'name' => 'formulário upload AJAX [BETA]', + 'description' => 'Mostra como implementar uploads de arquivos no formulário.', + ], + 'empty_form' => [ + 'name' => 'Formulário AJAX vazio', + 'description' => 'Crie um modelo vazio para seu formulário personalizado; Substituir o componente HTML.', + ], + 'shared' => [ + 'csrf_error' => 'A sessão do formulário expirou! Atualize a página.', + 'recaptcha_warn' => 'Aviso: reCAPTCHA não está configurado corretamente. Por favor, vá para Back-end> Configurações> CMS> Magic Forms e configure.', + 'group_validation' => 'Validação de formulários', + 'group_messages' => 'Mensagens Flash', + 'group_mail' => 'Configurações de Notificações', + 'group_mail_resp' => 'Configurações de Resposta Automática', + 'group_settings' => 'Mais configurações', + 'group_security' => 'Segurança', + 'group_recaptcha' => 'Configurações de reCAPTCHA', + 'group_uploader' => 'Configurações do Uploader', + 'validation_req' => 'A propriedade é obrigatória', + 'group' => ['title' => 'Grupo' , 'description' => 'Organize seus formulários com um nome de grupo personalizado. Esta opção é útil ao exportar dados.'], + 'rules' => ['title' => 'Regras' , 'description' => 'Defina suas próprias regras usando a validação Laravel'], + 'rules_messages' => ['title' => 'Mensagens de Regras' , 'description' => 'Use suas próprias mensagens de regras usando a validação do Laravel'], + 'messages_success' => ['title' => 'Sucesso' , 'description' => 'Mensagem quando o formulário é enviado com sucesso', 'default '=>' Seu formulário foi enviado com sucesso'], + 'messages_errors' => ['title' => 'Erros' , 'description' => 'Mensagem quando o formulário contém erros', 'default' => 'Ocorreu um erro no envio'], + 'mail_enabled' => ['title' => 'Enviar Notificações' , 'description' => 'Enviar notificações por email em todos os formulários enviados'], + 'mail_subject' => ['title' => 'Assunto' , 'description' => 'Substitui o assunto de email padrão'], + 'mail_recipients' => ['title' => 'Destinatários' , 'description' => 'Especifique destinatários de email (adicione um endereço por linha)'], + 'mail_uploads' => ['title' => 'Enviar Uploads' , 'description' => 'Enviar uploads como anexos'], + 'mail_resp_enabled' => ['title' => 'Enviar Resposta Automática' , 'description' => 'Enviar um email de resposta automática para a pessoa que envia o formulário'], + 'mail_resp_field' => ['title' => 'Campo de Email' , 'description' => 'Campo de formulário que contém o endereço de email do destinatário da resposta automática'], + 'mail_resp_from' => ['title' => 'Endereço do Remetente' , 'description' => 'Endereço de email do remetente de e-mail de resposta automática (por exemplo, noreply@yourcompany.com)'], + 'mail_resp_subject' => ['title' => 'Assunto' , 'description' => 'Substitui assunto de e-mail padrão'], + 'reset_form' => ['title' => 'Limpar Fomulário' , 'description' => 'Limpar o formulário após o envio com sucesso'], + 'allowed_fields' => ['title' => 'Campos permitidos' , 'description' => 'Especifique quais campos devem ser filtrados e armazenados (adicione um nome de campo por linha)'], + 'recaptcha_enabled' => ['title' => 'Habiltar reCAPTCHA' , 'description' => 'Inserir o widget reCAPTCHA no seu formulário'], + 'recaptcha_theme' => ['title' => 'Tema' , 'description' => 'Cor do tema do widget', 'light' => 'Claro' , 'dark' => 'Escuro'], + 'recaptcha_type' => ['title' => 'Tipo' , 'description' => 'O tipo de CAPTCHA para servir' , 'image' => 'Imagem' , 'audio' => 'Áudio'], + 'recaptcha_size' => ['title' => 'Tamanho' , 'description' => 'O tamanho do widget' , 'normal' => 'Normal', 'compact' => 'Compacto'], + 'uploader_enable' => ['title' => 'Permitir Uploads' , 'description' => 'Ativar o upload de arquivos. Você precisa ativar essa opção explicitamente como uma medida de segurança.'], + 'uploader_multi' => ['title' => 'Multiplos Arquivos' , 'description' => 'Permitir multiplos uploads de arquivos'], + 'uploader_pholder' => ['title' => 'texto do Placeholder' , 'description' => 'Texto a ser exibido quando nenhum arquivo é carregado', 'default' => 'Clique ou arraste os arquivos para fazer o upload'], + 'uploader_maxsize' => ['title' => 'Limite do Tamanho do Arquivo' , 'description' => 'O tamanho máximo de arquivo que pode ser carregado em megabytes'], + 'uploader_types' => ['title' => 'Tipos de Arquivos Permitidos' , 'description' => 'Extensões de arquivo permitido ou asterisco (*) para todos os tipos (adicionar uma extensão por linha)'], + ] + ], + 'settings' => [ + 'section_flash_messages' => 'Mensagens Flash', + 'global_messages_success' => ['label' => 'Mensagem de sucesso global' , 'comment' => '(Esta definição pode ser substituída a partir do componente)', 'default' => 'Seu formulário foi enviado com sucesso'], + 'global_messages_errors' => ['label' => 'Mensagem de Erros Global' , 'comment' => '(Esta definição pode ser substituída a partir do componente)', 'default' => 'Ocorreu um erro o envio do formulário'], + 'section_recaptcha' => 'Configurações de reCAPTCHA', + 'recaptcha_site_key' => 'Chave do site', + 'recaptcha_secret_key' => 'Chave secreta', + ], + 'permissions' => [ + 'tab' => 'Formulários Mágico', + 'access_records' => 'Acessar dados de formulários armazenados', + 'access_exports' => 'Acesso à exportação de dados armazenados', + 'access_settings' => 'Acesso do módulo de configuração', + ], + 'mails' => [ + 'form_notification' => ['description' => 'Notificar quando um formulário é enviado'], + 'form_autoresponse' => ['description' => 'Resposta automática quando um formulário é enviado'], + ], + 'validation' => [ + 'recaptcha_error' => 'Não é possível validar o campo reCAPTCHA' + ], + ]; +?> diff --git a/plugins/martin/forms/lang/ru/lang.php b/plugins/martin/forms/lang/ru/lang.php new file mode 100644 index 0000000..c2dad15 --- /dev/null +++ b/plugins/martin/forms/lang/ru/lang.php @@ -0,0 +1,170 @@ + [ + 'name' => 'Magic Forms', + 'description' => 'С легкостью создайте AJAX форму' + ], + + 'menu' => [ + 'label' => 'Magic Forms', + 'records' => ['label' => 'Записи'], + 'exports' => ['label' => 'Экспорт'], + 'settings' => 'Настройки плагина', + ], + + 'controllers' => [ + 'records' => [ + 'title' => 'Все заявки', + 'view_title' => 'Запись', + 'error' => 'Запись не найдена', + 'deleted' => 'Запись успешно удалена', + 'columns' => [ + 'id' => 'ID записи', + 'group' => 'Группа', + 'ip' => 'IP адрес', + 'form_data' => 'Поля формы', + 'files' => 'Прикрепленные файлы', + 'created_at' => 'Создано', + ], + 'buttons' => [ + 'read' => 'Отметить как прочитано', + 'unread' => 'Отметить как непрочитанное', + 'gdpr_clean' => 'Очистить GDPR', + ], + 'alerts' => [ + 'gdpr_confirm' => "Вы действительно хотите удалить эти старые записи?\nЭто действие не может быть отменено!", + 'gdpr_success' => 'Очистка GDPR успешна', + 'gdpr_perms' => 'У вас нет прав использования этой функции', + ], + ], + 'exports' => [ + 'title' => 'Экспорт записей', + 'breadcrumb' => 'Экспорт', + 'filter_section' => '1. Фильтровать записи', + 'filter_type' => 'Экспорт всех записей', + 'filter_groups' => 'Группы', + 'filter_date_after' => 'Дата от', + 'filter_date_before' => 'дата до', + 'options_section' => '2. Экстра опции', + 'options_metadata' => 'Вложить метаданные', + 'options_metadata_com' => 'Экспорт записей с метаданными(ID, группа, дата создания)', + 'options_deleted' => 'Вложить удаленные записи', + 'options_delimiter' => 'Использовать альтернативный разделитель полей', + 'options_delimiter_com' => 'Точка с запятой вместо запятой', + 'options_utf' => 'Кодировать в UTF8', + 'options_utf_com' => 'Ваш CSV файл будет закодирван в UTF-8', + ], + ], + + 'components' => [ + 'generic_form' => [ + 'name' => 'Стандартная AJAX форма', + 'description' => 'Выводит стандартную форму; Перезапишите HTML компонент со своей формой.', + ], + 'upload_form' => [ + 'name' => 'AJAX форма с файлами', + 'description' => '[BETA] Форма с возможностью прикрепления файлов.', + ], + 'empty_form' => [ + 'name' => 'Пустая AJAX форма', + 'description' => 'Создает пустой плейсхолдер для вашей формы, который вы должны перезаписать своим HTML кодом.', + ], + 'shared' => [ + 'csrf_error' => 'Сессия формы истекла! Перезагрузите страницу.', + 'recaptcha_warn' => 'Внимание: reCAPTCHA настроена не правильно. Пожалуйста, перейдите в Настройки > CMS > Magic Forms и заполните все обязательные поля.', + 'group_validation' => 'Валидация формы', + 'group_messages' => 'Флэш-сообщения', + 'group_mail' => 'Настройки уведомлений', + 'group_mail_resp' => 'Настройки авто-ответа', + 'group_settings' => 'Остальные настройки', + 'group_security' => 'Безопасность', + 'group_recaptcha' => 'Настройки reCAPTCHA', + 'group_advanced' => 'Продвинутые настройки', + 'group_uploader' => 'Настройки файлового загрузчика', + 'validation_req' => 'Этот атрибут обязателен', + 'group' => ['title' => 'Группа' , 'description' => 'Организуйте свои формы по группам. Эта опция полезна для экспорта записей.'], + 'rules' => ['title' => 'Правила' , 'description' => 'Используйте валидацию Laravel для установки своих правил обработки полей.'], + 'rules_messages' => ['title' => 'Сообщения от правил', 'description' => 'Используйте свои собственные сообщения от кастомных валидаций.'], + 'custom_attributes' => ['title' => 'Кастомные атрибуты' , 'description' => 'Используйте кастомные атрибуты в своих правилах валидаций.'], + 'messages_success' => ['title' => 'Успешно' , 'description' => 'Сообщение об успешной отправке формы', 'default' => 'Ваша форма была успешно отправлена!'], + 'messages_errors' => ['title' => 'Ошибки' , 'description' => 'Сообщение с ошибками формы' , 'default' => 'В вашей заявке содержатся ошибки.'], + 'messages_partial' => ['title' => 'Кастомный фрагмент' , 'description' => 'Переопределить стандартные уведомления кастомным фрагментом из вашей фронтенд темы.'], + 'mail_enabled' => ['title' => 'Отправка уведомлений','description' => 'Отправлять увдеомления по email после каждого успешного заполенния формы.'], + 'mail_subject' => ['title' => 'Тема письма' , 'description' => 'Переопределить тему письма по умолчанию'], + 'mail_recipients' => ['title' => 'Получатели' , 'description' => 'Укажите получателей электронной почты (По одному адресу в строке)'], + 'mail_bcc' => ['title' => 'BCC' , 'description' => 'Отправить копию получателям электронной почты (По одному адресу в строке)'], + 'mail_replyto' => ['title' => 'ReplyTo поле email' , 'description' => 'Поле формы, содержащее адрес электронной почты отправителя, который будет использоваться как "ReplyTo"'], + 'mail_template' => ['title' => 'Шаблон письма' , 'description' => 'Используйте собственный шаблон письма. Укажите код шаблона, например, "martin.forms::mail.notification" (находится в разделе Настройки -> Почтовые шаблоны). Оставьте пустым, чтобы использовать по умолчанию.'], + 'mail_uploads' => ['title' => 'Прикреплять файлы' , 'description' => 'Прикреплять файлы к письму'], + 'mail_resp_enabled' => ['title' => 'Отправить авто-ответ','description' => 'Отправить автоответчик на email отправителя.'], + 'mail_resp_field' => ['title' => 'Email поле' , 'description' => 'Имя поля с email отправителя'], + 'mail_resp_from' => ['title' => 'Email адрес отправителя', 'description' => 'Адрес электронной почты отправителя электронной почты с автоматическим ответом (например, noreply@yourcompany.com)'], + 'mail_resp_subject' => ['title' => 'Тема пиьсма' , 'description' => 'Переопределить тему письма по умолчанию'], + 'reset_form' => ['title' => 'Сбросить форму' , 'description' => 'Сбросить форму после успешной отправки'], + 'redirect' => ['title' => 'Редирект' , 'description' => 'Редирект на URL после успешной отправки формы.'], + 'inline_errors' => ['title' => 'Встроенные ошибки' , 'description' => 'Отображать встроенные ошибки. Это требует дополнительного кода, проверьте документацию для получения дополнительной информации.', 'disabled' => 'Отключено', 'display' => 'Отображать ошибки', 'variable' => 'JS переменная'], + 'js_on_success' => ['title' => '"Успешный" JS' , 'description' => 'Выполнияет скрипт после успешной отправки формы. Не используйте