app = $app; $this->request = $app->request; } /** * Find the form using it's ID. * * @param $formId * * @return null|\stdClass */ public function findForm($formId) { return wpFluent()->table('fluentform_forms')->find($formId); } /** * Handle form submition * * @return \Exception */ public function onSubmit() { // Parse the url encoded data from the request object. parse_str($this->app->request->get('data'), $data); // Merge it back again to the request object. $this->app->request->merge(['data' => $data]); $formId = intval($this->app->request->get('form_id')); $form = $this->findForm($formId); // Parse the form and get the flat inputs with validations. $this->parser = (new FormParser($form)); $fields = $this->parser->getInputs(['rules']); // Sanitize the data properly. $this->formData = fluentFormSanitizer($data, null, $fields); // Now validate the data using the previous validations. $this->validate($form, $fields); // Prepare the data to be inserted to the DB. $insertData = $this->prepareInsertData($formId); $insertId = wpFluent()->table('fluentform_submissions')->insert($insertData); try { $this->app->doAction('fluentform_submission_inserted', $insertId, $this->formData, $form); } catch (\Exception $e) { if (defined('WP_DEBUG') && WP_DEBUG) { return $e; } } $this->sendResponse($insertId, $form); } /** * Prepare response and do actions/filters * and send the response to the client. * * @param int $insertId * @param \StdClass $form * * @return void */ private function sendResponse($insertId, $form) { $formSettings = wpFluent()->table('fluentform_form_meta') ->where('form_id', $form->id) ->where('meta_key', 'formSettings') ->first(); $formSettings = json_decode($formSettings->value, true); $confirmation = $formSettings['confirmation']; $message = MessageShortCodeParser::parseMessageShortCode( array(array('message' => $confirmation['messageToShow'])), $insertId, $this->formData, $form, false ); if ($confirmation['redirectTo'] == 'samePage') { $returnData = array( 'message' => $message[0]['message'], 'formBehavior' => $confirmation['samePageFormBehavior'], ); } else { $redirectUrl = $confirmation['customUrl']; if ($confirmation['redirectTo'] == 'customPage') { $redirectUrl = get_permalink($confirmation['customPage']); } $returnData = array( 'message' => $message[0]['message'], 'redirectUrl' => $redirectUrl ); } $returnData = $this->app->applyFilters( 'fluentform_submission_confirmation', $returnData, $form, $confirmation ); wp_send_json_success(array( 'insert_id' => $insertId, 'result' => $returnData ), 200); } /** * Validate form data. * * @param $form * @param $fields * * @return bool */ private function validate($form, &$fields) { $this->validateRestrictions($form, $fields); $this->validateNonce($form->id); $this->validateReCaptcha(); $validations = $this->parser->getValidations($this->formData, $fields); // Fire an event so that one can hook into it to work with the rules & messages. $validations = $this->app->applyFilters('fluentform_validations', $validations, $form, $this->parser); $validator = \FluentValidator\Validator::make($this->formData, $validations[0], $validations[1]); if ($validator->validate()->fails()) { $errors = []; foreach ($validator->errors() as $attribute => $rules) { $position = strpos($attribute, ']'); if ($position) { $attribute = substr($attribute, 0, strpos($attribute, ']') + 1); } $errors[$attribute] = $rules; } // Fire an event so that one can hook into it to work with the errors. $errors = $this->app->applyFilters('fluentform_validation_error', $errors, $form); wp_send_json(['errors' => $errors], 423); } return true; } /** * Validate nonce. * * @param $formId */ protected function validateNonce($formId) { $shouldVerifyNonce = $this->app->applyFilters('fluentform_nonce_verify', true, $formId); if ($shouldVerifyNonce) { $nonce = ArrayHelper::get($this->formData, '_fluentform_'.$formId.'_fluentformnonce'); if (! wp_verify_nonce($nonce, 'fluentform-submit-form')) { $errors = $this->app->applyFilters('fluentForm_nonce_error', [ '_fluentformnonce' => [ __('Nonce verification failed, please try again.', 'fluentform') ] ]); wp_send_json(['errors' => $errors], 423); } } } /** * Validate reCaptcha. * * @param $formId */ private function validateReCaptcha() { if ($this->parser->hasElement('recaptcha')) { $isValid = ReCaptcha::validate(ArrayHelper::get($this->formData, 'g-recaptcha-response')); if (! $isValid) { wp_send_json([ 'errors' => [ 'g-recaptcha-response' => [ __('reCaptcha verification failed, please try again.', 'fluentform') ] ] ], 423); } } } /** * Validate form data based on the form restrictions settings. * * @param \stdClass $form * @param $fields */ private function validateRestrictions($form, &$fields) { $formSettings = wpFluent()->table('fluentform_form_meta')->where('form_id', $form->id) ->where('meta_key', 'formSettings') ->first(); $form->settings = $formSettings ? json_decode($formSettings->value, true) : []; $isAllowed = [ 'status' => true, 'message' => '' ]; // This will check the following restriction settings. // 1. limitNumberOfEntries // 2. scheduleForm // 3. requireLogin $isAllowed = apply_filters('fluentform_is_form_renderable', $isAllowed, $form); if (! $isAllowed['status']) { wp_send_json([ 'errors' => [ 'restricted' => [ __($isAllowed['message'], 'fluentform') ] ] ], 423); } // Since we are here, we should now handle if the form should be allowed to submit empty. $restrictions = ArrayHelper::get($form->settings, 'restrictions.denyEmptySubmission', []); $this->handleDenyEmptySubmission($restrictions, $fields); } /** * Handle response when empty form submission is not allowed. * * @param array $settings * @param $fields */ private function handleDenyEmptySubmission($settings = [], &$fields) { // Determine whether empty form submission is allowed or not. if (ArrayHelper::get($settings, 'enabled')) { // confirm this form has no required fields. if (! $this->parser->hasRequiredFields($fields)) { // Filter out the form data which doesn't have values. $filteredFormData = array_filter( // Filter out the other meta fields that aren't actual inputs. array_intersect_key($this->formData, $fields) ); if (! count($filteredFormData)) { wp_send_json([ 'errors' => [ 'restricted' => [ __(ArrayHelper::get($settings, 'message'), 'fluentform') ] ] ], 423); } } } } /** * Prepare the data to be inserted to the database. * * @param $formId * * @return array */ public function prepareInsertData($formId, $formData = false) { if(!$formData) { $formData = $this->formData; } $previousItem = wpFluent()->table('fluentform_submissions') ->where('form_id', $formId) ->orderBy('id', 'DESC') ->first(); $serialNumber = 1; if ($previousItem) { $serialNumber = $previousItem->serial_number + 1; } $browser = new Browser; $formData = apply_filters('fluentform_insert_response_data', $formData, $formId); return [ 'form_id' => $formId, 'serial_number' => $serialNumber, 'response' => json_encode($formData), 'source_url' => site_url(ArrayHelper::get($formData, '_wp_http_referer')), 'user_id' => get_current_user_id(), 'browser' => $browser->getBrowser(), 'device' => $browser->getPlatform(), 'ip' => $this->app->request->getIp(), 'created_at' => date("Y-m-d H:i:s"), 'updated_at' => date("Y-m-d H:i:s") ]; } /** * Delegate the validation rules & messages to the * ones that the validation library recognizes. * * @param $rules * @param $messages * * @return array */ protected function delegateValidations($rules, $messages, $search = [], $replace = []) { $search = $search ?: ['max_file_size', 'allowed_file_types']; $replace = $replace ?: ['max', 'mimes']; foreach ($rules as &$rule) { $rule = str_replace($search, $replace, $rule); } foreach ($messages as $key => $message) { $newKey = str_replace($search, $replace, $key); $messages[$newKey] = $message; unset($messages[$key]); } return [$rules, $messages]; } }