File "init.php"
Full path: /home/dora/public_html/wp-content/themes/bricks/includes/integrations/form/init.php
File size: 13.17 KB
MIME-type: --
Charset: utf-8
<?php
namespace Bricks\Integrations\Form;
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
class Init {
protected $uploaded_files;
protected $form_settings;
protected $form_fields;
protected $results;
public function __construct() {
add_action( 'wp_ajax_bricks_form_submit', [ $this, 'form_submit' ] );
add_action( 'wp_ajax_nopriv_bricks_form_submit', [ $this, 'form_submit' ] );
}
/**
* Element Form: Submit
*
* @since 1.0
*/
public function form_submit() {
$this->form_settings = \Bricks\Helpers::get_element_settings( $_POST['postId'], $_POST['formId'] );
if ( ! isset( $this->form_settings['actions'] ) || empty( $this->form_settings['actions'] ) ) {
wp_send_json_error(
[
'code' => 400,
'action' => '',
'type' => 'error',
'message' => esc_html__( 'No action has been set for this form.', 'bricks' ),
]
);
}
// Google ReCAPTCHA v3 (invisible)
if ( isset( $this->form_settings['enableRecaptcha'] ) ) {
$recaptcha_verified = false;
$recaptcha_secret_key = \Bricks\Database::get_setting( 'apiSecretKeyGoogleRecaptcha', false );
if ( ! empty( $_POST['recaptchaToken'] ) && $recaptcha_secret_key ) {
// Verify token @see https://developers.google.com/recaptcha/docs/verify
$url = 'https://www.google.com/recaptcha/api/siteverify?secret=' . $recaptcha_secret_key . '&response=' . $_POST['recaptchaToken'];
// Use \Bricks\Helpers::remote_get() directly (@since 1.8.1)
$recaptcha_res = \Bricks\Helpers::remote_get( $url );
if ( ! is_wp_error( $recaptcha_res ) && wp_remote_retrieve_response_code( $recaptcha_res ) === 200 ) {
$recaptcha = json_decode( wp_remote_retrieve_body( $recaptcha_res ) );
/*
* Google reCAPTCHA v3 returns a score
*
* 1.0 is very likely a good interaction. 0.0 is very likely a bot.
*
* https://academy.bricksbuilder.io/article/form-element/#spam
*/
$score = apply_filters( 'bricks/form/recaptcha_score_threshold', 0.5 );
// Action was set on the grecaptcha.execute (@see frontend.js)
if ( $recaptcha->success && $recaptcha->score >= $score && $recaptcha->action == 'bricks_form_submit' ) {
$recaptcha_verified = true;
}
}
}
if ( ! $recaptcha_verified ) {
$error = esc_html__( 'Invalid Google reCaptcha.', 'bricks' );
if ( ! empty( $recaptcha->{'error-codes'} ) ) {
$error .= ' [' . implode( ',', $recaptcha->{'error-codes'} ) . ']';
}
wp_send_json_error(
[
'code' => 400,
'action' => '',
'type' => 'error',
'message' => $error,
]
);
}
}
$this->form_fields = stripslashes_deep( $_POST );
$this->uploaded_files = $this->handle_files();
// STEP: Validate form submission via filter (@since 1.7.1)
$validation_errors = [];
$validation_errors = apply_filters( 'bricks/form/validate', $validation_errors, $this );
// STEP: Validate required fields (@since 1.7.1)
$validation_errors = $this->validate_required_fields( $validation_errors );
// STEP: Validate submitted form (@since 1.7.1)
if ( is_array( $validation_errors ) && count( $validation_errors ) ) {
// Set validation error messages
$this->set_error_messages( $validation_errors );
// Halts execution if an action reported an error (@since 1.7.1 to run validator before running the form action)
$this->maybe_stop_processing();
}
// STEP: Run selected form submit 'actions'
$available_actions = self::get_available_actions();
foreach ( $this->form_settings['actions'] as $form_action ) {
if ( ! array_key_exists( $form_action, $available_actions ) ) {
continue;
}
$action_class = 'Bricks\Integrations\Form\Actions\\' . str_replace( ' ', '_', ucwords( str_replace( '-', ' ', $form_action ) ) );
$action = new $action_class( $form_action );
if ( ! method_exists( $action_class, 'run' ) ) {
continue;
}
$action->run( $this );
// Halts execution if an action reported an error
$this->maybe_stop_processing();
}
// All fine, success
$this->finish();
}
/**
* If there are any errors, stop execution
*
* @return void
*/
private function maybe_stop_processing() {
$errors = ! empty( $this->results['error'] ) && is_array( $this->results['error'] ) ? $this->results['error'] : [];
// type 'danger' used before 1.7.1
if ( ! count( $errors ) && ! empty( $this->results['danger'] ) && is_array( $this->results['danger'] ) ) {
$errors = $this->results['danger'];
}
if ( ! count( $errors ) ) {
return;
}
// Get last error
$error = array_pop( $errors );
// Remove uploaded files, if exist
$this->remove_files();
// Leave
wp_send_json_error( $error );
}
private function finish() {
$form_settings = $this->form_settings;
// Remove uploaded files, if exist
$this->remove_files();
// Basic response
$response = [
'type' => 'success',
'message' => isset( $form_settings['successMessage'] ) ? $this->render_data( $form_settings['successMessage'] ) : esc_html__( 'Success', 'bricks' )
];
if ( empty( $this->results ) ) {
wp_send_json_success( $response );
}
// Check for redirects
if ( ! empty( $this->results['redirect'] ) ) {
$redirect = array_pop( $this->results['redirect'] );
$post_id = ! empty( $_POST['postId'] ) ? $_POST['postId'] : get_the_ID();
$response['redirectTo'] = ! empty( $redirect['redirectTo'] ) ? bricks_render_dynamic_data( $redirect['redirectTo'], $post_id ) : '';
$response['redirectTimeout'] = isset( $redirect['redirectTimeout'] ) ? $redirect['redirectTimeout'] : 0;
}
// Check for 'info' messages (e.g. Mailchimp pending message)
if ( ! empty( $this->results['info'] ) ) {
foreach ( $this->results['info'] as $info ) {
if ( ! empty( $info['message'] ) ) {
$response['info'][] = $info['message'];
}
}
}
// Check for 'success' messages (e.g. custom bricks/form/validate) (@since 1.7.1)
if ( ! empty( $this->results['success'] ) ) {
foreach ( $this->results['success'] as $success ) {
if ( ! empty( $success['message'] ) ) {
$response['message'] = $success['message'];
}
}
}
// NOTE: Undocumented
$response = apply_filters( 'bricks/form/response', $response, $this );
// Evaluate results
wp_send_json_success( $response );
}
/**
* Set action result
*
* type: success OR danger
*
* @param array $result
* @return void
*/
public function set_result( $result ) {
$type = isset( $result['type'] ) ? $result['type'] : 'success';
$this->results[ $type ][] = $result;
}
/**
* Getters
*/
public function get_settings() {
return $this->form_settings;
}
public function get_fields() {
return $this->form_fields;
}
public function get_uploaded_files() {
return $this->uploaded_files;
}
public function get_results() {
return $this->results;
}
/**
* Handle with any files uploaded with form
*
* @param string $action
* @return void
*/
public function handle_files() {
if ( empty( $_FILES ) ) {
return [];
}
// https://developer.wordpress.org/reference/functions/wp_handle_upload/
$overrides = [ 'action' => 'bricks_form_submit' ];
$uploaded_files = [];
// Each form may have more than one input file type, each may have multiple files
foreach ( $_FILES as $input_name => $files ) {
if ( empty( $files['name'] ) ) {
continue;
}
foreach ( $files['name'] as $key => $value ) {
if ( empty( $files['name'][ $key ] ) || $files['error'][ $key ] !== UPLOAD_ERR_OK ) {
continue;
}
$file = [
'name' => $files['name'][ $key ],
'type' => $files['type'][ $key ],
'tmp_name' => $files['tmp_name'][ $key ],
'error' => $files['error'][ $key ],
'size' => $files['size'][ $key ]
];
$uploaded = wp_handle_upload( $file, $overrides );
// Upload success (uploaded to 'wp-content/uploads' folder)
if ( $uploaded && ! isset( $uploaded['error'] ) ) {
$uploaded_files[ $input_name ][] = $uploaded;
}
}
}
return $uploaded_files;
}
/**
* Eventually remove uploaded files
*
* @return void
*/
public function remove_files() {
if ( empty( $this->uploaded_files ) ) {
return;
}
// Remove uploaded files
foreach ( $this->uploaded_files as $input_name => $files ) {
foreach ( $files as $file ) {
@unlink( $file['file'] );
}
}
}
/**
* Replace any {{field_id}} by the submitted form field content and after renders dynamic data
*
* @param string $content
* @return void
*/
public function render_data( $content ) {
// \w: Matches any word character (alphanumeric & underscore).
// Only matches low-ascii characters (no accented or non-roman characters).
// Equivalent to [A-Za-z0-9_]
// https://regexr.com/
preg_match_all( '/{{(\w+)}}/', $content, $matches );
if ( ! empty( $matches[0] ) ) {
foreach ( $matches[1] as $key => $field_id ) {
// Format: '{{zjkcdw}}' // Dynamic email data format
$tag = $matches[0][ $key ];
$value = $this->get_field_value( $field_id );
$value = ! empty( $value ) && is_array( $value ) ? implode( ', ', $value ) : $value;
$content = str_replace( $tag, $value, $content );
}
}
$fields = $this->get_fields();
$post_id = isset( $fields['postId'] ) ? $fields['postId'] : 0;
// Render dynamic data
$content = bricks_render_dynamic_data( $content, $post_id );
return $content;
}
/**
* Get value of individual form field by field ID
*
* @param string $field_id
* @return void
*/
public function get_field_value( $field_id = '' ) {
$form_fields = $this->get_fields();
// NOTE: Undocumented {{referrer_url}}
if ( $field_id === 'referrer_url' && isset( $_POST['referrer'] ) ) {
return esc_url( $_POST['referrer'] );
}
if ( empty( $field_id ) || ! array_key_exists( "form-field-{$field_id}", $form_fields ) ) {
return '';
}
return $form_fields[ "form-field-{$field_id}" ];
}
/**
* Available actions after form submission
*
* @return void
*/
public static function get_available_actions() {
return [
'custom' => esc_html__( 'Custom', 'bricks' ),
'email' => esc_html__( 'Email', 'bricks' ),
'redirect' => esc_html__( 'Redirect', 'bricks' ),
'mailchimp' => 'Mailchimp',
'sendgrid' => 'SendGrid',
'login' => esc_html__( 'User Login', 'bricks' ),
'registration' => esc_html__( 'User Registration', 'bricks' ),
];
}
/**
* Set form submit error messages
*
* @param array $error_messages
*
* @since 1.7.1
*/
public function set_error_messages( $error_messages ) {
if ( empty( $error_messages ) ) {
return;
}
if ( is_string( $error_messages ) ) {
$error_messages = [ $error_messages ];
}
// One error: Return error message as string
if ( count( $error_messages ) === 1 ) {
$this->set_result(
[
'type' => 'error',
'message' => $error_messages,
]
);
return;
}
// More than one error: Return error messages as unordered list
$message = '<ul>';
// Combine $error_messages into a single string
foreach ( $error_messages as $error_message ) {
$message .= "<li>{$error_message}</li>";
}
$message .= '</ul>';
$this->set_result(
[
'type' => 'error',
'message' => $message,
]
);
}
/**
* Validate required fields
*
* @param array|string $custom_validation_errors Custom validation errors adding via filter 'bricks_form_validation_errors'.
*
* @return array
*
* @since 1.7.1
*/
public function validate_required_fields( $custom_validation_errors = [] ) {
$submitted_fields = $this->get_fields();
$uploaded_files = $this->get_uploaded_files();
$form_settings = $this->get_settings();
$form_settings_fields = ! empty( $form_settings['fields'] ) ? $form_settings['fields'] : [];
$errors = [];
foreach ( $form_settings_fields as $form_settings_field ) {
// Skip if field is not required
if ( empty( $form_settings_field['required'] ) ) {
continue;
}
$error = false;
// File field: file
if ( $form_settings_field['type'] === 'file' ) {
if ( empty( $uploaded_files[ "form-field-{$form_settings_field['id']}" ] ) ) {
$error = true;
}
}
// All other field types
else {
if (
! isset( $submitted_fields[ "form-field-{$form_settings_field['id']}" ] ) ||
$submitted_fields[ "form-field-{$form_settings_field['id']}" ] === ''
) {
$error = true;
}
}
if ( $error ) {
// Field is required & empty: Add error message
$field_label = ! empty( $form_settings_field['label'] ) ? $form_settings_field['label'] : $form_settings_field['type'];
$errors[] = esc_html__( 'Required', 'bricks' ) . ": $field_label";
}
}
// Custom validation error is a string: Convert to array
if ( $custom_validation_errors && is_string( $custom_validation_errors ) ) {
$custom_validation_errors = [ $custom_validation_errors ];
}
// Filter out empty error strings
if ( is_array( $custom_validation_errors ) && count( $custom_validation_errors ) ) {
$custom_validation_errors = array_filter( $custom_validation_errors );
$errors = array_merge( $errors, $custom_validation_errors );
}
// Return: Array of validation errors (each error as a string, representing a single error message)
return $errors;
}
}