├── .github └── ISSUE_TEMPLATE │ ├── bug-report.md │ └── config.yml ├── LICENCE.md ├── Plugin.php ├── README.md ├── assets ├── css │ └── records.css ├── imgs │ └── icon.svg └── js │ ├── inline-errors.js │ └── recaptcha.js ├── classes ├── BackendHelpers.php ├── FilePond │ ├── FilePond.php │ ├── FilePondController.php │ ├── InvalidPathException.php │ └── LaravelFilepondException.php ├── GDPR.php ├── MagicForm.php ├── Mails │ ├── AutoResponse.php │ ├── Mailable.php │ └── Notification.php ├── ReCaptcha.php ├── ReCaptchaValidator.php ├── SharedProperties.php └── UnreadRecords.php ├── components ├── EmptyForm.php ├── FilePondForm.php ├── GenericForm.php ├── emptyform │ └── default.htm ├── filepondform │ └── default.htm ├── genericform │ └── default.htm └── partials │ ├── filepond.htm │ ├── flash.htm │ ├── js │ ├── recaptcha.htm │ └── reset-form.htm │ └── recaptcha.htm ├── composer.json ├── controllers ├── Exports.php ├── Records.php ├── exports │ ├── config_form.yaml │ └── index.htm └── records │ ├── config_filter.yaml │ ├── config_list.yaml │ ├── index.htm │ ├── partials │ ├── _action_button.htm │ ├── _list_toolbar.htm │ └── _view_toolbar.htm │ └── view.htm ├── lang ├── de │ └── lang.php ├── en │ └── lang.php ├── fr │ └── lang.php ├── nl │ └── lang.php ├── pt-br │ └── lang.php ├── ru │ └── lang.php ├── tr │ └── lang.php └── zh-cn │ └── lang.php ├── models ├── Record.php ├── Settings.php ├── export │ ├── _header.htm │ └── fields.yaml ├── record │ ├── columns.yaml │ └── fields │ │ ├── _files_fields.htm │ │ └── _stored_fields.htm └── settings │ ├── _gdpr_help.htm │ ├── _plugin_help.htm │ ├── _recaptcha_help.htm │ └── fields.yaml ├── phpunit.xml ├── routes.php ├── tests └── unit │ └── classes │ ├── BackendHelpersTest.php │ ├── GDPRTest.php │ └── UnreadRecordsTest.php ├── updates ├── add_group_field.php ├── add_unread_field.php ├── create_records_table.php └── version.yaml └── views └── mail ├── autoresponse.htm └── notification.htm /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report for Magic Forms 4 | 5 | --- 6 | 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Ask a question 4 | url: https://github.com/skydiver/wn-magic-forms/discussions 5 | about: Ask questions and discuss with other community members 6 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | # MIT license 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Plugin.php: -------------------------------------------------------------------------------- 1 | 'martin.forms::lang.plugin.name', 21 | 'description' => 'martin.forms::lang.plugin.description', 22 | 'author' => 'Martin M.', 23 | 'icon' => 'icon-bolt', 24 | 'homepage' => 'https://github.com/skydiver/' 25 | ]; 26 | } 27 | 28 | public function registerNavigation() 29 | { 30 | if (Settings::get('global_hide_button', false)) { 31 | return; 32 | } 33 | 34 | return [ 35 | 'forms' => [ 36 | 'label' => 'martin.forms::lang.menu.label', 37 | 'icon' => 'icon-bolt', 38 | 'iconSvg' => 'plugins/martin/forms/assets/imgs/icon.svg', 39 | 'url' => BackendHelpers::getBackendURL(['martin.forms.access_records' => 'martin/forms/records', 'martin.forms.access_exports' => 'martin/forms/exports'], 'martin.forms.access_records'), 40 | 'permissions' => ['martin.forms.*'], 41 | 'sideMenu' => [ 42 | 'records' => [ 43 | 'label' => 'martin.forms::lang.menu.records.label', 44 | 'icon' => 'icon-database', 45 | 'url' => Backend::url('martin/forms/records'), 46 | 'permissions' => ['martin.forms.access_records'], 47 | 'counter' => UnreadRecords::getTotal(), 48 | 'counterLabel' => 'Un-Read Messages' 49 | ], 50 | 'exports' => [ 51 | 'label' => 'martin.forms::lang.menu.exports.label', 52 | 'icon' => 'icon-download', 53 | 'url' => Backend::url('martin/forms/exports'), 54 | 'permissions' => ['martin.forms.access_exports'] 55 | ], 56 | ] 57 | ] 58 | ]; 59 | } 60 | 61 | public function registerSettings() 62 | { 63 | return [ 64 | 'config' => [ 65 | 'label' => 'martin.forms::lang.menu.label', 66 | 'description' => 'martin.forms::lang.menu.settings', 67 | 'category' => SettingsManager::CATEGORY_CMS, 68 | 'icon' => 'icon-bolt', 69 | 'class' => 'Martin\Forms\Models\Settings', 70 | 'permissions' => ['martin.forms.access_settings'], 71 | 'order' => 500 72 | ] 73 | ]; 74 | } 75 | 76 | public function registerPermissions() 77 | { 78 | return [ 79 | 'martin.forms.access_settings' => ['tab' => 'martin.forms::lang.permissions.tab', 'label' => 'martin.forms::lang.permissions.access_settings'], 80 | 'martin.forms.access_records' => ['tab' => 'martin.forms::lang.permissions.tab', 'label' => 'martin.forms::lang.permissions.access_records'], 81 | 'martin.forms.access_exports' => ['tab' => 'martin.forms::lang.permissions.tab', 'label' => 'martin.forms::lang.permissions.access_exports'], 82 | 'martin.forms.gdpr_cleanup' => ['tab' => 'martin.forms::lang.permissions.tab', 'label' => 'martin.forms::lang.permissions.gdpr_cleanup'], 83 | ]; 84 | } 85 | 86 | public function registerComponents() 87 | { 88 | return [ 89 | 'Martin\Forms\Components\GenericForm' => 'genericForm', 90 | 'Martin\Forms\Components\FilePondForm' => 'filepondForm', 91 | 'Martin\Forms\Components\EmptyForm' => 'emptyForm', 92 | ]; 93 | } 94 | 95 | public function registerMailTemplates() 96 | { 97 | return [ 98 | 'martin.forms::mail.notification' => Lang::get('martin.forms::lang.mails.form_notification.description'), 99 | 'martin.forms::mail.autoresponse' => Lang::get('martin.forms::lang.mails.form_autoresponse.description'), 100 | ]; 101 | } 102 | 103 | public function register() 104 | { 105 | $this->app->resolving('validator', function () { 106 | Validator::extend('recaptcha', 'Martin\Forms\Classes\ReCaptchaValidator@validateReCaptcha'); 107 | }); 108 | } 109 | 110 | public function registerSchedule($schedule) 111 | { 112 | $schedule->call(function () { 113 | GDPR::cleanRecords(); 114 | })->daily(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://banners.beyondco.de/Magic%20Forms%20for%20WinterCMS.png?theme=light&packageManager=composer+require&packageName=martin%2Fwn-forms-plugin&pattern=architect&style=style_1&description=Create+easy+%28and+almost+magic%29+forms&md=1&showWatermark=0&fontSize=100px&images=lightning-bolt) 2 | 3 | 4 | ## Why Magic Forms? 5 | Almost everyday we do forms for our clients, personal projects, etc 6 | 7 | Sometimes we need to add or remove fields, change validations, store data and at some point, this can be boring and repetitive. 8 | 9 | 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. 10 | 11 | 12 | ## Features 13 | * Create any type of form: contact, feedback, registration, uploads, etc 14 | * Write only HTML 15 | * Don't code forms logic 16 | * Laravel validation 17 | * Custom validation errors 18 | * Use multiple forms on same page 19 | * Store on database 20 | * Export data in CSV 21 | * Access database records from backend 22 | * Send mail notifications to multiple recipients 23 | * Auto-response email on form submit 24 | * reCAPTCHA validation 25 | * Support for Translate plugin 26 | * Inline errors with fields (read documentation for more info) 27 | * File uploads using Filepond 28 | 29 | 30 | ## Documentation 31 | Checkout our docs at: 32 | > https://magicforms.vercel.app/ 33 | -------------------------------------------------------------------------------- /assets/css/records.css: -------------------------------------------------------------------------------- 1 | .files-container ul { padding:0; list-style:none; } 2 | 3 | .record-table { width:100%; border-collapse:collapse; box-shadow:5px 5px 10px #AAA; } 4 | .record-table td { padding:10px; border:solid 1px #D4D8DA; } 5 | .record-label { width:20%; background-color:#ECF0F1; text-align:right; } 6 | .record-value { width:80%; background-color:#F9F9F9; } 7 | .record-value ul { margin:0; } 8 | .record-metadata { font-size:0.8em; color:#888; font-weight:bold; } -------------------------------------------------------------------------------- /assets/imgs/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | image/svg+xml -------------------------------------------------------------------------------- /assets/js/inline-errors.js: -------------------------------------------------------------------------------- 1 | $(window).on('ajaxInvalidField', function(event, fieldElement, fieldName, errorMsg, isFirst) { 2 | $(fieldElement).closest('.form-group').addClass('has-error'); 3 | }); 4 | 5 | $(document).on('ajaxPromise', '[data-request]', function() { 6 | $(this).closest('form').find('.form-group.has-error').removeClass('has-error'); 7 | }); -------------------------------------------------------------------------------- /assets/js/recaptcha.js: -------------------------------------------------------------------------------- 1 | var captchas = []; 2 | 3 | var onloadCallback = function() { 4 | jQuery('.g-recaptcha').each(function(index, el) { 5 | captchas[el.id] = grecaptcha.render(el, $(el).data()); 6 | }); 7 | } 8 | 9 | function resetReCaptcha(id) { 10 | var widget = captchas[id]; 11 | grecaptcha.reset(widget); 12 | } -------------------------------------------------------------------------------- /classes/BackendHelpers.php: -------------------------------------------------------------------------------- 1 | $URL) { 22 | if ($user->hasAccess($permission)) { 23 | return Backend::url($URL); 24 | } 25 | } 26 | return Backend::url($urls[$default]); 27 | } 28 | 29 | /** 30 | * Check if Translator plugin is installed 31 | * 32 | * @return boolean 33 | */ 34 | public static function isTranslatePlugin(): bool 35 | { 36 | return class_exists('\RainLab\Translate\Classes\Translator') && class_exists('\RainLab\Translate\Models\Message'); 37 | } 38 | 39 | /** 40 | * Render an array|object as HTML list (UL > LI) 41 | * 42 | * @param mixed $data List items 43 | * 44 | * @return string 45 | */ 46 | public static function array2ul($data): string 47 | { 48 | $return = ''; 49 | foreach ($data as $index => $item) { 50 | if (!is_string($item)) { 51 | $return .= '
  • ' . htmlspecialchars($index, ENT_QUOTES) . '
  • "; 52 | } else { 53 | $return .= '
  • '; 54 | if (is_object($data)) { 55 | $return .= htmlspecialchars($index, ENT_QUOTES) . ' - '; 56 | } 57 | $return .= htmlspecialchars($item, ENT_QUOTES) . '
  • '; 58 | } 59 | } 60 | return $return; 61 | } 62 | 63 | /** 64 | * Anonymize an IPv4 address 65 | * (credits: https://github.com/geertw/php-ip-anonymizer) 66 | * 67 | * @param string $address IPv4 address 68 | * 69 | * @return string Anonymized address 70 | */ 71 | public static function anonymizeIPv4(string $address): string 72 | { 73 | return inet_ntop(inet_pton($address) & inet_pton("255.255.255.0")); 74 | } 75 | 76 | /** 77 | * Extract string from curly braces 78 | * 79 | * @param string $pattern Pattern to replace 80 | * @param string $replacement Replacement string 81 | * @param string $subject Strings to replace 82 | * 83 | * @return string 84 | */ 85 | public static function replaceToken(string $pattern, string $replacement = null, string $subject): string 86 | { 87 | $pattern = '/{{\s*(' . $pattern . ')\s*}}/'; 88 | return preg_replace($pattern, $replacement, $subject); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /classes/FilePond/FilePond.php: -------------------------------------------------------------------------------- 1 | getTempPath(); 36 | 37 | if (!Str::startsWith($filePath, $tempPath)) { 38 | throw new InvalidPathException(); 39 | } 40 | 41 | return $filePath; 42 | } 43 | 44 | public function getTempPath() 45 | { 46 | $defaultPath = temp_path('magic-forms-temp'); 47 | 48 | if (File::exists($defaultPath)) { 49 | return $defaultPath; 50 | } 51 | 52 | File::makeDirectory($defaultPath); 53 | return $defaultPath; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /classes/FilePond/FilePondController.php: -------------------------------------------------------------------------------- 1 | filepond = $filepond; 27 | } 28 | 29 | /** 30 | * Uploads the file to the temporary directory 31 | * and returns an encrypted path to the file 32 | * 33 | * @param Request $request 34 | * @return \Illuminate\Http\Response 35 | */ 36 | public function upload(Request $request): \Illuminate\Http\Response 37 | { 38 | $field = $this->getUploadFieldName(); 39 | $input = $request->file($field); 40 | $this->file = is_array($input) ? $input[0] : $input; 41 | 42 | 43 | /** VALIDATE UPLOAD FILE SIZE */ 44 | if ($this->checkInvalidSize()) { 45 | $error = e(trans('martin.forms::lang.classes.FilePond.error_filesize')); 46 | return Response::make($error, 422, [ 47 | 'Content-Type' => 'text/plain', 48 | ]); 49 | } 50 | 51 | /** VALIDATE UPLOAD FILE TYPE */ 52 | if ($this->checkInvalidFile()) { 53 | $error = e(trans('martin.forms::lang.classes.FilePond.error_filetype')); 54 | return Response::make($error, 422, [ 55 | 'Content-Type' => 'text/plain', 56 | ]); 57 | } 58 | 59 | if ($input === null) { 60 | return Response::make($field . ' is required', 422, [ 61 | 'Content-Type' => 'text/plain', 62 | ]); 63 | } 64 | 65 | $filePath = $this->generateTempFilename(); 66 | $filePathParts = pathinfo($filePath); 67 | 68 | if (!$this->file->move($filePathParts['dirname'], $filePathParts['basename'])) { 69 | $error = e(trans('martin.forms::lang.classes.FilePond.error_savefile')); 70 | return Response::make($error, 500, [ 71 | 'Content-Type' => 'text/plain', 72 | ]); 73 | } 74 | 75 | return Response::make($this->filepond->getServerIdFromPath($filePath), 200, [ 76 | 'Content-Type' => 'text/plain', 77 | ]); 78 | } 79 | 80 | /** 81 | * Takes the given encrypted filepath and deletes 82 | * it if it hasn't been tampered with 83 | * 84 | * @param Request $request 85 | * @return mixed 86 | */ 87 | public function delete(Request $request): \Illuminate\Http\Response 88 | { 89 | $filePath = $this->filepond->getPathFromServerId($request->getContent()); 90 | 91 | if (unlink($filePath)) { 92 | return Response::make('', 200, [ 93 | 'Content-Type' => 'text/plain', 94 | ]); 95 | } 96 | 97 | return Response::make('', 500, [ 98 | 'Content-Type' => 'text/plain', 99 | ]); 100 | } 101 | 102 | /** 103 | * Get field name used for uploads 104 | * 105 | * @return string 106 | */ 107 | private function getUploadFieldName(): string 108 | { 109 | return request()->headers->get('FILEPOND-FIELD'); 110 | } 111 | 112 | /** 113 | * Generate unique temporary filename 114 | * 115 | * @return string 116 | */ 117 | private function generateTempFilename(): string 118 | { 119 | return vsprintf('%s%s%s__%s', [ 120 | $this->filepond->getTempPath(), 121 | DIRECTORY_SEPARATOR, 122 | str_random(8), 123 | $this->file->getClientOriginalName() 124 | ]); 125 | } 126 | 127 | /** 128 | * Check if uploaded file has a valid size 129 | * 130 | * @return boolean 131 | */ 132 | private function checkInvalidSize(): bool 133 | { 134 | $max_size = Settings::get('global_allowed_filesize', 10000); 135 | 136 | $field = $this->getUploadFieldName(); 137 | 138 | $validator = Validator::make(request()->all(), [ 139 | $field . '.*' => 'max:' . $max_size, 140 | ]); 141 | 142 | return $validator->fails(); 143 | } 144 | 145 | /** 146 | * Check if uploaded file has a valid mime type 147 | * 148 | * @return boolean 149 | */ 150 | private function checkInvalidFile(): bool 151 | { 152 | $field = $this->getUploadFieldName(); 153 | $types = $this->allowedFileTypes(); 154 | 155 | $validator = Validator::make(request()->all(), [ 156 | $field . '.*' => 'mimes:' . $types, 157 | ]); 158 | 159 | return $validator->fails(); 160 | } 161 | 162 | /** 163 | * Get a list of allowed files types 164 | * 165 | * @return string 166 | */ 167 | private function allowedFileTypes(): string 168 | { 169 | $settings = Settings::get('global_allowed_files', false); 170 | 171 | if ($settings) { 172 | return $settings; 173 | } 174 | 175 | $default = Definitions::get('defaultExtensions'); 176 | 177 | return implode(',', $default); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /classes/FilePond/InvalidPathException.php: -------------------------------------------------------------------------------- 1 | subDays($gdpr_days); 24 | $rows = Record::whereDate('created_at', '<', $days)->forceDelete(); 25 | return $rows; 26 | } 27 | 28 | Flash::error(e(trans('martin.forms::lang.classes.GDPR.alert_invalid_gdpr'))); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /classes/MagicForm.php: -------------------------------------------------------------------------------- 1 | page['recaptcha_enabled'] = $this->isReCaptchaEnabled(); 32 | $this->page['recaptcha_misconfigured'] = $this->isReCaptchaMisconfigured(); 33 | 34 | if ($this->property('uploader_enable')) { 35 | $this->page['allowed_filesize'] = Settings::get('global_allowed_filesize'); 36 | } 37 | 38 | if ($this->isReCaptchaEnabled()) { 39 | $this->loadReCaptcha(); 40 | } 41 | 42 | if ($this->isReCaptchaMisconfigured()) { 43 | $this->page['recaptcha_warn'] = Lang::get('martin.forms::lang.components.shared.recaptcha_warn'); 44 | } 45 | 46 | if ($this->property('inline_errors') == 'display') { 47 | $this->addJs('assets/js/inline-errors.js'); 48 | } 49 | } 50 | 51 | public function settings() 52 | { 53 | return [ 54 | 'recaptcha_site_key' => Settings::get('recaptcha_site_key'), 55 | 'recaptcha_secret_key' => Settings::get('recaptcha_secret_key'), 56 | ]; 57 | } 58 | 59 | public function onFormSubmit() 60 | { 61 | // FLASH PARTIAL 62 | $flash_partial = $this->property('messages_partial', '@flash.htm'); 63 | 64 | // CSRF CHECK 65 | if (Config::get('cms.enableCsrfProtection') && (Session::token() != post('_token'))) { 66 | throw new AjaxException(['#' . $this->alias . '_forms_flash' => $this->renderPartial($flash_partial, [ 67 | 'status' => 'error', 68 | 'type' => 'danger', 69 | 'content' => Lang::get('martin.forms::lang.components.shared.csrf_error'), 70 | ])]); 71 | } 72 | 73 | // LOAD TRANSLATOR PLUGIN 74 | if (BackendHelpers::isTranslatePlugin()) { 75 | $translator = \RainLab\Translate\Classes\Translator::instance(); 76 | $translator->loadLocaleFromSession(); 77 | $locale = $translator->getLocale(); 78 | \RainLab\Translate\Models\Message::setContext($locale); 79 | } 80 | 81 | // FILTER ALLOWED FIELDS 82 | $allow = $this->property('allowed_fields'); 83 | if (is_array($allow) && !empty($allow)) { 84 | foreach ($allow as $field) { 85 | $post[$field] = post($field); 86 | } 87 | if ($this->isReCaptchaEnabled()) { 88 | $post['g-recaptcha-response'] = post('g-recaptcha-response'); 89 | } 90 | } else { 91 | $post = post(); 92 | } 93 | 94 | // SANITIZE FORM DATA 95 | if ($this->property('sanitize_data') == 'htmlspecialchars') { 96 | $post = $this->array_map_recursive(function ($value) { 97 | return htmlspecialchars($value, ENT_QUOTES); 98 | }, $post); 99 | } 100 | 101 | // VALIDATION PARAMETERS 102 | $rules = (array)$this->property('rules'); 103 | $msgs = (array)$this->property('rules_messages'); 104 | $custom_attributes = (array)$this->property('custom_attributes'); 105 | 106 | // TRANSLATE CUSTOM ERROR MESSAGES 107 | if (BackendHelpers::isTranslatePlugin()) { 108 | foreach ($msgs as $rule => $msg) { 109 | $msgs[$rule] = \RainLab\Translate\Models\Message::trans($msg); 110 | } 111 | } 112 | 113 | // ADD reCAPTCHA VALIDATION 114 | if ($this->isReCaptchaEnabled() && $this->property('recaptcha_size') != 'invisible') { 115 | $rules['g-recaptcha-response'] = 'required'; 116 | } 117 | 118 | // DO FORM VALIDATION 119 | $validator = Validator::make($post, $rules, $msgs, $custom_attributes); 120 | 121 | // NICE reCAPTCHA FIELD NAME 122 | if ($this->isReCaptchaEnabled()) { 123 | $fields_names = ['g-recaptcha-response' => 'reCAPTCHA']; 124 | $validator->setAttributeNames(array_merge($fields_names, $custom_attributes)); 125 | } 126 | 127 | // VALIDATE ALL + CAPTCHA EXISTS 128 | if ($validator->fails()) { 129 | 130 | // GET DEFAULT ERROR MESSAGE 131 | $message = $this->property('messages_errors'); 132 | 133 | // LOOK FOR TRANSLATION 134 | if (BackendHelpers::isTranslatePlugin()) { 135 | $message = \RainLab\Translate\Models\Message::trans($message); 136 | } 137 | 138 | // THROW ERRORS 139 | if ($this->property('inline_errors') == 'display') { 140 | throw new ValidationException($validator); 141 | } else { 142 | throw new AjaxException($this->exceptionResponse($validator, [ 143 | 'status' => 'error', 144 | 'type' => 'danger', 145 | 'title' => $message, 146 | 'list' => $validator->messages()->all(), 147 | 'errors' => json_encode($validator->messages()->messages()), 148 | 'jscript' => $this->property('js_on_error'), 149 | ])); 150 | } 151 | } 152 | 153 | // IF FIRST VALIDATION IS OK, VALIDATE CAPTCHA vs GOOGLE 154 | // (this prevents to resolve captcha after every form error) 155 | if ($this->isReCaptchaEnabled()) { 156 | 157 | // PREPARE RECAPTCHA VALIDATION 158 | $rules = ['g-recaptcha-response' => 'recaptcha']; 159 | $err_msg = ['g-recaptcha-response.recaptcha' => Lang::get('martin.forms::lang.validation.recaptcha_error')]; 160 | 161 | // DO SECOND VALIDATION 162 | $validator = Validator::make($post, $rules, $err_msg); 163 | 164 | // VALIDATE ALL + CAPTCHA EXISTS 165 | if ($validator->fails()) { 166 | 167 | // THROW ERRORS 168 | if ($this->property('inline_errors') == 'display') { 169 | throw new ValidationException($validator); 170 | } else { 171 | throw new AjaxException($this->exceptionResponse($validator, [ 172 | 'status' => 'error', 173 | 'type' => 'danger', 174 | 'content' => Lang::get('martin.forms::lang.validation.recaptcha_error'), 175 | 'errors' => json_encode($validator->messages()->messages()), 176 | 'jscript' => $this->property('js_on_error'), 177 | ])); 178 | } 179 | } 180 | } 181 | 182 | // REMOVE EXTRA FIELDS FROM STORED DATA 183 | unset($post['_token'], $post['g-recaptcha-response'], $post['_session_key'], $post['files']); 184 | 185 | // FIRE BEFORE SAVE EVENT 186 | Event::fire('martin.forms.beforeSaveRecord', [&$post, $this]); 187 | 188 | if (count($custom_attributes)) { 189 | $post = collect($post)->mapWithKeys(function ($val, $key) use ($custom_attributes) { 190 | return [array_get($custom_attributes, $key, $key) => $val]; 191 | })->all(); 192 | } 193 | 194 | $record = new Record; 195 | $record->ip = $this->getIP(); 196 | $record->created_at = date('Y-m-d H:i:s'); 197 | 198 | // SAVE RECORD TO DATABASE 199 | if (!$this->property('skip_database')) { 200 | $record->form_data = json_encode($post, JSON_UNESCAPED_UNICODE); 201 | if ($this->property('group') != '') { 202 | $record->group = $this->property('group'); 203 | } 204 | 205 | // attach files 206 | $this->attachFiles($record); 207 | 208 | $record->save(null, post('_session_key')); 209 | } 210 | 211 | // SEND NOTIFICATION EMAIL 212 | if ($this->property('mail_enabled')) { 213 | $notification = App::makeWith(Notification::class, [ 214 | $this->getProperties(), $post, $record, $record->files 215 | ]); 216 | $notification->send(); 217 | } 218 | 219 | // SEND AUTORESPONSE EMAIL 220 | if ($this->property('mail_resp_enabled')) { 221 | $autoresponse = App::makeWith(AutoResponse::class, [ 222 | $this->getProperties(), $post, $record 223 | ]); 224 | $autoresponse->send(); 225 | } 226 | 227 | // FIRE AFTER SAVE EVENT 228 | Event::fire('martin.forms.afterSaveRecord', [&$post, $this, $record]); 229 | 230 | // CHECK FOR REDIRECT 231 | if ($this->property('redirect')) { 232 | return Redirect::to($this->property('redirect')); 233 | } 234 | 235 | // GET DEFAULT SUCCESS MESSAGE 236 | $message = $this->property('messages_success'); 237 | 238 | // LOOK FOR TRANSLATION 239 | if (BackendHelpers::isTranslatePlugin()) { 240 | $message = \RainLab\Translate\Models\Message::trans($message); 241 | } 242 | 243 | // DISPLAY SUCCESS MESSAGE 244 | return ['#' . $this->alias . '_forms_flash' => $this->renderPartial($flash_partial, [ 245 | 'status' => 'success', 246 | 'type' => 'success', 247 | 'content' => $message, 248 | 'jscript' => $this->prepareJavaScript(), 249 | ])]; 250 | } 251 | 252 | private function exceptionResponse($validator, $params) 253 | { 254 | // FLASH PARTIAL 255 | $flash_partial = $this->property('messages_partial', '@flash.htm'); 256 | 257 | // EXCEPTION RESPONSE 258 | $response = ['#' . $this->alias . '_forms_flash' => $this->renderPartial($flash_partial, $params)]; 259 | 260 | // INCLUDE ERROR FIELDS IF REQUIRED 261 | if ($this->property('inline_errors') != 'disabled') { 262 | $response['error_fields'] = $validator->messages(); 263 | } 264 | 265 | return $response; 266 | } 267 | 268 | private function prepareJavaScript() 269 | { 270 | $code = false; 271 | 272 | /* SUCCESS JS */ 273 | if ($this->property('js_on_success') != '') { 274 | $code .= $this->property('js_on_success'); 275 | } 276 | 277 | /* RECAPTCHA JS */ 278 | if ($this->isReCaptchaEnabled()) { 279 | $code .= $this->renderPartial('@js/recaptcha.htm'); 280 | } 281 | 282 | /* RESET FORM JS */ 283 | if ($this->property('reset_form')) { 284 | $params = ['id' => '#' . $this->alias . '_forms_flash']; 285 | $code .= $this->renderPartial('@js/reset-form.htm', $params); 286 | } 287 | 288 | return $code; 289 | } 290 | 291 | private function getIP() 292 | { 293 | if ($this->property('anonymize_ip') == 'full') { 294 | return '(Not stored)'; 295 | } 296 | 297 | $ip = Request::getClientIp(); 298 | 299 | if ($this->property('anonymize_ip') == 'partial') { 300 | return BackendHelpers::anonymizeIPv4($ip); 301 | } 302 | 303 | return $ip; 304 | } 305 | 306 | private function array_map_recursive($callback, $array) 307 | { 308 | $func = function ($item) use (&$func, &$callback) { 309 | return is_array($item) ? array_map($func, $item) : call_user_func($callback, $item); 310 | }; 311 | 312 | return array_map($func, $array); 313 | } 314 | 315 | private function attachFiles(Record $record) 316 | { 317 | $files = post('files', null); 318 | 319 | if (!$files) { 320 | return; 321 | } 322 | 323 | foreach ($files as $file) { 324 | $filepond = App::make(FilePond::class); 325 | $filePath = $filepond->getPathFromServerId($file); 326 | 327 | $record->files()->create([ 328 | 'data' => $filePath 329 | ], post('_session_key')); 330 | } 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /classes/Mails/AutoResponse.php: -------------------------------------------------------------------------------- 1 | properties = $properties; 20 | $this->post = $post; 21 | $this->record = $record; 22 | } 23 | 24 | public function send() 25 | { 26 | // SET DEFAULT EMAIL DATA ARRAY 27 | $this->data = [ 28 | 'id' => $this->record->id, 29 | 'data' => $this->post, 30 | 'ip' => $this->record->ip, 31 | 'date' => $this->record->created_at 32 | ]; 33 | 34 | // CHECK FOR CUSTOM SUBJECT 35 | if (!empty($this->properties['mail_resp_subject'])) { 36 | $this->prepareCustomSubject(); 37 | } 38 | 39 | // SET EMAIL PARAMETERS 40 | $response = isset($this->properties['mail_resp_field']) ? $this->properties['mail_resp_field'] : null; 41 | $to = isset($this->post[$response]) ? $this->post[$response] : null; 42 | $name = isset($this->properties['mail_resp_name']) ? $this->properties['mail_resp_name'] : null; 43 | $from = isset($this->properties['mail_resp_from']) ? $this->properties['mail_resp_from'] : null; 44 | $subject = isset($this->properties['mail_resp_subject']) ? $this->properties['mail_resp_subject'] : null; 45 | 46 | if (filter_var($to, FILTER_VALIDATE_EMAIL) && filter_var($from, FILTER_VALIDATE_EMAIL)) { 47 | // CUSTOM TEMPLATE 48 | $template = $this->getTemplate(); 49 | 50 | // SEND AUTORESPONSE EMAIL 51 | Mail::sendTo($to, $template, $this->data, function ($message) use ($from, $name, $subject) { 52 | $message->from($from, $name); 53 | 54 | if (isset($subject)) { 55 | $message->subject($subject); 56 | } 57 | }); 58 | } 59 | } 60 | 61 | /** 62 | * Returns email template to use 63 | * 64 | * @return string 65 | */ 66 | public function getTemplate(): string 67 | { 68 | return !empty($this->properties['mail_resp_template']) && MailTemplate::findOrMakeTemplate($this->properties['mail_resp_template']) ? 69 | $this->properties['mail_resp_template'] : 70 | 'martin.forms::mail.autoresponse'; 71 | } 72 | 73 | /** 74 | * Parse custom subject and modify using form variables and custom settings 75 | * 76 | * @return void 77 | */ 78 | public function prepareCustomSubject() 79 | { 80 | // SET DATE FORMAT 81 | $dateFormat = $this->properties['emails_date_format'] ?? 'Y-m-d'; 82 | 83 | // DATA TO REPLACE 84 | $id = $this->data['id']; 85 | $ip = $this->data['ip']; 86 | $date = date($dateFormat); 87 | 88 | // REPLACE RECORD TOKENS IN SUBJECT 89 | $this->properties['mail_resp_subject'] = BackendHelpers::replaceToken('record.id', $id, $this->properties['mail_resp_subject']); 90 | $this->properties['mail_resp_subject'] = BackendHelpers::replaceToken('record.ip', $ip, $this->properties['mail_resp_subject']); 91 | $this->properties['mail_resp_subject'] = BackendHelpers::replaceToken('record.date', $date, $this->properties['mail_resp_subject']); 92 | 93 | // REPLACE FORM FIELDS TOKENS IN SUBJECT 94 | foreach ($this->data['data'] as $key => $value) { 95 | if (!is_array($value)) { 96 | $token = 'form.' . $key; 97 | $this->properties['mail_resp_subject'] = BackendHelpers::replaceToken($token, $value, $this->properties['mail_resp_subject']); 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /classes/Mails/Mailable.php: -------------------------------------------------------------------------------- 1 | properties = $properties; 22 | $this->post = $post; 23 | $this->record = $record; 24 | $this->files = $files; 25 | } 26 | 27 | public function send() 28 | { 29 | // CHECK IF THERE IS AT LEAST ONE MAIL ADDRESS 30 | if (!isset($this->properties['mail_recipients'])) { 31 | $this->properties['mail_recipients'] = false; 32 | } 33 | 34 | // CHECK IF THERE IS AT LEAST ONE MAIL ADDRESS 35 | if (!isset($this->properties['mail_bcc'])) { 36 | $this->properties['mail_bcc'] = false; 37 | } 38 | 39 | // EXIT IF NO EMAIL ADDRESSES ARE SET 40 | if (!$this->checkEmailSettings()) { 41 | return; 42 | } 43 | 44 | // CUSTOM TEMPLATE 45 | $template = $this->getTemplate(); 46 | 47 | // SET DEFAULT EMAIL DATA ARRAY 48 | $this->data = [ 49 | 'id' => $this->record->id, 50 | 'data' => $this->post, 51 | 'ip' => $this->record->ip, 52 | 'date' => $this->record->created_at 53 | ]; 54 | 55 | // CHECK FOR CUSTOM SUBJECT 56 | if (!empty($this->properties['mail_subject'])) { 57 | $this->prepareCustomSubject(); 58 | } 59 | 60 | // SEND NOTIFICATION EMAIL 61 | Mail::sendTo($this->properties['mail_recipients'], $template, $this->data, function ($message) { 62 | // SEND BLIND CARBON COPY 63 | if (!empty($this->properties['mail_bcc']) && is_array($this->properties['mail_bcc'])) { 64 | $message->bcc($this->properties['mail_bcc']); 65 | } 66 | 67 | // USE CUSTOM SUBJECT 68 | if (!empty($this->properties['mail_subject'])) { 69 | $message->subject($this->properties['mail_subject']); 70 | } 71 | 72 | // ADD REPLY TO ADDRESS 73 | if (!empty($this->properties['mail_replyto'])) { 74 | $message->replyTo($this->properties['mail_replyto']); 75 | } 76 | 77 | // ADD UPLOADS 78 | if (!empty($this->properties['mail_uploads']) && !empty($this->files)) { 79 | foreach ($this->files as $file) { 80 | $message->attach($file->getLocalPath(), ['as' => $file->getFilename()]); 81 | } 82 | } 83 | }); 84 | } 85 | 86 | /** 87 | * Check if emails address are set 88 | * 89 | * @return boolean 90 | */ 91 | private function checkEmailSettings(): bool 92 | { 93 | return (is_array($this->properties['mail_recipients']) || is_array($this->properties['mail_bcc'])); 94 | } 95 | 96 | public function getTemplate(): string 97 | { 98 | return !empty($this->properties['mail_template']) && MailTemplate::findOrMakeTemplate($this->properties['mail_template']) ? 99 | $this->properties['mail_template'] : 100 | 'martin.forms::mail.notification'; 101 | } 102 | 103 | public function prepareCustomSubject() 104 | { 105 | // SET DATE FORMAT 106 | $dateFormat = $this->properties['emails_date_format'] ?? 'Y-m-d'; 107 | 108 | // DATA TO REPLACE 109 | $id = $this->data['id']; 110 | $ip = $this->data['ip']; 111 | $date = date($dateFormat); 112 | 113 | // REPLACE RECORD TOKENS IN SUBJECT 114 | $this->properties['mail_subject'] = BH::replaceToken('record.id', $id, $this->properties['mail_subject']); 115 | $this->properties['mail_subject'] = BH::replaceToken('record.ip', $ip, $this->properties['mail_subject']); 116 | $this->properties['mail_subject'] = BH::replaceToken('record.date', $date, $this->properties['mail_subject']); 117 | 118 | // REPLACE FORM FIELDS TOKENS IN SUBJECT 119 | foreach ($this->data['data'] as $key => $value) { 120 | if (!is_array($value)) { 121 | $token = 'form.' . $key; 122 | $this->properties['mail_subject'] = BH::replaceToken($token, $value, $this->properties['mail_subject']); 123 | } 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /classes/ReCaptcha.php: -------------------------------------------------------------------------------- 1 | translator = Translator::instance(); 25 | } 26 | } 27 | 28 | private function isReCaptchaEnabled() 29 | { 30 | return ($this->property('recaptcha_enabled') && Settings::get('recaptcha_site_key') != '' && Settings::get('recaptcha_secret_key') != ''); 31 | } 32 | 33 | private function isReCaptchaMisconfigured() 34 | { 35 | return ($this->property('recaptcha_enabled') && (Settings::get('recaptcha_site_key') == '' || Settings::get('recaptcha_secret_key') == '')); 36 | } 37 | 38 | private function getReCaptchaLang($lang = '') 39 | { 40 | if (BackendHelpers::isTranslatePlugin()) { 41 | $lang = '&hl=' . $this->activeLocale = $this->translator->getLocale(); 42 | } else { 43 | $lang = '&hl=' . $this->activeLocale = app()->getLocale(); 44 | } 45 | return $lang; 46 | } 47 | 48 | private function loadReCaptcha() 49 | { 50 | $this->addJs('https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit' . $this->getReCaptchaLang(), ['async', 'defer']); 51 | $this->addJs('assets/js/recaptcha.js'); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /classes/ReCaptchaValidator.php: -------------------------------------------------------------------------------- 1 | [ 13 | 'title' => 'martin.forms::lang.components.shared.group.title', 14 | 'description' => 'martin.forms::lang.components.shared.group.description', 15 | 'type' => 'string', 16 | 'showExternalParam' => false, 17 | ], 18 | 'rules' => [ 19 | 'title' => 'martin.forms::lang.components.shared.rules.title', 20 | 'description' => 'martin.forms::lang.components.shared.rules.description', 21 | 'type' => 'dictionary', 22 | 'group' => 'martin.forms::lang.components.shared.group_validation', 23 | 'showExternalParam' => false, 24 | ], 25 | 'rules_messages' => [ 26 | 'title' => 'martin.forms::lang.components.shared.rules_messages.title', 27 | 'description' => 'martin.forms::lang.components.shared.rules_messages.description', 28 | 'type' => 'dictionary', 29 | 'group' => 'martin.forms::lang.components.shared.group_validation', 30 | 'showExternalParam' => false, 31 | ], 32 | 'custom_attributes' => [ 33 | 'title' => 'martin.forms::lang.components.shared.custom_attributes.title', 34 | 'description' => 'martin.forms::lang.components.shared.custom_attributes.description', 35 | 'type' => 'dictionary', 36 | 'group' => 'martin.forms::lang.components.shared.group_validation', 37 | 'showExternalParam' => false, 38 | ], 39 | 'messages_success' => [ 40 | 'title' => 'martin.forms::lang.components.shared.messages_success.title', 41 | 'description' => 'martin.forms::lang.components.shared.messages_success.description', 42 | 'type' => 'string', 43 | 'group' => 'martin.forms::lang.components.shared.group_messages', 44 | 'default' => Lang::get('martin.forms::lang.components.shared.messages_success.default'), 45 | 'showExternalParam' => false, 46 | 'validation' => ['required' => ['message' => Lang::get('martin.forms::lang.components.shared.validation_req')]] 47 | ], 48 | 'messages_errors' => [ 49 | 'title' => 'martin.forms::lang.components.shared.messages_errors.title', 50 | 'description' => 'martin.forms::lang.components.shared.messages_errors.description', 51 | 'type' => 'string', 52 | 'group' => 'martin.forms::lang.components.shared.group_messages', 53 | 'default' => Lang::get('martin.forms::lang.components.shared.messages_errors.default'), 54 | 'showExternalParam' => false, 55 | 'validation' => ['required' => ['message' => Lang::get('martin.forms::lang.components.shared.validation_req')]] 56 | ], 57 | 'messages_partial' => [ 58 | 'title' => 'martin.forms::lang.components.shared.messages_partial.title', 59 | 'description' => 'martin.forms::lang.components.shared.messages_partial.description', 60 | 'type' => 'string', 61 | 'group' => 'martin.forms::lang.components.shared.group_messages', 62 | 'showExternalParam' => false 63 | ], 64 | 'mail_enabled' => [ 65 | 'title' => 'martin.forms::lang.components.shared.mail_enabled.title', 66 | 'description' => 'martin.forms::lang.components.shared.mail_enabled.description', 67 | 'type' => 'checkbox', 68 | 'group' => 'martin.forms::lang.components.shared.group_mail', 69 | 'showExternalParam' => false 70 | ], 71 | 'mail_subject' => [ 72 | 'title' => 'martin.forms::lang.components.shared.mail_subject.title', 73 | 'description' => 'martin.forms::lang.components.shared.mail_subject.description', 74 | 'type' => 'string', 75 | 'group' => 'martin.forms::lang.components.shared.group_mail', 76 | 'showExternalParam' => false 77 | ], 78 | 'mail_recipients' => [ 79 | 'title' => 'martin.forms::lang.components.shared.mail_recipients.title', 80 | 'description' => 'martin.forms::lang.components.shared.mail_recipients.description', 81 | 'type' => 'stringList', 82 | 'group' => 'martin.forms::lang.components.shared.group_mail', 83 | 'showExternalParam' => false 84 | ], 85 | 'mail_bcc' => [ 86 | 'title' => 'martin.forms::lang.components.shared.mail_bcc.title', 87 | 'description' => 'martin.forms::lang.components.shared.mail_bcc.description', 88 | 'type' => 'stringList', 89 | 'group' => 'martin.forms::lang.components.shared.group_mail', 90 | 'showExternalParam' => false 91 | ], 92 | 'mail_replyto' => [ 93 | 'title' => 'martin.forms::lang.components.shared.mail_replyto.title', 94 | 'description' => 'martin.forms::lang.components.shared.mail_replyto.description', 95 | 'type' => 'string', 96 | 'group' => 'martin.forms::lang.components.shared.group_mail', 97 | 'showExternalParam' => false 98 | ], 99 | 'mail_template' => [ 100 | 'title' => 'martin.forms::lang.components.shared.mail_template.title', 101 | 'description' => 'martin.forms::lang.components.shared.mail_template.description', 102 | 'type' => 'string', 103 | 'group' => 'martin.forms::lang.components.shared.group_mail', 104 | 'showExternalParam' => false 105 | ], 106 | 'mail_resp_enabled' => [ 107 | 'title' => 'martin.forms::lang.components.shared.mail_resp_enabled.title', 108 | 'description' => 'martin.forms::lang.components.shared.mail_resp_enabled.description', 109 | 'type' => 'checkbox', 110 | 'group' => 'martin.forms::lang.components.shared.group_mail_resp', 111 | 'showExternalParam' => false 112 | ], 113 | 'mail_resp_field' => [ 114 | 'title' => 'martin.forms::lang.components.shared.mail_resp_field.title', 115 | 'description' => 'martin.forms::lang.components.shared.mail_resp_field.description', 116 | 'type' => 'string', 117 | 'group' => 'martin.forms::lang.components.shared.group_mail_resp', 118 | 'showExternalParam' => false 119 | ], 120 | 'mail_resp_name' => [ 121 | 'title' => 'martin.forms::lang.components.shared.mail_resp_name.title', 122 | 'description' => 'martin.forms::lang.components.shared.mail_resp_name.description', 123 | 'type' => 'string', 124 | 'group' => 'martin.forms::lang.components.shared.group_mail_resp', 125 | 'showExternalParam' => false 126 | ], 127 | 'mail_resp_from' => [ 128 | 'title' => 'martin.forms::lang.components.shared.mail_resp_from.title', 129 | 'description' => 'martin.forms::lang.components.shared.mail_resp_from.description', 130 | 'type' => 'string', 131 | 'group' => 'martin.forms::lang.components.shared.group_mail_resp', 132 | 'showExternalParam' => false 133 | ], 134 | 'mail_resp_subject' => [ 135 | 'title' => 'martin.forms::lang.components.shared.mail_resp_subject.title', 136 | 'description' => 'martin.forms::lang.components.shared.mail_resp_subject.description', 137 | 'type' => 'string', 138 | 'group' => 'martin.forms::lang.components.shared.group_mail_resp', 139 | 'showExternalParam' => false 140 | ], 141 | 'mail_resp_template' => [ 142 | 'title' => 'martin.forms::lang.components.shared.mail_template.title', 143 | 'description' => 'martin.forms::lang.components.shared.mail_template.description', 144 | 'type' => 'string', 145 | 'group' => 'martin.forms::lang.components.shared.group_mail_resp', 146 | 'showExternalParam' => false 147 | ], 148 | 'reset_form' => [ 149 | 'title' => 'martin.forms::lang.components.shared.reset_form.title', 150 | 'description' => 'martin.forms::lang.components.shared.reset_form.description', 151 | 'type' => 'checkbox', 152 | 'group' => 'martin.forms::lang.components.shared.group_settings', 153 | 'showExternalParam' => false 154 | ], 155 | 'redirect' => [ 156 | 'title' => 'martin.forms::lang.components.shared.redirect.title', 157 | 'description' => 'martin.forms::lang.components.shared.redirect.description', 158 | 'type' => 'string', 159 | 'group' => 'martin.forms::lang.components.shared.group_settings', 160 | 'showExternalParam' => false 161 | ], 162 | 'inline_errors' => [ 163 | 'title' => 'martin.forms::lang.components.shared.inline_errors.title', 164 | 'description' => 'martin.forms::lang.components.shared.inline_errors.description', 165 | 'type' => 'dropdown', 166 | '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'], 167 | 'default' => 'disabled', 168 | 'group' => 'martin.forms::lang.components.shared.group_settings', 169 | 'showExternalParam' => false 170 | ], 171 | 'js_on_success' => [ 172 | 'title' => 'martin.forms::lang.components.shared.js_on_success.title', 173 | 'description' => 'martin.forms::lang.components.shared.js_on_success.description', 174 | 'type' => 'text', 175 | 'group' => 'martin.forms::lang.components.shared.group_settings', 176 | 'showExternalParam' => false 177 | ], 178 | 'js_on_error' => [ 179 | 'title' => 'martin.forms::lang.components.shared.js_on_error.title', 180 | 'description' => 'martin.forms::lang.components.shared.js_on_error.description', 181 | 'type' => 'text', 182 | 'group' => 'martin.forms::lang.components.shared.group_settings', 183 | 'showExternalParam' => false 184 | ], 185 | 'allowed_fields' => [ 186 | 'title' => 'martin.forms::lang.components.shared.allowed_fields.title', 187 | 'description' => 'martin.forms::lang.components.shared.allowed_fields.description', 188 | 'type' => 'stringList', 189 | 'group' => 'martin.forms::lang.components.shared.group_security', 190 | 'showExternalParam' => false 191 | ], 192 | 'sanitize_data' => [ 193 | 'title' => 'martin.forms::lang.components.shared.sanitize_data.title', 194 | 'description' => 'martin.forms::lang.components.shared.sanitize_data.description', 195 | 'type' => 'dropdown', 196 | 'options' => ['disabled' => 'martin.forms::lang.components.shared.sanitize_data.disabled', 'htmlspecialchars' => 'martin.forms::lang.components.shared.sanitize_data.htmlspecialchars'], 197 | 'default' => 'disabled', 198 | 'group' => 'martin.forms::lang.components.shared.group_security', 199 | 'showExternalParam' => false 200 | ], 201 | 'anonymize_ip' => [ 202 | 'title' => 'martin.forms::lang.components.shared.anonymize_ip.title', 203 | 'description' => 'martin.forms::lang.components.shared.anonymize_ip.description', 204 | 'type' => 'dropdown', 205 | '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'], 206 | 'default' => 'disabled', 207 | 'group' => 'martin.forms::lang.components.shared.group_security', 208 | 'showExternalParam' => false 209 | ], 210 | 'recaptcha_enabled' => [ 211 | 'title' => 'martin.forms::lang.components.shared.recaptcha_enabled.title', 212 | 'description' => 'martin.forms::lang.components.shared.recaptcha_enabled.description', 213 | 'type' => 'checkbox', 214 | 'group' => 'martin.forms::lang.components.shared.group_recaptcha', 215 | 'showExternalParam' => false 216 | ], 217 | 'recaptcha_theme' => [ 218 | 'title' => 'martin.forms::lang.components.shared.recaptcha_theme.title', 219 | 'description' => 'martin.forms::lang.components.shared.recaptcha_theme.description', 220 | 'type' => 'dropdown', 221 | 'options' => ['light' => 'martin.forms::lang.components.shared.recaptcha_theme.light', 'dark' => 'martin.forms::lang.components.shared.recaptcha_theme.dark'], 222 | 'default' => 'light', 223 | 'group' => 'martin.forms::lang.components.shared.group_recaptcha', 224 | 'showExternalParam' => false 225 | ], 226 | 'recaptcha_type' => [ 227 | 'title' => 'martin.forms::lang.components.shared.recaptcha_type.title', 228 | 'description' => 'martin.forms::lang.components.shared.recaptcha_type.description', 229 | 'type' => 'dropdown', 230 | 'options' => ['image' => 'martin.forms::lang.components.shared.recaptcha_type.image', 'audio' => 'martin.forms::lang.components.shared.recaptcha_type.audio'], 231 | 'default' => 'image', 232 | 'group' => 'martin.forms::lang.components.shared.group_recaptcha', 233 | 'showExternalParam' => false 234 | ], 235 | 'recaptcha_size' => [ 236 | 'title' => 'martin.forms::lang.components.shared.recaptcha_size.title', 237 | 'description' => 'martin.forms::lang.components.shared.recaptcha_size.description', 238 | 'type' => 'dropdown', 239 | 'options' => [ 240 | 'normal' => 'martin.forms::lang.components.shared.recaptcha_size.normal', 241 | 'compact' => 'martin.forms::lang.components.shared.recaptcha_size.compact', 242 | 'invisible' => 'martin.forms::lang.components.shared.recaptcha_size.invisible', 243 | ], 244 | 'default' => 'normal', 245 | 'group' => 'martin.forms::lang.components.shared.group_recaptcha', 246 | 'showExternalParam' => false 247 | ], 248 | 'skip_database' => [ 249 | 'title' => 'martin.forms::lang.components.shared.skip_database.title', 250 | 'description' => 'martin.forms::lang.components.shared.skip_database.description', 251 | 'type' => 'checkbox', 252 | 'group' => 'martin.forms::lang.components.shared.group_advanced', 253 | 'showExternalParam' => false 254 | ], 255 | 'emails_date_format' => [ 256 | 'title' => 'martin.forms::lang.components.shared.emails_date_format.title', 257 | 'description' => 'martin.forms::lang.components.shared.emails_date_format.description', 258 | 'default' => 'Y-m-d', 259 | 'group' => 'martin.forms::lang.components.shared.group_advanced', 260 | 'showExternalParam' => false 261 | ], 262 | ]; 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /classes/UnreadRecords.php: -------------------------------------------------------------------------------- 1 | count(); 12 | return ($unread > 0) ? $unread : null; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /components/EmptyForm.php: -------------------------------------------------------------------------------- 1 | 'martin.forms::lang.components.empty_form.name', 12 | 'description' => 'martin.forms::lang.components.empty_form.description', 13 | ]; 14 | } 15 | 16 | } 17 | 18 | ?> -------------------------------------------------------------------------------- /components/FilePondForm.php: -------------------------------------------------------------------------------- 1 | 'martin.forms::lang.components.filepond_form.name', 13 | 'description' => 'martin.forms::lang.components.filepond_form.description', 14 | ]; 15 | } 16 | 17 | public function defineProperties() 18 | { 19 | $local = [ 20 | 'mail_uploads' => [ 21 | 'title' => 'martin.forms::lang.components.shared.mail_uploads.title', 22 | 'description' => 'martin.forms::lang.components.shared.mail_uploads.description', 23 | 'type' => 'checkbox', 24 | 'default' => false, 25 | 'group' => 'martin.forms::lang.components.shared.group_mail', 26 | 'showExternalParam' => false 27 | ], 28 | 'uploader_enable' => [ 29 | 'title' => 'martin.forms::lang.components.shared.uploader_enable.title', 30 | 'description' => 'martin.forms::lang.components.shared.uploader_enable.description', 31 | 'default' => false, 32 | 'type' => 'checkbox', 33 | 'group' => 'martin.forms::lang.components.shared.group_uploader', 34 | 'showExternalParam' => false, 35 | ], 36 | ]; 37 | 38 | return array_merge(parent::defineProperties(), $local); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /components/GenericForm.php: -------------------------------------------------------------------------------- 1 | 'martin.forms::lang.components.generic_form.name', 12 | 'description' => 'martin.forms::lang.components.generic_form.description', 13 | ]; 14 | } 15 | 16 | } 17 | 18 | ?> -------------------------------------------------------------------------------- /components/emptyform/default.htm: -------------------------------------------------------------------------------- 1 |

    Here goes your custom form

    2 |

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

    3 |

    You can copy/paste this basic template:

    4 |
     5 |     <form data-request="{{ __SELF__ }}::onFormSubmit">
     6 |         {{ form_token() }}
     7 |         <div id="{{ __SELF__ }}_forms_flash"></div>
     8 |         <!-- YOUR FORM FIELDS -->
     9 |         {% partial '@recaptcha' %}
    10 |         <!-- SUBMIT BUTTON -->
    11 |     </form>
    12 | 
    13 | -------------------------------------------------------------------------------- /components/filepondform/default.htm: -------------------------------------------------------------------------------- 1 | {% if __SELF__.property('uploader_enable') == 0 %} 2 |
    3 |
    Warning
    4 |
    Uploads are disabled.
    5 |
    You need to explicitly enable this option on the component (this is a security measure).
    6 |
    7 | {% endif %} 8 | 9 | {{ form_ajax(__SELF__ ~ '::onFormSubmit') }} 10 | 11 |
    12 | 13 |
    14 |

    Apply for online job

    15 |
    16 | 17 |
    18 | 19 | 20 |
    21 | 22 |
    23 | 24 | 25 |
    26 | 27 |
    28 | 29 | 37 |
    38 | 39 |
    40 | 41 | 42 |
    43 | 44 |
    45 | 46 | {% if __SELF__.property('uploader_enable') == 1 %} 47 |
    48 |

    Upload your resume

    49 | 50 |
    51 | 52 |
    53 | {% endif %} 54 | 55 |
    56 | {% partial '@recaptcha' %} 57 |
    58 | 59 | {{ form_submit() }} 60 | 61 | {{ form_close() }} 62 | 63 | {% partial '@filepond' %} 64 | -------------------------------------------------------------------------------- /components/genericform/default.htm: -------------------------------------------------------------------------------- 1 |
    2 | 3 | {{ form_token() }} 4 | 5 |
    6 | 7 |
    8 | 9 | 10 |
    11 | 12 |
    13 | 14 | 15 |
    16 | 17 |
    18 | 19 | 20 |
    21 | 22 |
    23 | 24 |
    25 | 26 |
    27 | {% partial '@recaptcha' %} 28 |
    29 | 30 | 31 | 32 |
    -------------------------------------------------------------------------------- /components/partials/filepond.htm: -------------------------------------------------------------------------------- 1 | {% if __SELF__.property('uploader_enable') == 1 %} 2 | 3 | 4 | 5 | 6 | 43 | 44 | 49 | {% endif %} 50 | -------------------------------------------------------------------------------- /components/partials/flash.htm: -------------------------------------------------------------------------------- 1 | {% spaceless %} 2 | 3 | 23 | 24 | {% if jscript %} 25 | 31 | {% endif %} 32 | 33 | {% endspaceless %} 34 | -------------------------------------------------------------------------------- /components/partials/js/recaptcha.htm: -------------------------------------------------------------------------------- 1 | resetReCaptcha('{{ __SELF__ }}'); -------------------------------------------------------------------------------- /components/partials/js/reset-form.htm: -------------------------------------------------------------------------------- 1 | $('{{ id }}').parents('form')[0].reset(); -------------------------------------------------------------------------------- /components/partials/recaptcha.htm: -------------------------------------------------------------------------------- 1 | {% if (recaptcha_enabled) %} 2 |
    3 | {% elseif (recaptcha_misconfigured) %} 4 |
    {{ recaptcha_warn }}
    5 | {% endif %} -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "martin/wn-forms-plugin", 3 | "type": "winter-plugin", 4 | "description": "Create easy (and almost magic) AJAX forms", 5 | "keywords": ["winter", "cms", "plugin", "forms", "magic forms"], 6 | "homepage": "https://github.com/skydiver/wn-magic-forms/", 7 | "license": "MIT" 8 | } 9 | -------------------------------------------------------------------------------- /controllers/Exports.php: -------------------------------------------------------------------------------- 1 | pageTitle = e(trans('martin.forms::lang.controllers.exports.title')); 31 | $this->create('frontend'); 32 | } 33 | 34 | public function csv() 35 | { 36 | 37 | $records = Record::orderBy('created_at'); 38 | 39 | // FILTER GROUPS 40 | if (!empty($groups = post('Record.filter_groups'))) { 41 | $records->whereIn('group', $groups); 42 | } 43 | 44 | // FILTER DATE 45 | if (!empty($date_after = post('Record.filter_date_after'))) { 46 | $records->whereDate('created_at', '>=', $date_after); 47 | } 48 | 49 | // FILTER DATE 50 | if (!empty($date_before = post('Record.filter_date_before'))) { 51 | $records->whereDate('created_at', '<=', $date_before); 52 | } 53 | 54 | // FILTER DELETED 55 | if (post('Record.options_deleted')) { 56 | $records->withTrashed(); 57 | } 58 | 59 | // CREATE CSV 60 | $csv = CsvWriter::createFromFileObject(new SplTempFileObject()); 61 | 62 | // CHANGE DELIMTER 63 | if (post('Record.options_delimiter')) { 64 | $csv->setDelimiter(';'); 65 | } 66 | 67 | // SET UTF-8 Output 68 | if (post('Record.options_utf')) { 69 | $csv->setOutputBOM(AbstractCsv::BOM_UTF8); 70 | } 71 | 72 | // CSV HEADERS 73 | $headers = []; 74 | 75 | // METADATA HEADERS 76 | if (post('Record.options_metadata')) { 77 | $meta_headers = [ 78 | e(trans('martin.forms::lang.controllers.records.columns.id')), 79 | e(trans('martin.forms::lang.controllers.records.columns.group')), 80 | e(trans('martin.forms::lang.controllers.records.columns.ip')), 81 | e(trans('martin.forms::lang.controllers.records.columns.created_at')), 82 | ]; 83 | $headers = array_merge($meta_headers, $headers); 84 | } 85 | 86 | // ADD STORED FIELDS AS HEADER ROW IN CSV 87 | $filteredRecords = $records->get(); 88 | $record = $filteredRecords->first(); 89 | $headers = array_merge($headers, array_keys($record->form_data_arr)); 90 | 91 | // ADD FILES HEADER 92 | if (post('Record.options_files')) { 93 | $headers[] = e(trans('martin.forms::lang.controllers.records.columns.files')); 94 | } 95 | 96 | // ADD HEADERS 97 | $csv->insertOne($headers); 98 | 99 | // WRITE CSV LINES 100 | foreach ($records->get() as $row) { 101 | $data = (array) json_decode($row['form_data']); 102 | 103 | // IF DATA IS ARRAY CONVERT TO JSON STRING 104 | foreach ($data as $field => $value) { 105 | if (is_array($value) || is_object($value)) { 106 | $data[$field] = json_encode($value); 107 | } 108 | } 109 | 110 | // ADD METADATA IF NEEDED 111 | if (post('Record.options_metadata')) { 112 | array_unshift($data, $row['id'], $row['group'], $row['ip'], $row['created_at']); 113 | } 114 | 115 | // ADD ATTACHED FILES 116 | if (post('Record.options_files') && $row->files->count() > 0) { 117 | $data[] = $row->filesList(); 118 | } 119 | 120 | $csv->insertOne($data); 121 | } 122 | 123 | // RETURN CSV 124 | $csv->output('records.csv'); 125 | exit(); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /controllers/Records.php: -------------------------------------------------------------------------------- 1 | unread = false; 42 | $record->save(); 43 | $this->addCss('/plugins/martin/forms/assets/css/records.css'); 44 | $this->pageTitle = e(trans('martin.forms::lang.controllers.records.view_title')); 45 | $this->vars['record'] = $record; 46 | } 47 | 48 | public function onDelete() 49 | { 50 | if (($checkedIds = post('checked')) && is_array($checkedIds) && count($checkedIds)) { 51 | Record::whereIn('id', $checkedIds)->delete(); 52 | } 53 | 54 | $counter = UnreadRecords::getTotal(); 55 | 56 | return [ 57 | 'counter' => ($counter != null) ? $counter : 0, 58 | 'list' => $this->listRefresh() 59 | ]; 60 | } 61 | 62 | public function onDeleteSingle() 63 | { 64 | $id = post('id'); 65 | $record = Record::find($id); 66 | 67 | if ($record) { 68 | $record->delete(); 69 | Flash::success(e(trans('martin.forms::lang.controllers.records.deleted'))); 70 | } else { 71 | Flash::error(e(trans('martin.forms::lang.controllers.records.error'))); 72 | } 73 | 74 | return Redirect::to(Backend::url('martin/forms/records')); 75 | } 76 | 77 | public function download($record_id, $file_id) 78 | { 79 | $record = Record::findOrFail($record_id); 80 | $file = $record->files->find($file_id); 81 | 82 | if (!$file) { 83 | App::abort(404, Lang::get('backend::lang.import_export.file_not_found_error')); 84 | } 85 | 86 | return response()->download($file->getLocalPath(), $file->getFilename()); 87 | exit(); 88 | } 89 | 90 | public function listInjectRowClass($record, $definition = null) 91 | { 92 | if ($record->unread) { 93 | return 'new'; 94 | } 95 | } 96 | 97 | public function onReadState() 98 | { 99 | if (($checkedIds = post('checked')) && is_array($checkedIds) && count($checkedIds)) { 100 | $unread = (post('state') == 'read') ? 0 : 1; 101 | Record::whereIn('id', $checkedIds)->update(['unread' => $unread]); 102 | } 103 | 104 | $counter = UnreadRecords::getTotal(); 105 | 106 | return [ 107 | 'counter' => ($counter != null) ? $counter : 0, 108 | 'list' => $this->listRefresh() 109 | ]; 110 | } 111 | 112 | public function onGDPRClean() 113 | { 114 | if ($this->user->hasPermission(['martin.forms.gdpr_cleanup'])) { 115 | GDPR::cleanRecords(); 116 | Flash::success(e(trans('martin.forms::lang.controllers.records.alerts.gdpr_success'))); 117 | } else { 118 | Flash::error(e(trans('martin.forms::lang.controllers.records.alerts.gdpr_perms'))); 119 | } 120 | 121 | $counter = UnreadRecords::getTotal(); 122 | 123 | return [ 124 | 'counter' => ($counter != null) ? $counter : 0, 125 | 'list' => $this->listRefresh() 126 | ]; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /controllers/exports/config_form.yaml: -------------------------------------------------------------------------------- 1 | form : $/martin/forms/models/export/fields.yaml 2 | modelClass: Martin\Forms\Models\Record -------------------------------------------------------------------------------- /controllers/exports/index.htm: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | Backend::url('martin/forms/exports/csv/')]) ?> 9 | 10 | formRender() ?> 11 | 12 |
    13 | 'btn btn-primary']) ?> 14 |
    15 | 16 | -------------------------------------------------------------------------------- /controllers/records/config_filter.yaml: -------------------------------------------------------------------------------- 1 | scopes: 2 | 3 | group: 4 | label : martin.forms::lang.controllers.records.columns.group 5 | type : group 6 | modelClass: Martin\Forms\Models\Record 7 | options : filterGroups 8 | conditions: "`group` in (:filtered)" 9 | 10 | created_at: 11 | label : martin.forms::lang.controllers.records.columns.created_at 12 | type : daterange 13 | conditions: created_at >= ':after' AND created_at <= ':before' -------------------------------------------------------------------------------- /controllers/records/config_list.yaml: -------------------------------------------------------------------------------- 1 | list : $/martin/forms/models/record/columns.yaml 2 | modelClass : Martin\Forms\Models\Record 3 | title : martin.forms::lang.controllers.records.title 4 | recordUrl : martin/forms/records/view/:id 5 | noRecordsMessage: backend::lang.list.no_records 6 | recordsPerPage : 20 7 | showSetup : true 8 | showSorting : true 9 | showCheckboxes : true 10 | filter : config_filter.yaml 11 | 12 | defaultSort: 13 | column : created_at 14 | direction: desc 15 | 16 | toolbar: 17 | buttons: partials/list_toolbar 18 | search : 19 | prompt: backend::lang.list.search_prompt -------------------------------------------------------------------------------- /controllers/records/index.htm: -------------------------------------------------------------------------------- 1 | listRender() ?> -------------------------------------------------------------------------------- /controllers/records/partials/_action_button.htm: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /controllers/records/partials/_list_toolbar.htm: -------------------------------------------------------------------------------- 1 |
    2 | 3 | makePartial('$/martin/forms/controllers/records/partials/_action_button.htm', [ 5 | 'type' => 'danger', 6 | 'icon' => 'oc-icon-trash', 7 | 'label' => e(trans('backend::lang.list.delete_selected')), 8 | 'onclick' => "$(this).data('request-data', { checked: $('.control-list').listWidget('getChecked') })", 9 | 'needs_selected' => true, 10 | 'request' => 'onDelete', 11 | 'request_confirm' => e(trans('backend::lang.list.delete_selected_confirm')), 12 | 'request_success' => " 13 | $('#records-toolbar').find('button').prop('disabled', true); 14 | $.oc.flashMsg({ 15 | 'text': '" . e(trans('backend::lang.list.delete_selected_success')) . "', 16 | 'class': 'success', 17 | 'interval': 3 18 | }); 19 | $.oc.sideNav.setCounter('forms/records', data.counter); 20 | $('#Lists').html(data.list['#Lists']); 21 | ", 22 | ]); 23 | ?> 24 | 25 |
    26 | makePartial('$/martin/forms/controllers/records/partials/_action_button.htm', [ 28 | 'type' => 'default', 29 | 'icon' => 'oc-icon-eye-slash', 30 | 'label' => e(trans('martin.forms::lang.controllers.records.buttons.unread')), 31 | 'onclick' => "$(this).data('request-data', { state: 'unread', checked: $('.control-list').listWidget('getChecked') })", 32 | 'needs_selected' => true, 33 | 'request' => 'onReadState', 34 | 'request_confirm' => '', 35 | 'request_success' => " 36 | $('#records-toolbar').find('button').prop('disabled', true); 37 | $.oc.sideNav.setCounter('forms/records', data.counter); 38 | $('#Lists').html(data.list['#Lists']); 39 | ", 40 | ]); 41 | 42 | echo $this->makePartial('$/martin/forms/controllers/records/partials/_action_button.htm', [ 43 | 'type' => 'default', 44 | 'icon' => 'oc-icon-eye', 45 | 'label' => e(trans('martin.forms::lang.controllers.records.buttons.read')), 46 | 'onclick' => "$(this).data('request-data', { state: 'read', checked: $('.control-list').listWidget('getChecked') })", 47 | 'needs_selected' => true, 48 | 'request' => 'onReadState', 49 | 'request_confirm' => '', 50 | 'request_success' => " 51 | $('#records-toolbar').find('button').prop('disabled', true); 52 | $.oc.sideNav.setCounter('forms/records', data.counter); 53 | $('#Lists').html(data.list['#Lists']); 54 | ", 55 | ]); 56 | ?> 57 |
    58 | 59 | makePartial('$/martin/forms/controllers/records/partials/_action_button.htm', [ 62 | 'type' => 'danger', 63 | 'icon' => 'oc-icon-history', 64 | 'label' => e(trans('martin.forms::lang.controllers.records.buttons.gdpr_clean')), 65 | 'request' => 'onGDPRClean', 66 | 'request_confirm' => e(trans('martin.forms::lang.controllers.records.alerts.gdpr_confirm')), 67 | 'request_success' => " 68 | $('#records-toolbar').find('button').blur(); 69 | $.oc.sideNav.setCounter('forms/records', data.counter); 70 | $('#Lists').html(data.list['#Lists']); 71 | ", 72 | ]); 73 | } 74 | ?> 75 | 76 |
    -------------------------------------------------------------------------------- /controllers/records/partials/_view_toolbar.htm: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 | 11 |
    12 |
    13 |
    -------------------------------------------------------------------------------- /controllers/records/view.htm: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | makePartial('partials/view_toolbar') ?> 9 | 10 |

    Record #id ?>

    11 | 12 | 13 | form_data_arr as $label => $value): ?> 14 | 15 | 16 | 25 | 26 | 27 | files) > 0): ?> 28 | 29 | 30 | 42 | 43 | 44 |
    : 17 | 18 |
      19 | 20 |
    21 | 22 | 23 | 24 |
    Attached Files: 31 | 41 |
    45 | 46 |
    47 |

    : group ?>

    48 |

    : ip ?>

    49 |

    : created_at ?>

    50 |
    -------------------------------------------------------------------------------- /lang/de/lang.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'name' => 'Magic Forms', 7 | 'description' => 'einfaches Erstellen von AJAX Formularen' 8 | ], 9 | 10 | 'menu' => [ 11 | 'label' => 'Magic Forms', 12 | 'records' => ['label' => 'Records'], 13 | 'exports' => ['label' => 'Export'], 14 | 'settings' => 'Konfigurieren der plugin parameter', 15 | ], 16 | 17 | 'controllers' => [ 18 | 'records' => [ 19 | 'title' => 'Einträge anzeigen', 20 | 'view_title' => 'Details zum Eintrag', 21 | 'error' => 'Eintrag nicht gefunden', 22 | 'deleted' => 'Eintrag erfolgreich gelöscht', 23 | 'columns' => [ 24 | 'id' => 'Eintrag ID', 25 | 'group' => 'Gruppe', 26 | 'ip' => 'IP Adresse', 27 | 'form_data' => 'Gespeicherte Felder', 28 | 'files' => 'Anhänge', 29 | 'created_at' => 'Erstellt', 30 | ], 31 | 'buttons' => [ 32 | 'read' => 'Als gelesen markieren', 33 | 'unread' => 'Als ungelesen markieren', 34 | 'gdpr_clean' => 'DSVGO Aufräumung', 35 | ], 36 | 'alerts' => [ 37 | 'gdpr_confirm' => "Sind sie sicher dass Sie alte Einträge aufräumen wollen?\nDiese Aktion kann nicht wiederrufen werden!", 38 | 'gdpr_success' => 'DSVGO Aufräumung wurde erfolgreich ausgeführt', 39 | 'gdpr_perms' => 'Sie haben keine Berechtigung für diese Funktion.', 40 | ], 41 | ], 42 | 'exports' => [ 43 | 'title' => 'Einträge exportieren', 44 | 'breadcrumb' => 'Exportieren', 45 | 'filter_section' => '1. Einträge filtern', 46 | 'filter_type' => 'Alle Einträge exportieren', 47 | 'filter_groups' => 'Gruppen', 48 | 'filter_date_after' => 'Nach dem Datum', 49 | 'filter_date_before' => 'Vor dem Datum', 50 | 'options_section' => '2. Extra Optionen', 51 | 'options_metadata' => 'Inklusive Metadaten', 52 | 'options_metadata_com' => 'Exportiere die Einträge mit Metadaten (Eintrag ID, Gruppe, IP, Erstellungsdatum)', 53 | 'options_deleted' => 'Inklusive gelöschter Einträge', 54 | 'options_delimiter' => 'Alternatives Trennzeichen benutzen', 55 | 'options_delimiter_com' => 'Semikolon als Trennzeichen', 56 | 'options_utf' => 'Enkodierung in UTF8', 57 | 'options_utf_com' => 'Enkodierung Ihrer CSV-Datei im UTF-8 Format für die Unterstützung von Umlauten und Sonderzeichen.', 58 | ], 59 | ], 60 | 61 | 'components' => [ 62 | 'generic_form' => [ 63 | 'name' => 'Generisches AJAX Formular', 64 | 'description' => 'Mit Standardeinstellungen rendered ein generisches Formular; Überschreib Komponenten HTML mit deinen eigenen Feldern.', 65 | ], 66 | 'upload_form' => [ 67 | 'name' => 'Upload AJAX Formular [BETA]', 68 | 'description' => 'Zeigt wie man Dateiupload in deinem Formular implementiert.', 69 | ], 70 | 'empty_form' => [ 71 | 'name' => 'Leeres AJAX Formular', 72 | 'description' => 'Erstellt eine leere Vorlage für dein individuelles Formular; Überschreib Komponenten HTML.', 73 | ], 74 | 'shared' => [ 75 | 'csrf_error' => 'Formular Seitzung abgelaufen! Bitte die Seite neuladen.', 76 | 'recaptcha_warn' => 'Warnung: reCAPTCHA ist nicht ordentlich konfiguriert. Bitte, gehe zum Backend > Einstellungen > CMS > Magic Forms um reCAPTCHA zu kofigurieren.', 77 | 'group_validation' => 'Formular Validierung', 78 | 'group_messages' => 'Flash Benachrichtung', 79 | 'group_mail' => 'Benachrichtigungen Einstellungen', 80 | 'group_mail_resp' => 'Automatische Antwort Einstellungen', 81 | 'group_settings' => 'Weitere Einstellungen', 82 | 'group_security' => 'Sicherheit', 83 | 'group_recaptcha' => 'reCAPTCHA Einstellungen', 84 | 'group_advanced' => 'Fortgeschrittene Einstellungen', 85 | 'group_uploader' => 'Uploader Einstellungen', 86 | 'validation_req' => 'Die Eigenschaft wird benötigt', 87 | 'group' => ['title' => 'Gruppe' , 'description' => 'Organisiere deine Formulare durch eigene Gruppennamen. Diese Option ist für das Exportieren der Daten sehr praktischt.'], 88 | 'rules' => ['title' => 'Regeln' , 'description' => 'Erstelle eigene Regeln Mithilfe der Laravel Validierungsfunktion'], 89 | 'rules_messages' => ['title' => 'Regeln Benachrichtigungen' , 'description' => 'Erstelle eigene Benachrichtigungen Mithilfe der Laravel Validierungsfunktion'], 90 | 'custom_attributes' => ['title' => 'Eigene Attribute' , 'description' => 'Erstelle eigene Attribute Mithilfe der Laravel Validierungsfunktion'], 91 | 'messages_success' => ['title' => 'Erfolg' , 'description' => 'Nachricht bei erfolgreicher Übermittlung des Formulars', 'default' => 'Your form was successfully submitted' ], 92 | 'messages_errors' => ['title' => 'Fehler' , 'description' => 'Nachricht bei Fehlern während der Eingabe der Formulardaten' , 'default' => 'There were errors with your submission'], 93 | 'messages_partial' => ['title' => 'Benutze eigene Partial' , 'description' => 'Überschreibe Flash-Nachrichten mit deinem eigenem Partial in deinem Theme'], 94 | 'mail_enabled' => ['title' => 'Sende Benachrichtigungen' , 'description' => 'Sende eine Benachrichtigungsmail nach jeder erfolgreichen Übertragung.'], 95 | 'mail_subject' => ['title' => 'Betreff' , 'description' => 'Überschreibe die standard E-Mail Betreffszeile'], 96 | 'mail_recipients' => ['title' => 'Empfänger' , 'description' => 'E-Mail Empfänger eintragen (Füge eine E-Mail Adresse pro Zeile ein)'], 97 | 'mail_bcc' => ['title' => 'BCC' , 'description' => 'Sende blind carbon copy (BCC) zur folgenden Empfängern (Füge eine E-Mail Adresse per Zeile ein)'], 98 | '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.'], 99 | '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.'], 100 | 'mail_uploads' => ['title' => 'Sende Uploads' , 'description' => 'Sende Uploads als Anhang'], 101 | 'mail_resp_enabled' => ['title' => 'Sende Auto-Antwort' , 'description' => 'Sende eine automatische Antwort E-Mail zu der Person die dein Formular ausgfüllt.'], 102 | 'mail_resp_field' => ['title' => 'E-Mail Feld' , 'description' => 'Formular Feld das die E-Mail Adresse enthät für die automatische Antwort'], 103 | 'mail_resp_from' => ['title' => 'Absender Adresse' , 'description' => 'Absender E-Mail Adresse was benutzt werden soll (z.B. noreply@yourcompany.com)'], 104 | 'mail_resp_subject' => ['title' => 'Betreff' , 'description' => 'Überschreibe standard E-Mail Betreffszeile'], 105 | 'reset_form' => ['title' => 'Resete Formular' , 'description' => 'Resete das Formular nach erfolgreichen Übertragung'], 106 | 'redirect' => ['title' => 'Umleitung bei Erfolg', 'description' => 'URL-Umleitung nach erfoglreichen Übertragung'], 107 | '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'], 108 | 'js_on_success' => ['title' => 'JS bei Erfolg' , 'description' => 'Führe eigenen JavaScript-Code aus wenn das Formular erfolgreich übertragen wurde. Keine Script Tags benutzen!'], 109 | '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!'], 110 | 'allowed_fields' => ['title' => 'Erlaubte Felder' , 'description' => 'Festlegen welche Felder gefiltert und gespeichert werden sollen. (Füge ein Feld per Zeile ein)'], 111 | 'anonymize_ip' => ['title' => 'IP-Anonymisierung' , 'description' => 'IP-Adresse nicht speichern.', 'full' => 'Voll', 'partial' => 'Teilanonymisierung', 'disabled' => 'Deaktiviert'], 112 | 'sanitize_data' => ['title' => 'Bereinigung der Formulardaten' , 'description' => 'Bereinige die Formulardaten und speichere das Ergebnis in der Datenbank', 'disabled' => 'deaktiviert', 'htmlspecialchars' => 'Benutzer htmlspecialchars'], 113 | 'recaptcha_enabled' => ['title' => 'Aktiviere reCAPTCHA' , 'description' => 'reCAPTCHA-Widget in deinem Formular einfügen'], 114 | 'recaptcha_theme' => ['title' => 'Theme' , 'description' => 'Farbschema des Widgets', 'light' => 'Hell' , 'dark' => 'Dunkel'], 115 | 'recaptcha_type' => ['title' => 'Typ' , 'description' => 'reCAPTCHA Typ festlegen' , 'image' => 'Bild' , 'audio' => 'Audio'], 116 | 'recaptcha_size' => [ 117 | 'title' => 'Größe', 118 | 'description' => 'Die Größe des Widgets', 119 | 'normal' => 'Normal', 120 | 'compact' => 'Kompakt', 121 | 'invisible' => 'Unsichtbar', 122 | ], 123 | '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.'], 124 | 'emails_date_format' => ['title' => 'Datum Formatierung in E-Mails', 'description' => 'Ändere die Datumformatierung die dann in E-Mails verwendet wird.'], 125 | 'uploader_enable' => ['title' => 'Erlaube Uploads' , 'description' => 'Aktiviere Datei-Upload. Diese Option muss aufgrund der Sicherheitseinstellungen explizit aktiviert werden.'], 126 | 'uploader_multi' => ['title' => 'Merhfachdateien' , 'description' => 'Erlaube Mehrfachdateien-Uploads'], 127 | 'uploader_pholder' => ['title' => 'Platzhalter Text' , 'description' => 'Platzhalter Text der angezeigt wird so lange keine Datei hochgeladen wurde', 'default' => 'Click or drag files to upload'], 128 | 'uploader_maxsize' => ['title' => 'Dateigröße Limitierung' , 'description' => 'Die maximale Dateigröße die hochgeladen werden kann in Megabytes'], 129 | 'uploader_types' => ['title' => 'Erlaubte Datei-Typen' , 'description' => 'Erlaubte Dateiendungen oder ein Stern (*) für alle Typen (Füge eine Dateiendung per Zeile ein)'], 130 | '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 ?'], 131 | ] 132 | ], 133 | 134 | 'settings' => [ 135 | 'tabs' => ['general' => 'Allgemein', 'recaptcha' => 'reCAPTCHA', 'gdpr' => 'DSVGO'], 136 | 'section_flash_messages' => 'Flash Nachrichten', 137 | 'global_messages_success' => ['label' => 'Globale Erfolgsnachricht', 'comment' => '(Diese Einstellung kann aus der Komponente heraus überschrieben werden)', 'default' => 'Your form was successfully submitted'], 138 | 'global_messages_errors' => ['label' => 'Globale Fehlernachricht' , 'comment' => '(Diese Einstellung kann aus der Komponente heraus überschrieben werden)', 'default' => 'There were errors with your submission'], 139 | 'plugin_help' => 'Die Plugindokumentation erreichst Du über die GitHub repo:', 140 | 'global_hide_button' => 'Navigationsobjekt vestecken', 141 | 'global_hide_button_desc' => 'Praktisch, wenn Du eigene Events mit einem eigenem Plugin nutzen möchtest.', 142 | 'section_recaptcha' => 'reCAPTCHA Einstellungen', 143 | 'recaptcha_site_key' => 'Site key', 144 | 'recaptcha_secret_key' => 'Secret key', 145 | 'gdpr_help_title' => 'Information', 146 | '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.', 147 | 'gdpr_enable' => 'Aktiviere EU-DSVGO konforme Aufräumung', 148 | 'gdpr_days' => 'Behalte die Einträge für die Anzahl an: X Tagen', 149 | ], 150 | 151 | 'permissions' => [ 152 | 'tab' => 'Magic Forms', 153 | 'access_records' => 'Zugriff auf gespeicherte Formular-Einsendedaten', 154 | 'access_exports' => 'Zugriff für das Exportieren der gespeicherten Formular-Einsendedaten', 155 | 'access_settings' => 'Zugriff auf Modul-Konfiguration', 156 | 'gdpr_cleanup' => 'Führe EU-DSVGO Aufräumung der Datenbank durch.', 157 | ], 158 | 159 | 'mails' => [ 160 | 'form_notification' => ['description' => 'Benachrichtung nach erfolgreicher Einsendung der Formulardaten.'], 161 | 'form_autoresponse' => ['description' => 'Automatische Antwort nach erfolgreicher Einsendung der Formulardaten.'], 162 | ], 163 | 164 | 'validation' => [ 165 | 'recaptcha_error' => 'Kann das reCAPTCHA Feld nicht validieren.' 166 | ], 167 | 168 | 'classes' => [ 169 | 'GDPR' => [ 170 | 'alert_gdpr_disabled' => 'EU-DSVGO Einstellungen sind deaktiviert.', 171 | 'alert_invalid_gdpr' => 'Ungültige Einstellung des EU-DSVGO Aufräumvorgangs Intervals nach X Tagen.', 172 | ] 173 | ] 174 | 175 | ]; 176 | 177 | ?> 178 | -------------------------------------------------------------------------------- /lang/en/lang.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'name' => 'Magic Forms', 7 | 'description' => 'Create easy AJAX forms' 8 | ], 9 | 10 | 'menu' => [ 11 | 'label' => 'Magic Forms', 12 | 'records' => ['label' => 'Records'], 13 | 'exports' => ['label' => 'Export'], 14 | 'settings' => 'Configure plugin parameters', 15 | ], 16 | 17 | 'controllers' => [ 18 | 'records' => [ 19 | 'title' => 'View Records', 20 | 'view_title' => 'Record Details', 21 | 'error' => 'Record not found', 22 | 'deleted' => 'Record deleted successfully', 23 | 'columns' => [ 24 | 'id' => 'Record ID', 25 | 'group' => 'Group', 26 | 'ip' => 'IP Address', 27 | 'form_data' => 'Stored Fields', 28 | 'files' => 'Attached Files', 29 | 'created_at' => 'Created', 30 | ], 31 | 'buttons' => [ 32 | 'read' => 'Mark as Read', 33 | 'unread' => 'Mark as Unread', 34 | 'gdpr_clean' => 'GDPR Clean', 35 | ], 36 | 'alerts' => [ 37 | 'gdpr_confirm' => "Are you sure you want to clean old records?\nThis action cannot be undone!", 38 | 'gdpr_success' => 'GDPR cleanup was executed successfully', 39 | 'gdpr_perms' => 'You don\'t have permission to this feature', 40 | ], 41 | ], 42 | 'exports' => [ 43 | 'title' => 'Export Records', 44 | 'breadcrumb' => 'Export', 45 | 'filter_section' => '1. Filter records', 46 | 'filter_type' => 'Export all records', 47 | 'filter_groups' => 'Groups', 48 | 'filter_date_after' => 'Date after', 49 | 'filter_date_before' => 'Date before', 50 | 'options_section' => '2. Extra options', 51 | 'options_metadata' => 'Include metadata', 52 | 'options_metadata_com' => 'Export records with metadata (Record ID, group, IP, created date)', 53 | 'options_deleted' => 'Include deleted records', 54 | 'options_files' => 'Include attached files', 55 | 'options_files_com' => 'Only download URLs will be exported', 56 | 'options_delimiter' => 'Use alternative delimiter', 57 | 'options_delimiter_com' => 'Use semicolon as delimiter', 58 | 'options_utf' => 'Encode in UTF8', 59 | 'options_utf_com' => 'Encode your csv in UTF-8 to support non standard characters', 60 | ], 61 | ], 62 | 63 | 'components' => [ 64 | 'generic_form' => [ 65 | 'name' => 'Generic AJAX Form', 66 | 'description' => 'By default renders a generic form; override component HTML with your custom fields.', 67 | ], 68 | 'empty_form' => [ 69 | 'name' => 'Empty AJAX Form', 70 | 'description' => 'Create a empty template for your custom form; override component HTML.', 71 | ], 72 | 'filepond_form' => [ 73 | 'name' => 'Upload AJAX Form', 74 | 'description' => 'Sample AJAX form with upload capabilities using FilePond.', 75 | ], 76 | 'shared' => [ 77 | 'csrf_error' => 'Form session expired! Please refresh the page.', 78 | 'recaptcha_warn' => 'Warning: reCAPTCHA is not properly configured. Please, goto Backend > Settings > CMS > Magic Forms and configure.', 79 | 'group_validation' => 'Form Validation', 80 | 'group_messages' => 'Flash Messages', 81 | 'group_mail' => 'Notifications Settings', 82 | 'group_mail_resp' => 'Auto-Response Settings', 83 | 'group_settings' => 'More Settings', 84 | 'group_security' => 'Security', 85 | 'group_recaptcha' => 'reCAPTCHA Settings', 86 | 'group_advanced' => 'Advanced Settings', 87 | 'group_uploader' => 'Uploader Settings', 88 | 'validation_req' => 'The property is required', 89 | 'group' => ['title' => 'Group', 'description' => 'Organize your forms with a custom group name. This option is useful when exporting data.'], 90 | 'rules' => ['title' => 'Rules', 'description' => 'Set your own rules using Laravel validation'], 91 | 'rules_messages' => ['title' => 'Rules Messages', 'description' => 'Use your own rules messages using Laravel validation'], 92 | 'custom_attributes' => ['title' => 'Custom Attributes', 'description' => 'Use your own custom attributes using Laravel validation'], 93 | 'messages_success' => ['title' => 'Success', 'description' => 'Message when the form is successfully submitted', 'default' => 'Your form was successfully submitted'], 94 | 'messages_errors' => ['title' => 'Errors', 'description' => 'Message when the form contains errors', 'default' => 'There were errors with your submission'], 95 | 'messages_partial' => ['title' => 'Use Custom Partial', 'description' => 'Override flash messages with your custom partial inside your theme'], 96 | 'mail_enabled' => ['title' => 'Send Notifications', 'description' => 'Send mail notifications on every form submitted'], 97 | 'mail_subject' => ['title' => 'Subject', 'description' => 'Override default email subject'], 98 | 'mail_recipients' => ['title' => 'Recipients', 'description' => 'Specify email recipients (add one address per line)'], 99 | 'mail_bcc' => ['title' => 'BCC', 'description' => 'Send blind carbon copy to email recipients (add one address per line)'], 100 | 'mail_replyto' => ['title' => 'ReplyTo Email Field', 'description' => 'Form field containing the email address of sender to be used as "ReplyTo"'], 101 | '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.'], 102 | 'mail_uploads' => ['title' => 'Send Uploads', 'description' => 'Send uploads as attachments'], 103 | 'mail_resp_enabled' => ['title' => 'Send Auto-Response', 'description' => 'Send an auto-response email to the person submitting the form'], 104 | 'mail_resp_field' => ['title' => 'Email Field', 'description' => 'Form field containing the email address of the recipient of auto-response'], 105 | 'mail_resp_name' => ['title' => 'Sender Name', 'description' => 'Name of auto-response email sender (e.g. John Doe)'], 106 | 'mail_resp_from' => ['title' => 'Sender Address', 'description' => 'Email address of auto-response email sender (e.g. noreply@yourcompany.com)'], 107 | 'mail_resp_subject' => ['title' => 'Subject', 'description' => 'Override default email subject'], 108 | 'reset_form' => ['title' => 'Reset Form', 'description' => 'Reset form after successfully submit'], 109 | 'redirect' => ['title' => 'Redirect on Success', 'description' => 'Redirect to URL on successfully submit.'], 110 | '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'], 111 | 'js_on_success' => ['title' => 'JS on Success', 'description' => 'Execute custom JavaScript code when the form was successfully submitted. Don\'t use script tags.'], 112 | 'js_on_error' => ['title' => 'JS on Error', 'description' => 'Execute custom JavaScript code when the form doesn\'t validate. Don\'t use script tags.'], 113 | 'allowed_fields' => ['title' => 'Allowed Fields', 'description' => 'Specify which fields should be filtered and stored (add one field name per line)'], 114 | 'anonymize_ip' => ['title' => 'Anonymize IP', 'description' => 'Don\'t store IP address', 'full' => 'Full', 'partial' => 'Partial', 'disabled' => 'Disabled'], 115 | 'sanitize_data' => ['title' => 'Sanitize form data', 'description' => 'Sanitize form data and save result on database', 'disabled' => 'Disabled', 'htmlspecialchars' => 'Use htmlspecialchars'], 116 | 'recaptcha_enabled' => ['title' => 'Enable reCAPTCHA', 'description' => 'Insert the reCAPTCHA widget on your form'], 117 | 'recaptcha_theme' => ['title' => 'Theme', 'description' => 'The color theme of the widget', 'light' => 'Light', 'dark' => 'Dark'], 118 | 'recaptcha_type' => ['title' => 'Type', 'description' => 'The type of CAPTCHA to serve', 'image' => 'Image', 'audio' => 'Audio'], 119 | 'recaptcha_size' => [ 120 | 'title' => 'Size', 121 | 'description' => 'The size of the widget', 122 | 'normal' => 'Normal', 123 | 'compact' => 'Compact', 124 | 'invisible' => 'Invisible', 125 | ], 126 | 'skip_database' => ['title' => 'Skip DB', 'description' => 'Don\'t store this form on database. Useful if you want to use events with your custom plugin.'], 127 | 'emails_date_format' => ['title' => 'Date format on emails', 'description' => 'Set custom format for dates used on emails subjects.'], 128 | 'uploader_enable' => ['title' => 'Allow uploads', 'description' => 'Enable files uploading. You need to explicitly enable this option as a security measure.'], 129 | 'uploader_filesize' => ['title' => 'File size limit', 'description' => 'The maximum file size that can be uploaded. Ex: 10MB, 750KB.'], 130 | ] 131 | ], 132 | 133 | 'settings' => [ 134 | 'tabs' => ['general' => 'General', 'recaptcha' => 'reCAPTCHA', 'gdpr' => 'GDPR'], 135 | 'section_flash_messages' => 'Flash Messages', 136 | 'global_messages_success' => ['label' => 'Global Success Message', 'comment' => '(This setting can be overridden from the component)', 'default' => 'Your form was successfully submitted'], 137 | 'global_messages_errors' => ['label' => 'Global Errors Message', 'comment' => '(This setting can be overridden from the component)', 'default' => 'There were errors with your submission'], 138 | 'plugin_help' => 'You can access plugin documentation at GitHub repo:', 139 | 'global_hide_button' => 'Hide navigation item', 140 | 'global_hide_button_desc' => 'Useful if you want to use events with your custom plugin.', 141 | 'section_recaptcha' => 'reCAPTCHA Settings', 142 | 'recaptcha_site_key' => 'Site key', 143 | 'recaptcha_secret_key' => 'Secret key', 144 | 'gdpr_help_title' => 'Information', 145 | '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', 146 | 'gdpr_enable' => 'Enable GDPR', 147 | 'gdpr_days' => 'Keep records for a maximum of X days', 148 | ], 149 | 150 | 'permissions' => [ 151 | 'tab' => 'Magic Forms', 152 | 'access_records' => 'Access stored forms data', 153 | 'access_exports' => 'Access to export stored data', 154 | 'access_settings' => 'Access module configuration', 155 | 'gdpr_cleanup' => 'Perform GDPR database cleanup', 156 | ], 157 | 158 | 'mails' => [ 159 | 'form_notification' => ['description' => 'Notify when a form is submitted'], 160 | 'form_autoresponse' => ['description' => 'Auto-Response when a form is submitted'], 161 | ], 162 | 163 | 'validation' => [ 164 | 'recaptcha_error' => 'Cannot validate reCAPTCHA field' 165 | ], 166 | 167 | 'classes' => [ 168 | 'GDPR' => [ 169 | 'alert_gdpr_disabled' => 'GDPR options are disabled', 170 | 'alert_invalid_gdpr' => 'Invalid GDPR days setting value', 171 | ], 172 | 'FilePond' => [ 173 | 'error_filesize' => 'File size not allowed', 174 | 'error_filetype' => 'File type not allowed', 175 | 'error_savefile' => 'Could not save file', 176 | ] 177 | ] 178 | 179 | ]; 180 | -------------------------------------------------------------------------------- /lang/fr/lang.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'name' => 'Magic Forms', 5 | 'description' => 'Créer des formulaires AJAX facilement' 6 | ], 7 | 'menu' => [ 8 | 'label' => 'Magic Forms', 9 | 'records' => ['label' => 'Enregistrements'], 10 | 'exports' => ['label' => 'Export'], 11 | 'settings' => 'Configurer kes parameters du plugin', 12 | ], 13 | 'controllers' => [ 14 | 'records' => [ 15 | 'title' => 'Voir les enregistrements', 16 | 'view_title' => 'Détails de l\'enregistrement', 17 | 'error' => 'Enregistrement non trouvé', 18 | 'deleted' => 'Enregistrement supprimé avec succès', 19 | 'columns' => [ 20 | 'id' => 'Enregistrement N°', 21 | 'group' => 'Groupe', 22 | 'ip' => 'Adresse IP', 23 | 'form_data' => 'Champs stockés', 24 | 'files' => 'Fichiers attachés', 25 | 'created_at' => 'Créer le', 26 | ], 27 | 'buttons' => [ 28 | 'read' => 'Marquer comme lu', 29 | 'unread' => 'Marquer comme non lu', 30 | 'gdpr_clean' => 'Nettoyage RGPD', 31 | ], 32 | 'alerts' => [ 33 | 'gdpr_confirm' => "Êtes-vous sûr de vouloir nettoyer les anciens enregistrements?\nCette action ne peut pas être annulée!", 34 | 'gdpr_success' => 'Le nettoyage RGPD a été exécuté avec succès', 35 | 'gdpr_perms' => 'Vous n\'avez pas l\'autorisation pour utiliser cette fonctionnalité', 36 | ], 37 | ], 38 | 'exports' => [ 39 | 'title' => 'Exporter les enregistrements', 40 | 'breadcrumb' => 'Exporter', 41 | 'filter_section' => '1. Filtrer les enregistrements', 42 | 'filter_type' => 'Exporter tous les enregistrements', 43 | 'filter_groups' => 'Groupes', 44 | 'filter_date_after' => 'Date de début', 45 | 'filter_date_before' => 'Date de fin', 46 | 'options_section' => '2. Options supplémentaires', 47 | 'options_metadata' => 'Inclure les metadonnées', 48 | 'options_metadata_com' => 'Exporter les enregistrements avec les metadonnées (n°, groupe, IP, date de création)', 49 | 'options_deleted' => 'Inclure les enregistrements supprimés', 50 | ], 51 | ], 52 | 'components' => [ 53 | 'generic_form' => [ 54 | 'name' => 'Formulaire générique AJAX', 55 | 'description' => 'Rendu d\'un formulaire générique; remplacer le composant HTML par vos propres champs personnalisés.', 56 | ], 57 | 'upload_form' => [ 58 | 'name' => 'Téléchargements AJAX [BETA]', 59 | 'description' => 'Montre comment implémenter des téléchargements de fichiers sur votre formulaire.', 60 | ], 61 | 'empty_form' => [ 62 | 'name' => 'Formulaire AJAX vide', 63 | 'description' => 'Créer un modèle vide pour votre formulaire personnalisé; remplacer le composant HTML.', 64 | ], 65 | 'shared' => [ 66 | 'csrf_error' => 'La session du formulaire a expirée ! Veuillez actualiser la page.', 67 | 'recaptcha_warn' => 'Avertissement: reCAPTCHA n\'est pas correctement configuré. Àllez dans Backend > Paramètres > CMS > Magic Forms et configurez svp.', 68 | 'group_validation' => 'Validation du formulaire', 69 | 'group_messages' => 'Messages Flash ', 70 | 'group_mail' => 'Notifications', 71 | 'group_mail_resp' => 'Réponse automatique', 72 | 'group_settings' => 'Plus de réglages', 73 | 'group_security' => 'Securité', 74 | 'group_recaptcha' => 'reCAPTCHA', 75 | 'group_uploader' => 'Transfert de fichiers', 76 | 'validation_req' => 'La propriété est requise', 77 | 'group' => ['title' => 'Groupe' , 'description' => 'Organisez vos formulaires avec un nom de groupe personnalisé. Cette option est utile lorsque vous exportez des données.'], 78 | 'rules' => ['title' => 'Règles' , 'description' => 'Définissez vos propres règles en utilisant la validation de Laravel'], 79 | 'rules_messages' => ['title' => 'Messages de règles' , 'description' => 'Utilisez vos propres messages de règles en utilisant la validation de Laravel'], 80 | 'messages_success' => ['title' => 'Succès' , 'description' => 'Message lorsque le formulaire est soumis avec succès' , 'default' => 'Votre formulaire a été envoyé avec succès' ], 81 | 'messages_errors' => ['title' => 'Erreurs' , 'description' => 'Message lorsque le formulaire contient des erreurs' , 'default' => 'Il y a eu des erreurs dans votre formulaire'], 82 | 'messages_partial' => ['title' => 'Utiliser un modèle partiel personnalisé', 'description' => 'Modifier le message flash avec un modèle partiel personnalisé de votre thème'], 83 | 'mail_enabled' => ['title' => 'Envoyer des notifications' , 'description' => 'Envoyer des notifications par mail sur chaque formulaire envoyé'], 84 | 'mail_subject' => ['title' => 'Sujet' , 'description' => 'Remplacer le sujet par défaut du courrier électronique'], 85 | 'mail_recipients' => ['title' => 'Destinataires' , 'description' => 'Spécifier les destinataires des e-mails (ajouter une adresse par ligne)'], 86 | 'mail_bcc' => ['title' => 'CCI' , 'description' => 'Envoyer une copie carbone invisible aux destinataires des e-mails (ajouter une adresse par ligne)'], 87 | '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"'], 88 | 'mail_uploads' => ['title' => 'Envoyer les téléchargements' , 'description' => 'Envoi des fichiers téléchargés en pièce jointe'], 89 | '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.'], 90 | 'mail_uploads' => ['title' => 'Envoyer les téléchargements' , 'description' => 'Envoyer les téléchargements en pièces jointes'], 91 | 'mail_resp_enabled' => ['title' => 'Envoyer une réponse automatique' , 'description' => 'Envoyer un e-mail d\'auto-réponse à la personne qui soumet le formulaire'], 92 | 'mail_resp_field' => ['title' => 'Champ email' , 'description' => 'Champ de formulaire contenant l\'adresse e-mail du destinataire de réponse automatique '], 93 | '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)'], 94 | 'mail_resp_subject' => ['title' => 'Sujet' , 'description' => 'Remplacer le sujet par défaut du courrier électronique'], 95 | 'reset_form' => ['title' => 'Réinitialiser le formulaire' , 'description' => 'Réinitialiser le formulaire après l\'envoi réussi'], 96 | '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.'], 97 | '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'], 98 | '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'], 99 | '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.'], 100 | 'allowed_fields' => ['title' => 'Allowed Fields' , 'description' => 'Spécifiez quels champs doivent être filtrés et stockés (ajoutez un nom de champ par ligne).'], 101 | 'anonymize_ip' => ['title' => 'Anonymiser IP' , 'description' => 'Ne pas enregistrer les adresses IP', 'full' => 'Complet', 'partial' => 'Partiel', 'disabled' => 'Désactivé'], 102 | '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'], 103 | 'recaptcha_enabled' => ['title' => 'Activer reCAPTCHA' , 'description' => 'Insère le widget reCAPTCHA sur votre formulaire'], 104 | 'recaptcha_theme' => ['title' => 'Thème' , 'description' => 'Le thème de couleur du widget' , 'light' => 'Léger' , 'dark' => 'Sombre'], 105 | 'recaptcha_type' => ['title' => 'Type' , 'description' => 'Le type de CAPTCHA à servir' , 'image' => 'Image' , 'audio' => 'Audio'], 106 | 'recaptcha_size' => ['title' => 'Taille' , 'description' => 'La taille du widget' , 'normal' => 'Normale', 'compact' => 'Compacte'], 107 | '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é.'], 108 | '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é.'], 109 | 'uploader_multi' => ['title' => 'Fichiers multiples' , 'description' => 'Autoriser plusieurs téléchargements de fichiers'], 110 | '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'], 111 | 'uploader_maxsize' => ['title' => 'Limite de taille de fichier' , 'description' => 'Taille maximale du fichier pouvant être téléchargée en mégaoctets'], 112 | '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)'], 113 | 'uploader_remFile' => ['title' => 'Texte de Suppression' , 'description' => 'Texte à afficher dans la popup lors de la suppression d\'un fichier', 'default' => 'Êtes-vous sûr ?'], 114 | ] 115 | ], 116 | 'settings' => [ 117 | 'tabs' => ['general' => 'Général', 'recaptcha' => 'reCAPTCHA', 'gdpr' => 'RGPD'], 118 | 'section_flash_messages' => 'Messages Flash', 119 | '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'], 120 | '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'], 121 | 'plugin_help' => 'Vous pouvez accéder à la documentation du plugin sur GitHub :', 122 | 'global_hide_button' => 'Masquer l\'élément de navigation', 123 | 'global_hide_button_desc' => 'Utile si vous souhaitez utiliser des événements avec votre plugin personnalisé.', 124 | 'section_recaptcha' => 'Paramètres reCAPTCHA', 125 | 'recaptcha_site_key' => 'Clé du site', 126 | 'recaptcha_secret_key' => 'Clé secrète', 127 | 'gdpr_help_title' => 'Information', 128 | '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.', 129 | 'gdpr_enable' => 'Activer RGPD', 130 | 'gdpr_days' => 'Garder les enregistrements pour un maximum de X jours', 131 | ], 132 | 'permissions' => [ 133 | 'tab' => 'Magic Forms', 134 | 'access_records' => 'Accéder aux données des formulaires stockés', 135 | 'access_exports' => 'Accès aux exports des formulaires stockés', 136 | 'access_settings' => 'Accès à la configuration du module', 137 | 'gdpr_cleanup' => 'Accès au nettoyage de la base de donnée RGPD', 138 | ], 139 | 'mails' => [ 140 | 'form_notification' => ['description' => 'Notifier quand un formulaire est envoyé'], 141 | 'form_autoresponse' => ['description' => 'Auto-Réponse lorsqu\'un formulaire est envoyé'], 142 | ], 143 | 'validation' => [ 144 | 'recaptcha_error' => 'Impossible de valider le champ reCAPTCHA' 145 | ], 146 | 'classes' => [ 147 | 'GDPR' => [ 148 | 'alert_gdpr_disabled' => 'LEs options RGPD sont désactivés', 149 | 'alert_invalid_gdpr' => 'Valeur de réglage des jours RGPD invalide', 150 | ] 151 | ] 152 | ]; 153 | ?> 154 | -------------------------------------------------------------------------------- /lang/nl/lang.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'name' => 'Magic Forms', 7 | 'description' => 'Eenvoudige AJAX-formulieren maken', 8 | ], 9 | 10 | 'menu' => [ 11 | 'label' => 'Magic Forms', 12 | 'records' => ['label' => 'Records'], 13 | 'exports' => ['label' => 'Exporteren'], 14 | 'settings' => 'Configureer plugin parameters', 15 | ], 16 | 17 | 'controllers' => [ 18 | 'records' => [ 19 | 'title' => 'Bekijk Records', 20 | 'view_title' => 'Record Details', 21 | 'error' => 'Record niet gevonden', 22 | 'deleted' => 'Record succesvol verwijderd', 23 | 'columns' => [ 24 | 'id' => 'Record ID', 25 | 'group' => 'Groep', 26 | 'ip' => 'IP adres', 27 | 'form_data' => 'Opgeslagen velden', 28 | 'files' => 'Bijlagen', 29 | 'created_at' => 'Gemaakt', 30 | ], 31 | 'buttons' => [ 32 | 'read' => 'Als gelezen markeren', 33 | 'unread' => 'Als ongelezen markeren', 34 | 'gdpr_clean' => 'AVG opschoning uitvoeren', 35 | ], 36 | 'alerts' => [ 37 | 'gdpr_confirm' => "Weet u zeker dat u oude gegevens wilt wissen?\nDeze actie kan niet ongedaan worden gemaakt!", 38 | 'gdpr_success' => 'AVG-opschoning is met succes uitgevoerd', 39 | 'gdpr_perms' => 'Je hebt geen toestemming voor deze functie.', 40 | ], 41 | ], 42 | 'exports' => [ 43 | 'title' => 'Exporteer Records', 44 | 'breadcrumb' => 'Exporteren', 45 | 'filter_section' => '1. Filter records', 46 | 'filter_type' => 'Exporteer alle records', 47 | 'filter_groups' => 'Groepen', 48 | 'filter_date_after' => 'Vanaf datum', 49 | 'filter_date_before' => 'Tot datum', 50 | 'options_section' => '2. Extra opties', 51 | 'options_metadata' => 'Metagegevens meenemen', 52 | 'options_metadata_com' => 'Exporteer records met metadata (Record ID, groep, IP, aanmaakdatum)', 53 | 'options_deleted' => 'Verwijderde records meenemen', 54 | 'options_delimiter' => 'Use alternative delimiter', 55 | 'options_delimiter_com' => 'Gebruik alternatief scheidingsteken', 56 | 'options_utf' => 'Encoderen in UTF-8', 57 | 'options_utf_com' => 'Codeer uw csv in UTF-8 om niet-standaard tekens te ondersteunen', 58 | ], 59 | ], 60 | 61 | 'components' => [ 62 | 'generic_form' => [ 63 | 'name' => 'Algemeen AJAX Formulier', 64 | 'description' => 'Toont standaard een algemeen formulier; overschrijf component HTML met uw aangepaste velden.', 65 | ], 66 | 'upload_form' => [ 67 | 'name' => 'Upload AJAX Formulier [BETA]', 68 | 'description' => 'Laat zien hoe je bestandsuploads kunt implementeren in je formulier.', 69 | ], 70 | 'empty_form' => [ 71 | 'name' => 'Leeg AJAX Formulier', 72 | 'description' => 'Maak een leeg sjabloon voor uw aangepaste formulier; overschrijf component HTML.', 73 | ], 74 | 'shared' => [ 75 | 'csrf_error' => 'Formulier sessie verlopen! Vernieuw de pagina.', 76 | 'recaptcha_warn' => 'Waarschuwing: reCAPTCHA is niet goed geconfigureerd. Ga aub naar Backend > Instellingen > CMS > Magic Forms en configureer.', 77 | 'group_validation' => 'Formulier Validatie', 78 | 'group_messages' => 'Flash Berichten', 79 | 'group_mail' => 'Notificatie Instellingen', 80 | 'group_mail_resp' => 'Auto-Response Instellingen', 81 | 'group_settings' => 'Meer Instellingen', 82 | 'group_security' => 'Beveiliging', 83 | 'group_recaptcha' => 'reCAPTCHA Instellingen', 84 | 'group_advanced' => 'Geavanceerde Instellingen', 85 | 'group_uploader' => 'Uploader Instellingen', 86 | 'validation_req' => 'De property is verplicht', 87 | 'group' => ['title' => 'Groep' , 'description' => 'Organiseer uw formulieren met een aangepaste groepsnaam. Deze optie is handig bij het exporteren van gegevens.'], 88 | 'rules' => ['title' => 'Regels' , 'description' => 'Stel je eigen regels in met Laravel validatie'], 89 | 'rules_messages' => ['title' => 'Regels Berichten' , 'description' => 'Gebruik uw eigen regels berichten met behulp van Laravel validatie'], 90 | 'custom_attributes' => ['title' => 'Aangepaste attributen' , 'description' => 'Gebruik je eigen aangepaste attributen met Laravel validatie'], 91 | 'messages_success' => ['title' => 'Succes' , 'description' => 'Bericht wanneer het formulier succesvol is verzonden', 'default' => 'Uw formulier is succesvol verzonden'], 92 | 'messages_errors' => ['title' => 'Fouten' , 'description' => 'Bericht wanneer het formulier fouten bevat' , 'default' => 'Er zijn fouten opgetreden bij uw inzending'], 93 | 'messages_partial' => ['title' => 'Gebruik Aangepaste Partial' , 'description' => 'Overschrijf flashberichten met uw eigen partials in uw thema'], 94 | 'mail_enabled' => ['title' => 'Stuur Notificaties' , 'description' => 'Stuur e-mailmeldingen over elk ingediend formulier'], 95 | 'mail_subject' => ['title' => 'Onderwerp' , 'description' => 'Standaard e-mail onderwerp overschrijven'], 96 | 'mail_recipients' => ['title' => 'Ontvangers' , 'description' => 'Geef de e-mailadressen op (één adres per regel)'], 97 | 'mail_bcc' => ['title' => 'BCC' , 'description' => 'Stuur een BBC naar de ontvangers (één adres per regel)'], 98 | 'mail_replyto' => ['title' => 'ReplyTo E-mail Veld' , 'description' => 'Formulierveld met het e-mailadres van de afzender dat moet worden gebruikt als "ReplyTo"'], 99 | 'mail_template' => ['title' => 'E-mail sjabloon' , 'description' => 'Gebruik een aangepast e-mail sjabloon. Specificeer sjabloon code zoals "martin.forms::mail.notification" (te vinden onder Instellingen, E-mail sjablonen). Laat leeg om de standaard te gebruiken.'], 100 | 'mail_uploads' => ['title' => 'Verstuur uploads' , 'description' => 'Verstuur uploads als bijlagen'], 101 | 'mail_resp_enabled' => ['title' => 'Stuur automatisch antwoord' , 'description' => 'Stuur een automatische e-mail naar de persoon die het formulier heeft ingediend'], 102 | 'mail_resp_field' => ['title' => 'E-mail Veld' , 'description' => 'Formulierveld met het e-mailadres van de ontvanger van het automatische antwoord'], 103 | 'mail_resp_from' => ['title' => 'Verzender adres' , 'description' => 'E-mailadres van de afzender van de auto-response e-mail (bv. noreply@yourcompany.com)'], 104 | 'mail_resp_subject' => ['title' => 'Onderwerp' , 'description' => 'Standaard e-mail onderwerp overschrijven'], 105 | 'reset_form' => ['title' => 'Reset Form' , 'description' => 'Formulier terugzetten na succesvol verzenden'], 106 | 'redirect' => ['title' => 'Redirect on Success' , 'description' => 'Doorverwijzen bij succes'], 107 | 'inline_errors' => ['title' => 'Inline fouten' , 'description' => 'Toon inline fouten. Dit heeft extra code nodig, zie de documentatie voor meer info info.', 'disabled' => 'Uitgeschakeld', 'display' => 'Toon fouten', 'variable' => 'JS variabele'], 108 | 'js_on_success' => ['title' => 'JS bij Succes' , 'description' => 'Voer aangepaste JavaScript code uit wanneer het formulier succesvol is verstuurd. Gebruik geen script tags.'], 109 | 'js_on_error' => ['title' => 'JS bij Fout' , 'description' => 'Voer aangepaste JavaScript code uit als het formulier niet valideert. Gebruik geen script tags.'], 110 | 'allowed_fields' => ['title' => 'Toegestane Velden' , 'description' => 'Geef aan welke velden moeten worden gefilterd en opgeslagen (voeg één veldnaam per regel toe)'], 111 | 'anonymize_ip' => ['title' => 'Anonimiseer IP' , 'description' => 'Sla het IP adres niet op', 'full' => 'Volledig', 'partial' => 'Gedeeltelijk', 'disabled' => 'Uitgeschakeld'], 112 | 'sanitize_data' => ['title' => 'Sanitize form data' , 'description' => 'Formuliergegevens zuiveren', 'disabled' => 'Uitgeschakeld', 'htmlspecialchars' => 'Gebruik htmlspecialchars'], 113 | 'recaptcha_enabled' => ['title' => 'Schakel reCAPTCHA in' , 'description' => 'Plaats de reCAPTCHA widget op uw formulierm'], 114 | 'recaptcha_theme' => ['title' => 'Thema' , 'description' => 'Het kleurenthema van de widget', 'light' => 'Licht' , 'dark' => 'Donker'], 115 | 'recaptcha_type' => ['title' => 'Type' , 'description' => 'Het type CAPTCHA dat moet worden gebruikt' , 'image' => 'Afbeelding' , 'audio' => 'Audio'], 116 | 'recaptcha_size' => [ 117 | 'title' => 'Grootte', 118 | 'description' => 'De grootte van de widget', 119 | 'normal' => 'Normaal', 120 | 'compact' => 'Compact', 121 | 'invisible' => 'Onzichtbaar', 122 | ], 123 | 'skip_database' => ['title' => 'Sla database over' , 'description' => 'Sla dit formulier niet op in de database. Handig als je gebeurtenissen wilt gebruiken met je aangepaste plugin.'], 124 | 'emails_date_format' => ['title' => 'Datumnotatie op e-mails' , 'description' => 'Aangepaste notatie instellen voor datums in e-mailonderwerpen.'], 125 | 'uploader_enable' => ['title' => 'Sta uploads toe' , 'description' => 'Bestanden uploaden inschakelen. U moet deze optie expliciet inschakelen als veiligheidsmaatregel.'], 126 | 'uploader_multi' => ['title' => 'Meerdere bestanden' , 'description' => 'Sta uploaden van meerdere bestanden toe'], 127 | 'uploader_pholder' => ['title' => 'Placeholder tekst' , 'description' => 'Formulering die wordt weergegeven wanneer geen bestand is geüpload', 'default' => 'Klik of sleep bestanden om te uploaden'], 128 | 'uploader_maxsize' => ['title' => 'Limiet bestandsgrootte' , 'description' => 'De maximale bestandsgrootte die kan worden geüpload in megabytes'], 129 | 'uploader_types' => ['title' => 'Toegestane bestandstypen', 'description' => 'Toegestane bestandsextensies of sterretje (*) voor alle types (één extensie per regel)'], 130 | 'uploader_remFile' => ['title' => 'Popup tekst verwijderen' , 'description' => 'Tekst die wordt weergegeven in de popup wanneer u een bestand verwijdert', 'default' => 'Weet je het zeker?'], 131 | ], 132 | ], 133 | 134 | 'settings' => [ 135 | 'tabs' => ['general' => 'Algemeen', 'recaptcha' => 'reCAPTCHA', 'gdpr' => 'AVG'], 136 | 'section_flash_messages' => 'Flash Berichten', 137 | 'global_messages_success' => ['label' => 'Globaal succesbericht', 'comment' => '((Deze instelling kan worden overschreven vanuit het component))', 'default' => 'Uw formulier is succesvol verzonden'], 138 | 'global_messages_errors' => ['label' => 'Globaal foutbericht' , 'comment' => '((Deze instelling kan worden overschreven vanuit het component))', 'default' => 'Er zijn fouten opgetreden bij uw inzending'], 139 | 'plugin_help' => 'U kunt de plugin documentatie vinden op de GitHub repo:', 140 | 'global_hide_button' => 'Verberg menu item', 141 | 'global_hide_button_desc' => 'Handig als je events wilt gebruiken met je aangepaste plugin.', 142 | 'section_recaptcha' => 'reCAPTCHA Instellingen', 143 | 'recaptcha_site_key' => 'Site key', 144 | 'recaptcha_secret_key' => 'Secret key', 145 | 'gdpr_help_title' => 'Informatie', 146 | 'gdpr_help_comment' => 'Voor AVG-wetgeving in Europa, u kunt records niet onbeperkt bewaren, u moet ze wissen na een bepaalde periode, afhankelijk van uw behoeften', 147 | 'gdpr_enable' => 'Schakel AVG in', 148 | 'gdpr_days' => 'Bewaar de gegevens maximaal X dagen', 149 | ], 150 | 151 | 'permissions' => [ 152 | 'tab' => 'Magic Forms', 153 | 'access_records' => 'Toegang tot opgeslagen formuliergegevens', 154 | 'access_exports' => 'Toegang tot export van opgeslagen gegevens', 155 | 'access_settings' => 'Toegang tot instellingen', 156 | 'gdpr_cleanup' => 'AVG opschoning uitvoeren', 157 | ], 158 | 159 | 'mails' => [ 160 | 'form_notification' => ['description' => 'Notificeer wanneer een formulier wordt verzonden'], 161 | 'form_autoresponse' => ['description' => 'Automatisch antwoord wanneer een formulier wordt verzonden'], 162 | ], 163 | 164 | 'validation' => [ 165 | 'recaptcha_error' => 'Kan reCAPTCHA-veld niet valideren', 166 | ], 167 | 168 | 'classes' => [ 169 | 'GDPR' => [ 170 | 'alert_gdpr_disabled' => 'AVG opties zijn uitgeschakeld', 171 | 'alert_invalid_gdpr' => 'Ongeldige AVG instellingswaarde voor dagen', 172 | ], 173 | ], 174 | 175 | ]; 176 | -------------------------------------------------------------------------------- /lang/pt-br/lang.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'name' => 'Magic Forms', 6 | 'description' => 'Crie formulários fácilmente com AJAX' 7 | ], 8 | 'menu' => [ 9 | 'label' => 'Formulários Mágico', 10 | 'records' => ['label' => 'Registros'], 11 | 'exports' => ['label' => 'Exportar'], 12 | 'settings' => 'Configurar parâmetros do plug-in', 13 | ], 14 | 'controllers' => [ 15 | 'records' => [ 16 | 'title' => 'Ver registos', 17 | 'view_title' => 'Detalhes do registro', 18 | 'error' => 'Registro não encontrado', 19 | 'deleted' => 'Registro excluído com sucesso', 20 | 'columns' => [ 21 | 'id' => 'ID do registro', 22 | 'group' => 'Grupo', 23 | 'ip' => 'Endereço IP', 24 | 'form_data' => 'Campos armazenados', 25 | 'files' => 'Arquivos anexados', 26 | 'created_at' => 'Criado', 27 | ], 28 | ], 29 | 'exports' => [ 30 | 'title' => 'Exportar registros', 31 | 'breadcrumb' => 'Exportar', 32 | 'filter_section' => '1. Filtrar registos', 33 | 'filter_type' => 'Exportar todos os registros', 34 | 'filter_groups' => 'Grupos', 35 | 'filter_date_after' => 'Data após', 36 | 'filter_date_before' => 'Data anterior', 37 | 'options_section' => '2. Opções extras', 38 | 'options_metadata' => 'Incluir metadados', 39 | 'options_metadata_com' => 'Exportar registros com metadados (ID de registro, grupo, IP, data criação)', 40 | 'options_deleted' => 'Incluir registros excluídos', 41 | ], 42 | ], 43 | 'components' => [ 44 | 'generic_form' => [ 45 | 'name' => 'Formulário AJAX Genérico', 46 | 'description' => 'Por padrão renderiza um formulário genérico; Substitua o componente HTML com seus campos personalizados.', 47 | ], 48 | 'upload_form' => [ 49 | 'name' => 'formulário upload AJAX [BETA]', 50 | 'description' => 'Mostra como implementar uploads de arquivos no formulário.', 51 | ], 52 | 'empty_form' => [ 53 | 'name' => 'Formulário AJAX vazio', 54 | 'description' => 'Crie um modelo vazio para seu formulário personalizado; Substituir o componente HTML.', 55 | ], 56 | 'shared' => [ 57 | 'csrf_error' => 'A sessão do formulário expirou! Atualize a página.', 58 | 'recaptcha_warn' => 'Aviso: reCAPTCHA não está configurado corretamente. Por favor, vá para Back-end> Configurações> CMS> Magic Forms e configure.', 59 | 'group_validation' => 'Validação de formulários', 60 | 'group_messages' => 'Mensagens Flash', 61 | 'group_mail' => 'Configurações de Notificações', 62 | 'group_mail_resp' => 'Configurações de Resposta Automática', 63 | 'group_settings' => 'Mais configurações', 64 | 'group_security' => 'Segurança', 65 | 'group_recaptcha' => 'Configurações de reCAPTCHA', 66 | 'group_uploader' => 'Configurações do Uploader', 67 | 'validation_req' => 'A propriedade é obrigatória', 68 | 'group' => ['title' => 'Grupo' , 'description' => 'Organize seus formulários com um nome de grupo personalizado. Esta opção é útil ao exportar dados.'], 69 | 'rules' => ['title' => 'Regras' , 'description' => 'Defina suas próprias regras usando a validação Laravel'], 70 | 'rules_messages' => ['title' => 'Mensagens de Regras' , 'description' => 'Use suas próprias mensagens de regras usando a validação do Laravel'], 71 | 'messages_success' => ['title' => 'Sucesso' , 'description' => 'Mensagem quando o formulário é enviado com sucesso', 'default '=>' Seu formulário foi enviado com sucesso'], 72 | 'messages_errors' => ['title' => 'Erros' , 'description' => 'Mensagem quando o formulário contém erros', 'default' => 'Ocorreu um erro no envio'], 73 | 'mail_enabled' => ['title' => 'Enviar Notificações' , 'description' => 'Enviar notificações por email em todos os formulários enviados'], 74 | 'mail_subject' => ['title' => 'Assunto' , 'description' => 'Substitui o assunto de email padrão'], 75 | 'mail_recipients' => ['title' => 'Destinatários' , 'description' => 'Especifique destinatários de email (adicione um endereço por linha)'], 76 | 'mail_uploads' => ['title' => 'Enviar Uploads' , 'description' => 'Enviar uploads como anexos'], 77 | 'mail_resp_enabled' => ['title' => 'Enviar Resposta Automática' , 'description' => 'Enviar um email de resposta automática para a pessoa que envia o formulário'], 78 | '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'], 79 | '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)'], 80 | 'mail_resp_subject' => ['title' => 'Assunto' , 'description' => 'Substitui assunto de e-mail padrão'], 81 | 'reset_form' => ['title' => 'Limpar Fomulário' , 'description' => 'Limpar o formulário após o envio com sucesso'], 82 | 'allowed_fields' => ['title' => 'Campos permitidos' , 'description' => 'Especifique quais campos devem ser filtrados e armazenados (adicione um nome de campo por linha)'], 83 | 'recaptcha_enabled' => ['title' => 'Habiltar reCAPTCHA' , 'description' => 'Inserir o widget reCAPTCHA no seu formulário'], 84 | 'recaptcha_theme' => ['title' => 'Tema' , 'description' => 'Cor do tema do widget', 'light' => 'Claro' , 'dark' => 'Escuro'], 85 | 'recaptcha_type' => ['title' => 'Tipo' , 'description' => 'O tipo de CAPTCHA para servir' , 'image' => 'Imagem' , 'audio' => 'Áudio'], 86 | 'recaptcha_size' => ['title' => 'Tamanho' , 'description' => 'O tamanho do widget' , 'normal' => 'Normal', 'compact' => 'Compacto'], 87 | 'uploader_enable' => ['title' => 'Permitir Uploads' , 'description' => 'Ativar o upload de arquivos. Você precisa ativar essa opção explicitamente como uma medida de segurança.'], 88 | 'uploader_multi' => ['title' => 'Multiplos Arquivos' , 'description' => 'Permitir multiplos uploads de arquivos'], 89 | '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'], 90 | 'uploader_maxsize' => ['title' => 'Limite do Tamanho do Arquivo' , 'description' => 'O tamanho máximo de arquivo que pode ser carregado em megabytes'], 91 | '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)'], 92 | ] 93 | ], 94 | 'settings' => [ 95 | 'section_flash_messages' => 'Mensagens Flash', 96 | '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'], 97 | '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'], 98 | 'section_recaptcha' => 'Configurações de reCAPTCHA', 99 | 'recaptcha_site_key' => 'Chave do site', 100 | 'recaptcha_secret_key' => 'Chave secreta', 101 | ], 102 | 'permissions' => [ 103 | 'tab' => 'Formulários Mágico', 104 | 'access_records' => 'Acessar dados de formulários armazenados', 105 | 'access_exports' => 'Acesso à exportação de dados armazenados', 106 | 'access_settings' => 'Acesso do módulo de configuração', 107 | ], 108 | 'mails' => [ 109 | 'form_notification' => ['description' => 'Notificar quando um formulário é enviado'], 110 | 'form_autoresponse' => ['description' => 'Resposta automática quando um formulário é enviado'], 111 | ], 112 | 'validation' => [ 113 | 'recaptcha_error' => 'Não é possível validar o campo reCAPTCHA' 114 | ], 115 | ]; 116 | ?> 117 | -------------------------------------------------------------------------------- /lang/ru/lang.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'name' => 'Magic Forms', 7 | 'description' => 'С легкостью создайте AJAX форму' 8 | ], 9 | 10 | 'menu' => [ 11 | 'label' => 'Magic Forms', 12 | 'records' => ['label' => 'Записи'], 13 | 'exports' => ['label' => 'Экспорт'], 14 | 'settings' => 'Настройки плагина', 15 | ], 16 | 17 | 'controllers' => [ 18 | 'records' => [ 19 | 'title' => 'Все заявки', 20 | 'view_title' => 'Запись', 21 | 'error' => 'Запись не найдена', 22 | 'deleted' => 'Запись успешно удалена', 23 | 'columns' => [ 24 | 'id' => 'ID записи', 25 | 'group' => 'Группа', 26 | 'ip' => 'IP адрес', 27 | 'form_data' => 'Поля формы', 28 | 'files' => 'Прикрепленные файлы', 29 | 'created_at' => 'Создано', 30 | ], 31 | 'buttons' => [ 32 | 'read' => 'Отметить как прочитано', 33 | 'unread' => 'Отметить как непрочитанное', 34 | 'gdpr_clean' => 'Очистить GDPR', 35 | ], 36 | 'alerts' => [ 37 | 'gdpr_confirm' => "Вы действительно хотите удалить эти старые записи?\nЭто действие не может быть отменено!", 38 | 'gdpr_success' => 'Очистка GDPR успешна', 39 | 'gdpr_perms' => 'У вас нет прав использования этой функции', 40 | ], 41 | ], 42 | 'exports' => [ 43 | 'title' => 'Экспорт записей', 44 | 'breadcrumb' => 'Экспорт', 45 | 'filter_section' => '1. Фильтровать записи', 46 | 'filter_type' => 'Экспорт всех записей', 47 | 'filter_groups' => 'Группы', 48 | 'filter_date_after' => 'Дата от', 49 | 'filter_date_before' => 'дата до', 50 | 'options_section' => '2. Экстра опции', 51 | 'options_metadata' => 'Вложить метаданные', 52 | 'options_metadata_com' => 'Экспорт записей с метаданными(ID, группа, дата создания)', 53 | 'options_deleted' => 'Вложить удаленные записи', 54 | 'options_delimiter' => 'Использовать альтернативный разделитель полей', 55 | 'options_delimiter_com' => 'Точка с запятой вместо запятой', 56 | 'options_utf' => 'Кодировать в UTF8', 57 | 'options_utf_com' => 'Ваш CSV файл будет закодирван в UTF-8', 58 | ], 59 | ], 60 | 61 | 'components' => [ 62 | 'generic_form' => [ 63 | 'name' => 'Стандартная AJAX форма', 64 | 'description' => 'Выводит стандартную форму; Перезапишите HTML компонент со своей формой.', 65 | ], 66 | 'upload_form' => [ 67 | 'name' => 'AJAX форма с файлами', 68 | 'description' => '[BETA] Форма с возможностью прикрепления файлов.', 69 | ], 70 | 'empty_form' => [ 71 | 'name' => 'Пустая AJAX форма', 72 | 'description' => 'Создает пустой плейсхолдер для вашей формы, который вы должны перезаписать своим HTML кодом.', 73 | ], 74 | 'shared' => [ 75 | 'csrf_error' => 'Сессия формы истекла! Перезагрузите страницу.', 76 | 'recaptcha_warn' => 'Внимание: reCAPTCHA настроена не правильно. Пожалуйста, перейдите в Настройки > CMS > Magic Forms и заполните все обязательные поля.', 77 | 'group_validation' => 'Валидация формы', 78 | 'group_messages' => 'Флэш-сообщения', 79 | 'group_mail' => 'Настройки уведомлений', 80 | 'group_mail_resp' => 'Настройки авто-ответа', 81 | 'group_settings' => 'Остальные настройки', 82 | 'group_security' => 'Безопасность', 83 | 'group_recaptcha' => 'Настройки reCAPTCHA', 84 | 'group_advanced' => 'Продвинутые настройки', 85 | 'group_uploader' => 'Настройки файлового загрузчика', 86 | 'validation_req' => 'Этот атрибут обязателен', 87 | 'group' => ['title' => 'Группа' , 'description' => 'Организуйте свои формы по группам. Эта опция полезна для экспорта записей.'], 88 | 'rules' => ['title' => 'Правила' , 'description' => 'Используйте валидацию Laravel для установки своих правил обработки полей.'], 89 | 'rules_messages' => ['title' => 'Сообщения от правил', 'description' => 'Используйте свои собственные сообщения от кастомных валидаций.'], 90 | 'custom_attributes' => ['title' => 'Кастомные атрибуты' , 'description' => 'Используйте кастомные атрибуты в своих правилах валидаций.'], 91 | 'messages_success' => ['title' => 'Успешно' , 'description' => 'Сообщение об успешной отправке формы', 'default' => 'Ваша форма была успешно отправлена!'], 92 | 'messages_errors' => ['title' => 'Ошибки' , 'description' => 'Сообщение с ошибками формы' , 'default' => 'В вашей заявке содержатся ошибки.'], 93 | 'messages_partial' => ['title' => 'Кастомный фрагмент' , 'description' => 'Переопределить стандартные уведомления кастомным фрагментом из вашей фронтенд темы.'], 94 | 'mail_enabled' => ['title' => 'Отправка уведомлений','description' => 'Отправлять увдеомления по email после каждого успешного заполенния формы.'], 95 | 'mail_subject' => ['title' => 'Тема письма' , 'description' => 'Переопределить тему письма по умолчанию'], 96 | 'mail_recipients' => ['title' => 'Получатели' , 'description' => 'Укажите получателей электронной почты (По одному адресу в строке)'], 97 | 'mail_bcc' => ['title' => 'BCC' , 'description' => 'Отправить копию получателям электронной почты (По одному адресу в строке)'], 98 | 'mail_replyto' => ['title' => 'ReplyTo поле email' , 'description' => 'Поле формы, содержащее адрес электронной почты отправителя, который будет использоваться как "ReplyTo"'], 99 | 'mail_template' => ['title' => 'Шаблон письма' , 'description' => 'Используйте собственный шаблон письма. Укажите код шаблона, например, "martin.forms::mail.notification" (находится в разделе Настройки -> Почтовые шаблоны). Оставьте пустым, чтобы использовать по умолчанию.'], 100 | 'mail_uploads' => ['title' => 'Прикреплять файлы' , 'description' => 'Прикреплять файлы к письму'], 101 | 'mail_resp_enabled' => ['title' => 'Отправить авто-ответ','description' => 'Отправить автоответчик на email отправителя.'], 102 | 'mail_resp_field' => ['title' => 'Email поле' , 'description' => 'Имя поля с email отправителя'], 103 | 'mail_resp_from' => ['title' => 'Email адрес отправителя', 'description' => 'Адрес электронной почты отправителя электронной почты с автоматическим ответом (например, noreply@yourcompany.com)'], 104 | 'mail_resp_subject' => ['title' => 'Тема пиьсма' , 'description' => 'Переопределить тему письма по умолчанию'], 105 | 'reset_form' => ['title' => 'Сбросить форму' , 'description' => 'Сбросить форму после успешной отправки'], 106 | 'redirect' => ['title' => 'Редирект' , 'description' => 'Редирект на URL после успешной отправки формы.'], 107 | 'inline_errors' => ['title' => 'Встроенные ошибки' , 'description' => 'Отображать встроенные ошибки. Это требует дополнительного кода, проверьте документацию для получения дополнительной информации.', 'disabled' => 'Отключено', 'display' => 'Отображать ошибки', 'variable' => 'JS переменная'], 108 | 'js_on_success' => ['title' => '"Успешный" JS' , 'description' => 'Выполнияет скрипт после успешной отправки формы. Не используйте