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 0000000..bec4988
Binary files /dev/null and b/plugins/martin/forms/assets/imgs/upload.png differ
diff --git a/plugins/martin/forms/assets/js/inline-errors.js b/plugins/martin/forms/assets/js/inline-errors.js
new file mode 100644
index 0000000..eb7d34b
--- /dev/null
+++ b/plugins/martin/forms/assets/js/inline-errors.js
@@ -0,0 +1,7 @@
+$(window).on('ajaxInvalidField', function(event, fieldElement, fieldName, errorMsg, isFirst) {
+ $(fieldElement).closest('.form-group').addClass('has-error');
+});
+
+$(document).on('ajaxPromise', '[data-request]', function() {
+ $(this).closest('form').find('.form-group.has-error').removeClass('has-error');
+});
\ No newline at end of file
diff --git a/plugins/martin/forms/assets/js/recaptcha.js b/plugins/martin/forms/assets/js/recaptcha.js
new file mode 100644
index 0000000..753c207
--- /dev/null
+++ b/plugins/martin/forms/assets/js/recaptcha.js
@@ -0,0 +1,12 @@
+var captchas = [];
+
+var onloadCallback = function() {
+ jQuery('.g-recaptcha').each(function(index, el) {
+ captchas[el.id] = grecaptcha.render(el, $(el).data());
+ });
+}
+
+function resetReCaptcha(id) {
+ var widget = captchas[id];
+ grecaptcha.reset(widget);
+}
\ No newline at end of file
diff --git a/plugins/martin/forms/assets/js/uploader.js b/plugins/martin/forms/assets/js/uploader.js
new file mode 100644
index 0000000..de3b7b8
--- /dev/null
+++ b/plugins/martin/forms/assets/js/uploader.js
@@ -0,0 +1,360 @@
+/*
+ * File upload form field control
+ *
+ * Data attributes:
+ * - data-control="fileupload" - enables the file upload plugin
+ * - data-unique-id="XXX" - an optional identifier for multiple uploaders on the same page, this value will
+ * appear in the postback variable called X_OCTOBER_FILEUPLOAD
+ * - data-template - a Dropzone.js template to use for each item
+ *
+ * JavaScript API:
+ * $('div').fileUploader()
+ *
+ * Dependancies:
+ * - Dropzone.js
+ */
++function ($) { "use strict";
+
+ // FILEUPLOAD CLASS DEFINITION
+ // ============================
+
+ var FileUpload = function (element, options) {
+ this.$el = $(element)
+ this.options = options || {}
+
+ this.init()
+ }
+
+ FileUpload.prototype.init = function() {
+ if (this.options.isMulti === null) {
+ this.options.isMulti = this.$el.hasClass('is-multi')
+ }
+
+ if (this.options.isPreview === null) {
+ this.options.isPreview = this.$el.hasClass('is-preview')
+ }
+
+ this.$uploadButton = $('.upload-button', this.$el)
+ this.$filesContainer = $('.upload-files-container', this.$el)
+ this.uploaderOptions = {}
+
+ this.$el.on('click', '.upload-object.is-success', $.proxy(this.onClickSuccessObject, this))
+ this.$el.on('click', '.upload-object.is-error', $.proxy(this.onClickErrorObject, this))
+
+ // Stop here for preview mode
+ if (this.options.isPreview)
+ return
+
+ this.$el.on('click', '.upload-remove-button', $.proxy(this.onRemoveObject, this))
+
+ this.bindUploader()
+ }
+
+ FileUpload.prototype.dispose = function() {
+
+ this.$el.off('click', '.upload-object.is-success', $.proxy(this.onClickSuccessObject, this))
+ this.$el.off('click', '.upload-object.is-error', $.proxy(this.onClickErrorObject, this))
+ this.$el.off('click', '.upload-remove-button', $.proxy(this.onRemoveObject, this))
+
+ this.$el.removeData('oc.fileUpload')
+
+ this.$el = null
+ this.$uploadButton = null
+ this.$filesContainer = null
+ this.uploaderOptions = null
+
+ // In some cases options could contain callbacks,
+ // so it's better to clean them up too.
+ this.options = null
+ }
+
+ //
+ // Uploading
+ //
+
+ FileUpload.prototype.bindUploader = function() {
+ this.uploaderOptions = {
+ url: this.options.url,
+ paramName: this.options.paramName,
+ clickable: this.$uploadButton.get(0),
+ previewsContainer: this.$filesContainer.get(0),
+ maxFiles: !this.options.isMulti ? 1 : null,
+ headers: {}
+ }
+
+ if (this.options.fileTypes) {
+ this.uploaderOptions.acceptedFiles = this.options.fileTypes
+ }
+
+ if (this.options.template) {
+ this.uploaderOptions.previewTemplate = $(this.options.template).html()
+ }
+
+ if (this.options.uniqueId) {
+ this.uploaderOptions.headers['X-OCTOBER-FILEUPLOAD'] = this.options.uniqueId
+ }
+
+ this.uploaderOptions.thumbnailWidth = this.options.thumbnailWidth
+ ? this.options.thumbnailWidth : null
+
+ this.uploaderOptions.thumbnailHeight = this.options.thumbnailHeight
+ ? this.options.thumbnailHeight : null
+
+ this.uploaderOptions.resize = this.onResizeFileInfo
+
+ /*
+ * Add CSRF token to headers
+ */
+ var token = $('meta[name="csrf-token"]').attr('content')
+ if (token) {
+ this.uploaderOptions.headers['X-CSRF-TOKEN'] = token
+ }
+
+ this.dropzone = new Dropzone(this.$el.get(0), this.uploaderOptions)
+ this.dropzone.on('addedfile', $.proxy(this.onUploadAddedFile, this))
+ this.dropzone.on('sending', $.proxy(this.onUploadSending, this))
+ this.dropzone.on('success', $.proxy(this.onUploadSuccess, this))
+ this.dropzone.on('error', $.proxy(this.onUploadError, this))
+
+ var _this = this;
+
+ this.dropzone.on("queuecomplete", function (file) {
+ if (this.getUploadingFiles().length === 0 && this.getQueuedFiles().length === 0) {
+ $.event.trigger({
+ uploader: _this,
+ type : "uploadfinished",
+ message : "Dropzone Files upload finished",
+ });
+ }
+ });
+
+ this.dropzone.on("sending", function (file) {
+ $.event.trigger({
+ uploader: _this,
+ type : "uploadstarted",
+ message : "Dropzone File upload started",
+ });
+ });
+
+ }
+
+ FileUpload.prototype.onResizeFileInfo = function(file) {
+ var info,
+ targetWidth,
+ targetHeight
+
+ if (!this.options.thumbnailWidth && !this.options.thumbnailWidth) {
+ targetWidth = targetHeight = 100
+ }
+ else if (this.options.thumbnailWidth) {
+ targetWidth = this.options.thumbnailWidth
+ targetHeight = this.options.thumbnailWidth * file.height / file.width
+ }
+ else if (this.options.thumbnailHeight) {
+ targetWidth = this.options.thumbnailHeight * file.height / file.width
+ targetHeight = this.options.thumbnailHeight
+ }
+
+ // drawImage(image, srcX, srcY, srcWidth, srcHeight, trgX, trgY, trgWidth, trgHeight) takes an image, clips it to
+ // the rectangle (srcX, srcY, srcWidth, srcHeight), scales it to dimensions (trgWidth, trgHeight), and draws it
+ // on the canvas at coordinates (trgX, trgY).
+ info = {
+ srcX: 0,
+ srcY: 0,
+ srcWidth: file.width,
+ srcHeight: file.height,
+ trgX: 0,
+ trgY: 0,
+ trgWidth: targetWidth,
+ trgHeight: targetHeight
+ }
+
+ return info
+ }
+
+ FileUpload.prototype.onUploadAddedFile = function(file) {
+ var $object = $(file.previewElement).data('dzFileObject', file)
+
+ // Remove any exisiting objects for single variety
+ if (!this.options.isMulti) {
+ this.removeFileFromElement($object.siblings())
+ }
+
+ this.evalIsPopulated()
+ }
+
+ FileUpload.prototype.onUploadSending = function(file, xhr, formData) {
+ this.addExtraFormData(formData)
+ }
+
+ FileUpload.prototype.onUploadSuccess = function(file, response) {
+ var $preview = $(file.previewElement),
+ $img = $('.image img', $preview)
+
+ $preview.addClass('is-success')
+
+ if (response.id) {
+ $preview.data('id', response.id)
+ $preview.data('path', response.path)
+ $('.upload-remove-button', $preview).data('request-data', { file_id: response.id })
+ $img.attr('src', response.thumb)
+ }
+
+ /*
+ * Trigger change event (Compatability with october.form.js)
+ */
+ this.$el.closest('[data-field-name]').trigger('change.oc.formwidget')
+ }
+
+ FileUpload.prototype.onUploadError = function(file, error) {
+ var $preview = $(file.previewElement)
+ $preview.addClass('is-error')
+ }
+
+ FileUpload.prototype.addExtraFormData = function(formData) {
+ if (this.options.extraData) {
+ $.each(this.options.extraData, function (name, value) {
+ formData.append(name, value)
+ })
+ }
+
+ var $form = this.$el.closest('form')
+ if ($form.length > 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 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 = "";
+ 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 @@
+
\ 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 title %}
+
{{ title }}
+ {% endif %}
+
+ {% if content %}
+
{{ content }}
+ {% endif %}
+
+ {% if list %}
+
+ {% for item in list %}
+ {{ item }}
+ {% endfor %}
+
+ {% endif %}
+
+
+
+ {% 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
+
+
+
+ Name:
+
+
+
+
+ Email:
+
+
+
+
+ Position:
+
+
+ Lead
+ Senior
+ Direct
+ Corporate
+ Dynamic
+
+
+
+
+ Skills:
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+ {{ __SELF__.placeholderText }}
+
+
+
+
+ {% 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 %}
+
+
+
+
+
+
+
+
+ Browse
+
+
+
+
+ {% 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() ?>
+
+
+ = Form::submit(e(trans('martin.forms::lang.controllers.exports.title')), ['class' => '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 @@
+= $this->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 @@
+
+ disabled="disabled"
+
+ data-request=""
+ data-request-confirm=""
+ data-request-success=""
+
+ onclick=""
+
+
+ data-trigger-action="enable"
+ data-trigger=".control-list input[type=checkbox]"
+ data-trigger-condition="checked"
+
+ data-stripe-load-indicator>
+
+
\ 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 @@
+
+
+
+
+= $this->makePartial('partials/view_toolbar') ?>
+
+Record #id ?>
+
+
+form_data_arr as $label => $value): ?>
+
+ :
+
+
+
+
+
+
+
+
+
+files) > 0): ?>
+
+ Attached Files:
+
+
+
+
+
+
+
+
+
: group ?>
+
: ip ?>
+
: created_at ?>
+
\ 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' => 'Выполнияет скрипт после успешной отправки формы. Не используйте