Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release v2.1.0 #4

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
26 changes: 21 additions & 5 deletions classes/BackendHelpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

namespace Martin\Forms\Classes;

use Backend;
use BackendAuth;
use Backend\Facades\Backend;
use Backend\Facades\BackendAuth;

class BackendHelpers
{
Expand Down Expand Up @@ -36,6 +36,22 @@ public static function isTranslatePlugin(): bool
return class_exists('\RainLab\Translate\Classes\Translator') && class_exists('\RainLab\Translate\Models\Message');
}

/**
* Recursively apply a callback function to every items in give array
*
* @param $callback
* @param $array
* @return array|false[]
*/
public static function array_map_recursive($callback, $array)
{
$func = function ($item) use (&$func, &$callback) {
return is_array($item) ? array_map($func, $item) : call_user_func($callback, $item);
};

return array_map($func, $array);
}

/**
* Render an array|object as HTML list (UL > LI)
*
Expand Down Expand Up @@ -76,9 +92,9 @@ public static function anonymizeIPv4(string $address): string
/**
* Extract string from curly braces
*
* @param string $pattern Pattern to replace
* @param string $replacement Replacement string
* @param string $subject Strings to replace
* @param string $pattern Pattern to replace
* @param string|null $replacement Replacement string
* @param string $subject Strings to replace
*
* @return string
*/
Expand Down
157 changes: 28 additions & 129 deletions classes/MagicForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,20 @@
abstract class MagicForm extends ComponentBase
{

use \Martin\Forms\Classes\ReCaptcha;
use \Martin\Forms\Classes\SharedProperties;
use \Martin\Forms\Classes\Traits\PostData;
use \Martin\Forms\Classes\Traits\ReCaptcha;
use \Martin\Forms\Classes\Traits\RequestValidation;
use \Martin\Forms\Classes\Traits\SendMails;
use \Martin\Forms\Classes\Traits\SharedProperties;

private $flash_partial;
private $validator;

public function init()
{
// FLASH PARTIAL
$this->flash_partial = $this->property('messages_partial', '@flash.htm');
}

public function onRun()
{
Expand Down Expand Up @@ -58,17 +70,8 @@ public function settings()

public function onFormSubmit()
{
// FLASH PARTIAL
$flash_partial = $this->property('messages_partial', '@flash.htm');

// CSRF CHECK
if (Config::get('cms.enableCsrfProtection') && (Session::token() != post('_token'))) {
throw new AjaxException(['#' . $this->alias . '_forms_flash' => $this->renderPartial($flash_partial, [
'status' => 'error',
'type' => 'danger',
'content' => Lang::get('martin.forms::lang.components.shared.csrf_error'),
])]);
}
$this->checkCSRF();

// LOAD TRANSLATOR PLUGIN
if (BackendHelpers::isTranslatePlugin()) {
Expand All @@ -78,25 +81,8 @@ public function onFormSubmit()
\RainLab\Translate\Models\Message::setContext($locale);
}

// FILTER ALLOWED FIELDS
$allow = $this->property('allowed_fields');
if (is_array($allow) && !empty($allow)) {
foreach ($allow as $field) {
$post[$field] = post($field);
}
if ($this->isReCaptchaEnabled()) {
$post['g-recaptcha-response'] = post('g-recaptcha-response');
}
} else {
$post = post();
}

// SANITIZE FORM DATA
if ($this->property('sanitize_data') == 'htmlspecialchars') {
$post = $this->array_map_recursive(function ($value) {
return htmlspecialchars($value, ENT_QUOTES);
}, $post);
}
/** PREPARE FORM DATA */
$post = $this->preparePost();

// VALIDATION PARAMETERS
$rules = (array)$this->property('rules');
Expand All @@ -116,73 +102,24 @@ public function onFormSubmit()
}

// DO FORM VALIDATION
$validator = Validator::make($post, $rules, $msgs, $custom_attributes);
$this->validator = Validator::make($post, $rules, $msgs, $custom_attributes);

// NICE reCAPTCHA FIELD NAME
if ($this->isReCaptchaEnabled()) {
$fields_names = ['g-recaptcha-response' => 'reCAPTCHA'];
$validator->setAttributeNames(array_merge($fields_names, $custom_attributes));
}

// VALIDATE ALL + CAPTCHA EXISTS
if ($validator->fails()) {

// GET DEFAULT ERROR MESSAGE
$message = $this->property('messages_errors');

// LOOK FOR TRANSLATION
if (BackendHelpers::isTranslatePlugin()) {
$message = \RainLab\Translate\Models\Message::trans($message);
}

// THROW ERRORS
if ($this->property('inline_errors') == 'display') {
throw new ValidationException($validator);
} else {
throw new AjaxException($this->exceptionResponse($validator, [
'status' => 'error',
'type' => 'danger',
'title' => $message,
'list' => $validator->messages()->all(),
'errors' => json_encode($validator->messages()->messages()),
'jscript' => $this->property('js_on_error'),
]));
}
$this->validator->setAttributeNames(array_merge($fields_names, $custom_attributes));
}

// IF FIRST VALIDATION IS OK, VALIDATE CAPTCHA vs GOOGLE
// (this prevents to resolve captcha after every form error)
if ($this->isReCaptchaEnabled()) {
// CHECK FOR VALID FORM AND THROW ERROR IF NEEDED
$this->validateForm();

// PREPARE RECAPTCHA VALIDATION
$rules = ['g-recaptcha-response' => 'recaptcha'];
$err_msg = ['g-recaptcha-response.recaptcha' => Lang::get('martin.forms::lang.validation.recaptcha_error')];

// DO SECOND VALIDATION
$validator = Validator::make($post, $rules, $err_msg);

// VALIDATE ALL + CAPTCHA EXISTS
if ($validator->fails()) {

// THROW ERRORS
if ($this->property('inline_errors') == 'display') {
throw new ValidationException($validator);
} else {
throw new AjaxException($this->exceptionResponse($validator, [
'status' => 'error',
'type' => 'danger',
'content' => Lang::get('martin.forms::lang.validation.recaptcha_error'),
'errors' => json_encode($validator->messages()->messages()),
'jscript' => $this->property('js_on_error'),
]));
}
}
}
// IF FIRST VALIDATION IS OK, VALIDATE CAPTCHA vs GOOGLE (prevents to resolve captcha after every form error)
$this->validateReCaptcha($post);

// REMOVE EXTRA FIELDS FROM STORED DATA
unset($post['_token'], $post['g-recaptcha-response'], $post['_session_key'], $post['files']);

// FIRE BEFORE SAVE EVENT
/** FIRE BEFORE SAVE EVENT */
Event::fire('martin.forms.beforeSaveRecord', [&$post, $this]);

if (count($custom_attributes)) {
Expand All @@ -208,23 +145,10 @@ public function onFormSubmit()
$record->save(null, post('_session_key'));
}

// SEND NOTIFICATION EMAIL
if ($this->property('mail_enabled')) {
$notification = App::makeWith(Notification::class, [
$this->getProperties(), $post, $record, $record->files
]);
$notification->send();
}

// SEND AUTORESPONSE EMAIL
if ($this->property('mail_resp_enabled')) {
$autoresponse = App::makeWith(AutoResponse::class, [
$this->getProperties(), $post, $record
]);
$autoresponse->send();
}
/** SEND NOTIFICATION & AUTORESPONSE EMAILS */
$this->sendEmails($post, $record);

// FIRE AFTER SAVE EVENT
/** FIRE AFTER SAVE EVENT */
Event::fire('martin.forms.afterSaveRecord', [&$post, $this, $record]);

// CHECK FOR REDIRECT
Expand All @@ -241,30 +165,14 @@ public function onFormSubmit()
}

// DISPLAY SUCCESS MESSAGE
return ['#' . $this->alias . '_forms_flash' => $this->renderPartial($flash_partial, [
return ['#' . $this->alias . '_forms_flash' => $this->renderPartial($this->flash_partial, [
'status' => 'success',
'type' => 'success',
'content' => $message,
'jscript' => $this->prepareJavaScript(),
])];
}

private function exceptionResponse($validator, $params)
{
// FLASH PARTIAL
$flash_partial = $this->property('messages_partial', '@flash.htm');

// EXCEPTION RESPONSE
$response = ['#' . $this->alias . '_forms_flash' => $this->renderPartial($flash_partial, $params)];

// INCLUDE ERROR FIELDS IF REQUIRED
if ($this->property('inline_errors') != 'disabled') {
$response['error_fields'] = $validator->messages();
}

return $response;
}

private function prepareJavaScript()
{
$code = false;
Expand Down Expand Up @@ -303,15 +211,6 @@ private function getIP()
return $ip;
}

private function array_map_recursive($callback, $array)
{
$func = function ($item) use (&$func, &$callback) {
return is_array($item) ? array_map($func, $item) : call_user_func($callback, $item);
};

return array_map($func, $array);
}

private function attachFiles(Record $record)
{
$files = post('files', null);
Expand Down
3 changes: 1 addition & 2 deletions classes/ReCaptchaValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

namespace Martin\Forms\Classes;

use Request;
use Martin\Forms\Models\Settings;
use Illuminate\Support\Facades\Request;

class ReCaptchaValidator
{

public function validateReCaptcha($attribute, $value, $parameters)
{
$secret_key = Settings::get('recaptcha_secret_key');
Expand Down
51 changes: 51 additions & 0 deletions classes/Traits/PostData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace Martin\Forms\Classes\Traits;

use Martin\Forms\Classes\BackendHelpers;

trait PostData
{
/**
* Apply required transformations to form data
*
* @return array
*/
private function preparePost(): array
{
$allowed_fields = $this->property('allowed_fields');

if (empty($allowed_fields)) {
return input();
}

$post = [];

foreach ($allowed_fields as $field) {
$post[$field] = input($field);
}

if ($this->isReCaptchaEnabled()) {
$post['g-recaptcha-response'] = input('g-recaptcha-response');
}

if ($this->property('sanitize_data') == 'htmlspecialchars') {
$post = $this->sanitize($post);
}

return $post;
}

/**
* Sanitize form data using PHP "htmlspecialchars" function
*
* @param array $post
* @return array
*/
private function sanitize(array $post): array
{
return BackendHelpers::array_map_recursive(function ($value) {
return htmlspecialchars($value, ENT_QUOTES);
}, $post);
}
}
2 changes: 1 addition & 1 deletion classes/ReCaptcha.php → classes/Traits/ReCaptcha.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace Martin\Forms\Classes;
namespace Martin\Forms\Classes\Traits;

use Martin\Forms\Classes\BackendHelpers;
use Martin\Forms\Models\Settings;
Expand Down
Loading