HEX
Server: LiteSpeed
System: Linux server315.web-hosting.com 4.18.0-553.54.1.lve.el8.x86_64 #1 SMP Wed Jun 4 13:01:13 UTC 2025 x86_64
User: globfdxw (6114)
PHP: 8.1.34
Disabled: NONE
Upload Files
File: //home/globfdxw/www/wp-content/plugins/kirki/includes/API/Frontend/Controllers/FormController.php
<?php

/**
 * Collection controller
 *
 * @package kirki
 */

namespace Kirki\API\Frontend\Controllers;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

use WP_REST_Server;
use Kirki\Ajax\WpAdmin;
use Kirki\FormValidator\FormValidator;
use Kirki\HelperFunctions;
use WP_Error;


/**
 * FormController for managing front end form submission.
 */
class FormController extends FrontendRESTController {


	/**
	 * Register the form routes
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/form',
			array(
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'save_form_data' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
				),
				'schema' => array( $this, 'get_item_schema' ),
			)
		);
	}

	/**
	 * ReCaptcha token verification
	 *
	 * @param string $secret_key secret key.
	 * @param string $token token.
	 *
	 * @return bool
	 */
	private function verifyGoogleRecaptchaToken( $secret_key, $token ) {
		$url = 'https://www.google.com/recaptcha/api/siteverify';

		$response = HelperFunctions::http_post(
			$url,
			array(
				'method'      => 'POST',
				'httpversion' => '2.0',
				'headers'     => array(
					'Content-type' => 'application/x-www-form-urlencoded',
				),
				'body'        => array(
					'secret'   => $secret_key,
					'response' => $token,
				),
			),
		);

		if ( is_wp_error( $response ) ) {
			return false;
		}

		$response_body = wp_remote_retrieve_body( $response );

		$response_body = json_decode( $response_body, true );

		return (bool) $response_body['success'] ?? false;
	}

	/**
	 * Creates one item from the collection.
	 *
	 * @param \WP_REST_Request $request all user request parameter.
	 *
	 * @return \WP_Error|WP_REST_Response
	 */
	public function save_form_data( $request ) {
		$params          = $request->get_params();
		$additional_keys = array( '_kirki_form', '_wpnonce', '_wp_http_referer', 'g-recaptcha-token', 'g-recaptcha-response' );
		$form_data       = $params;

		if ( isset( $form_data['g-recaptcha-token'] ) ) {
			$common_data = WpAdmin::get_common_data( true );
			$version     = $common_data['recaptcha']['GRC_version'];
			$recaptcha   = $common_data['recaptcha'][ $version ];

			$recaptcha_secret_key = $recaptcha['GRC_secret_key'];
			$recaptcha_token      = $form_data['g-recaptcha-token'];

			$is_valid = $this->verifyGoogleRecaptchaToken( $recaptcha_secret_key, $recaptcha_token );
			if ( ! $is_valid ) {
				wp_send_json_error( 'Sorry, something error happened, please try with right data', 400 );
				exit;
			}
		}
		foreach ( $additional_keys as $key ) {
			unset( $form_data[ $key ] );
		}

		$form_meta_data_base64 = $request->get_param( '_kirki_form' );
		$wpnonce               = $request->get_param( '_wpnonce' );

		if ( ! isset( $form_meta_data_base64 ) || ! isset( $wpnonce ) ) {
			wp_send_json_error( 'Sorry, something error happened, please try with right data', 400 );
			exit;
		}

		// Retrieve form config from session.
		//phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
		$form_meta_data = explode( '|', base64_decode( base64_decode( $form_meta_data_base64 ) ) );
		$form_id        = isset( $form_meta_data[0] ) ? $form_meta_data[0] : null;
		$post_id        = isset( $form_meta_data[1] ) ? $form_meta_data[1] : null;
		$form_config    = HelperFunctions::get_session_data( $form_id );

		if ( ! $form_config ) {
			wp_send_json_error( 'Form config not found', 400 );
			exit;
		}

		// form validation
		$validation_result = FormValidator::validate( $form_data, $form_config['fields'] );
		if ( $validation_result['has_error'] ) {
			wp_send_json_error( $validation_result, 400 );
		}

		if ( ! isset( $form_id ) || ! isset( $post_id ) || ! $form_config ) {
			wp_send_json_error( 'Sorry, something error happened, please try with right data', 400 );
			exit;
		}

		$form_name      = $form_config['name'];
		$max_entry      = $form_config['maxEntry'];
		$response_limit = $form_config['responseLimit'];
		$mail_clients   = $form_config['mailClients'];
		$actions        = $form_config['actions'];

		$email_actions      = array();
		$webhook_actions    = array();
		$mailclient_actions = array();

		$email_list = array(); // Legacy list

		if ( isset( $form_config['emailNotification'] ) ) {
			$email_notification = $form_config['emailNotification'];
			$email_list         = $email_notification['enabled'] && isset( $email_notification['emailList'] ) && is_array( $email_notification['emailList'] ) ? $email_notification['emailList'] : null;
		} else {
			foreach ( $actions as $action ) {
				if ( isset( $action['type'] ) && $action['type'] === 'email' ) {
					$email_actions[] = $action;
				} elseif ( isset( $action['type'] ) && $action['type'] === 'webhooks' ) {
					$webhook_actions[] = $action;
				} elseif ( isset( $action['type'] ) && $action['type'] === 'mailclients' ) {
					$mailclient_actions[] = $action;
				}
			}
		}

		$entry_limit     = $max_entry['restricted'] ? $max_entry['value'] : null;
		$response_limit  = $response_limit['restricted'] ? $response_limit['value'] : null;
		$mail_clients    = isset( $mail_clients ) && is_array( $mail_clients ) ? $mail_clients : null;
		$has_email_field = ( isset( $form_data['email'] ) && is_string( $form_data['email'] ) ) || ( isset( $form_data['Email'] ) && is_string( $form_data['Email'] ) );

		if ( isset( $post_id, $form_id ) ) {
			$saved_form_id = '';
			$saved_form    = $this->get_form( $post_id, $form_id );

			if ( null === $saved_form ) {
				$saved_form_id = $this->insert_form( $post_id, $form_id, $form_name );
			} else {
				$saved_form_id = $saved_form['id'];
				if ( $saved_form['name'] !== $form_name ) {
					global $wpdb;
					// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
					$wpdb->update(
						$wpdb->prefix . KIRKI_FORM_TABLE,
						array(
							'name' => $form_name,
						),
						array( 'id' => $saved_form_id ),
						array( '%s' ),
						array( '%d' ),
					);
				}
			}

			$this->check_entry_limit( $saved_form_id, $entry_limit );
			$this->check_response_limit( $saved_form_id, $response_limit );

			if ( count( $_FILES ) > 0 ) {
				foreach ( $_FILES as $name => $fileData ) {
					if ( $fileData['error'] === UPLOAD_ERR_OK ) {

						// Sanitize file info
						$_FILES[ $name ]['name']     = wp_unslash( $fileData['name'] );
						$_FILES[ $name ]['type']     = wp_unslash( $fileData['type'] );
						$_FILES[ $name ]['tmp_name'] = wp_unslash( $fileData['tmp_name'] );
						$_FILES[ $name ]['error']    = wp_unslash( $fileData['error'] );
						$_FILES[ $name ]['size']     = wp_unslash( $fileData['size'] );

						// --- START: Custom validation ---
						// Assume $form_fields contains your form schema with 'allowed_types' and 'max_size' per field
						$field_config = $form_fields[ $name ] ?? null;
						$file_type    = $_FILES[ $name ]['type'];
						$file_size    = $_FILES[ $name ]['size'];

						if ( $field_config ) {
							// Check MIME/type allowlist
							if ( ! empty( $field_config['allowed_types'] ) && ! in_array( $file_type, $field_config['allowed_types'], true ) ) {
								$form_data[ $name ] = new WP_Error(
									'invalid_file_type',
									/* translators: %s: File type */
									sprintf( __( 'File type "%s" is not allowed for this field.', 'kirki' ), $file_type )
								);
								continue;
							}

							// Check max size
							if ( ! empty( $field_config['max_size'] ) && $file_size > $field_config['max_size'] ) {
								$form_data[ $name ] = new WP_Error(
									'file_too_large',
									/* translators: %s: Maximum file size in bytes */
									sprintf( __( 'File size exceeds the maximum allowed for this field (%s bytes).', 'kirki' ), $field_config['max_size'] )
								);
								continue;
							}
						}
						// --- END: Custom validation ---

						// Upload file safely
						$attachment_id = self::upload_file_to_media( $name );

						if ( ! is_wp_error( $attachment_id ) ) {
							$form_data[ $name ] = $attachment_id;
						} else {
							$form_data[ $name ] = $attachment_id; // Preserve WP_Error
						}
					}
				}
			}

			// Save data
			if ( isset( $saved_form_id ) && ( ! isset( $form_config['saveData'] ) || ( isset( $form_config['saveData'] ) && $form_config['saveData'] ) ) ) {
				$res = $this->insert_form_data( $form_data, $saved_form_id, $form_config['fields'] );
			} else {
				$res = true;
			}
			$res_data = false;
			if ( isset( $res ) && $res !== false ) {
				$res_data = true;
			}
		}

		// Adding shortcodes
		if ( isset( $email_actions ) && ! empty( $email_actions ) ) {
			foreach ( $email_actions as $email_action ) {
				if ( preg_match_all( '/\[([^\]]+)\]/', $email_action['emailList'], $matches ) ) {
					foreach ( $matches[1] as $match ) {
						if ( isset( $form_data[ $match ] ) ) {
							add_shortcode(
								$match,
								function() use ( $form_data, $match ) {
									return $form_data[ $match ];
								}
							);
						}
					}
				}
				if ( preg_match_all( '/\[([^\]]+)\]/', $email_action['replyTo'], $matches ) ) {
					foreach ( $matches[1] as $match ) {
						if ( isset( $form_data[ $match ] ) ) {
							add_shortcode(
								$match,
								function() use ( $form_data, $match ) {
									return $form_data[ $match ];
								}
							);
						}
					}
				}
				if ( preg_match_all( '/\[([^\]]+)\]/', $email_action['name'], $matches ) ) {
					foreach ( $matches[1] as $match ) {
						if ( isset( $form_data[ $match ] ) ) {
							add_shortcode(
								$match,
								function() use ( $form_data, $match ) {
									return $form_data[ $match ];
								}
							);
						}
					}
				}
				if ( preg_match_all( '/\[([^\]]+)\]/', $email_action['subject'], $matches ) ) {
					foreach ( $matches[1] as $match ) {
						if ( isset( $form_data[ $match ] ) ) {
							add_shortcode(
								$match,
								function() use ( $form_data, $match ) {
									return $form_data[ $match ];
								}
							);
						}
					}
				}
			}
			add_shortcode(
				'admin_email',
				function() {
					return get_option( 'admin_email' );
				}
			);
		}

		// Send mail action
		if ( $res_data ) {
			if ( ! empty( $email_list ) ) {
				$body = $this->convert_form_data_into_html_for_email( $form_data );

				add_filter(
					'wp_mail_content_type',
					function () {
						return 'text/html';
					}
				);
				// Prev data
				$this->send_email_notification( $email_list, 'New ' . $form_name, $body );
			} elseif ( ! empty( $email_actions ) ) {
				add_filter(
					'wp_mail_content_type',
					function () {
						return 'text/html';
					}
				);

				foreach ( $email_actions as $email_action ) {
					$body    = $this->convert_form_data_into_html_for_email( $form_data );
					$replyTo = '';
					$name    = '';
					$subject = 'New ' . $form_name;
					$header  = array();

					if ( isset( $email_action['body'] ) ) {
						$body = '';
						foreach ( $email_action['body'] as $body_data ) {
							if ( isset( $body_data['type'] ) && isset( $body_data['value'] ) && $body_data['type'] === 'text' ) {
								$body = $body . $body_data['value'];
							} elseif ( isset( $body_data['type'] ) && isset( $body_data['value'] ) && $body_data['type'] === 'form' && isset( $form_data[ $body_data['value'] ] ) ) {
								$body = $body . $form_data[ $body_data['value'] ];
							}
						}

						$body = nl2br( $body );
					}

					if ( isset( $email_action['replyTo'] ) ) {
						$replyTo = do_shortcode( $email_action['replyTo'] );
					}
					if ( isset( $email_action['name'] ) ) {
						$name = do_shortcode( $email_action['name'] );
					}
					if ( isset( $email_action['subject'] ) ) {
						$subject = do_shortcode( $email_action['subject'] );
					}

					if ( strlen( $replyTo ) > 0 && strlen( $name ) > 0 ) {
						$header = array( 'Reply-To: ' . $name . ' <' . $replyTo . '>' );
					}

					$this->send_email_notification( do_shortcode( $email_action['emailList'] ), $subject, $body, $header );
				}
			}
		}

		// Mailclients Action
		if ( $has_email_field || ( $mail_clients && count( $mail_clients ) && isset( $mail_clients[0]['email_field'] ) ) ) {
			$email = $form_data['email'] ?? $form_data['Email'];
			if ( ( is_array( $mail_clients ) && count( $mail_clients ) ) || count( $mailclient_actions ) ) {
				$merge_fields = array();

				if ( is_array( $form_data ) ) {
					foreach ( $form_data as $key => $value ) {
						if ( self::matchFormField( $key, 'fullname' ) || self::matchFormField( $key, 'name' ) ) {
							$merge_fields['Fullname'] = $value;
						}

						if ( self::matchFormField( $key, 'firstname' ) || self::matchFormField( $key, 'fname' ) ) {
							$merge_fields['FNAME'] = $value;
						}

						if ( self::matchFormField( $key, 'lastname' ) || self::matchFormField( $key, 'lname' ) ) {
							$merge_fields['LNAME'] = $value;
						}

						if ( self::matchFormField( $key, 'birthday' ) || self::matchFormField( $key, 'bday' ) ) {
							$merge_fields['BIRTHDAY'] = $value;
						}

						if ( self::matchFormField( $key, 'birthday' ) || self::matchFormField( $key, 'bday' ) ) {
							$merge_fields['BIRTHDAY'] = $value;
						}
					}
				}

				if ( is_array( $mail_clients ) && count( $mail_clients ) ) {
					foreach ( $mail_clients as $mail_client ) {
						if ( isset( $mail_client['enabled'] ) && $mail_client['enabled'] ) {
							$mailclient_actions[] = $mail_client;
							$name                 = $mail_client['name'];
						}
					}
				}
				foreach ( $mailclient_actions as $mail_client ) {
					$name = $mail_client['name'];
					switch ( $name ) {

						default: {
								break;
						}
					}
				}
			}
		}

		if ( isset( $webhook_actions ) && count( $webhook_actions ) ) {
			foreach ( $webhook_actions as $webhook ) {
				if ( isset( $webhook['action'] ) && isset( $webhook['method'] ) ) {
					if ( $webhook['method'] === 'get' ) {
						$query_string = http_build_query( $form_data );

						if ( substr( $webhook['action'], -1 ) !== '/' ) {
							$webhook['action'] = $webhook['action'] . '/';
						}

						$url_with_query = $webhook['action'] . '?' . $query_string;
						$response       = HelperFunctions::http_get( $url_with_query );
						if ( is_wp_error( $response ) ) {
							$res_data = false;
						}
					} elseif ( $webhook['method'] === 'post' ) {
						$options = array(
							'method'      => 'POST',
							'httpversion' => '2.0',
							'headers'     => array(
								'Content-type' => 'application/x-www-form-urlencoded',
							),
							'body'        => $form_data,
						);

						$response = HelperFunctions::http_post( $webhook['action'], $options );
						if ( is_wp_error( $response ) ) {
							$res_data = false;
						}
					}
				}
			}
		}

		do_action( 'kirki_form_submitted', $form_data, $form_config );

		return rest_ensure_response( $res_data );
	}

	private function upload_file_to_media( $name ) {
		require_once ABSPATH . 'wp-admin/includes/image.php';
		require_once ABSPATH . 'wp-admin/includes/file.php';
		require_once ABSPATH . 'wp-admin/includes/media.php';

		$attachment_id = media_handle_upload( $name, 0 );

		return $attachment_id;
	}

	/**
	 * Convert form data into html for email
	 *
	 * @param array $form_data form all data.
	 *
	 * @return string html string.
	 */
	private function convert_form_data_into_html_for_email( $form_data = array() ) {
		$html = '<ul>';

		if ( is_array( $form_data ) ) {
			foreach ( $form_data as $key => $value ) {
				$html .= '<li>' . esc_html( $key ) . ': ' . esc_html( $value ) . '</li>';
			}
		}

		$html .= '</ul>';
		return $html;
	}

	/**
	 * Send email notification to admin email
	 *
	 * @param string|string[] $to          Array or comma-separated list of email addresses to send message.
	 * @param string          $subject     Email subject.
	 * @param string          $message     Message contents.
	 *
	 * @return void
	 */
	private function send_email_notification( $to, $subject, $message, $headers = array() ) {
		apply_filters( 'kirki_element_smtp', '' );
		wp_mail( $to, $subject, $message, $headers );
	}


	/**
	 * Check form submit/entry limit
	 *
	 * @param int $form_id form id.
	 * @param int $limit     Form limit.
	 * @return void wp_send_json.
	 */
	private function check_entry_limit( $form_id, $limit = null ) {
		if ( ! empty( $limit ) || is_numeric( $limit ) ) {
			global $wpdb;
			$session_id = session_id();
			$table_name = $wpdb->prefix . KIRKI_FORM_DATA_TABLE;
			//phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			$query = $wpdb->prepare( "SELECT COUNT(DISTINCT timestamp) as total_entries FROM $table_name WHERE session_id=%s AND form_id=%s", array( $session_id, $form_id ) );
			//phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$res           = $wpdb->get_results( $query, ARRAY_A );
			$total_entries = (int) $res[0]['total_entries'];

			if ( $total_entries >= $limit ) {
				wp_send_json( false );
				die();
			}
		}
	}

	/**
	 * Check response limit
	 *
	 * @param int $form_id form id.
	 * @param int $limit     Form limit.
	 * @return void wp_send_json.
	 */
	private function check_response_limit( $form_id, $limit = null ) {
		if ( ! empty( $limit ) || is_numeric( $limit ) ) {
			global $wpdb;
			$table_name = $wpdb->prefix . KIRKI_FORM_DATA_TABLE;
			//phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			$query = $wpdb->prepare( "SELECT COUNT(DISTINCT timestamp) as total_entries FROM $table_name WHERE form_id=%s", array( $form_id ) );
			//phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$res           = $wpdb->get_results( $query, ARRAY_A );
			$total_entries = (int) $res[0]['total_entries'];

			if ( $total_entries >= $limit ) {
				wp_send_json( false );
				die();
			}
		}
	}

	/**
	 * Retrieve a specific form
	 *
	 * @param string $post_id wp post id.
	 * @param string $form_id user form id.
	 * @return mixed|null current field id.
	 */
	private function get_form( $post_id, $form_id ) {
		if ( isset( $post_id, $form_id ) ) {
			global $wpdb;
			$table_name = $wpdb->prefix . KIRKI_FORM_TABLE;
			//phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			$field = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE post_id=%d AND form_ele_id=%s", $post_id, $form_id ), ARRAY_A );

			return $field;
		}

		return null;
	}

	/**
	 * Insert a form
	 *
	 * @param string $post_id In which post/page the form resides.
	 * @param string $form_id ID of the form element.
	 * @param string $form_name Name of the form.
	 * @return string Inserted form ID.
	 */
	private function insert_form( $post_id, $form_id, $form_name ) {
		if ( isset( $post_id, $form_id, $form_name ) ) {
			global $wpdb;
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
			$wpdb->insert(
				$wpdb->prefix . KIRKI_FORM_TABLE,
				array(
					'post_id'     => (int) $post_id,
					'form_ele_id' => $form_id,
					'name'        => $form_name,
				),
				array(
					'%d',
					'%s',
					'%s',
				)
			);
			return $wpdb->insert_id;
		}

		return '';
	}

	/**
	 * Insert form data
	 *
	 * @param iterable|object $form_data form data.
	 * @param string          $form_id form id.
	 * @return int|Boolean
	 */
	private function insert_form_data( $form_data, $form_id, $form_data_types = array() ) {
		if ( isset( $form_data, $form_id ) && ! empty( $form_data ) ) {
			global $wpdb;
			$table_name    = $wpdb->prefix . KIRKI_FORM_DATA_TABLE;
			$timestamp     = time();
			$session_id    = session_id();
			$values        = array();
			$place_holders = array();
			$query         = "INSERT INTO $table_name (form_id, user_id, session_id, timestamp, input_key, input_value, input_type) VALUES ";
			$plholder_str  = '';

			foreach ( $form_data as $name => $value ) {
				$type = isset( $form_data_types[ $name ]['type'] ) ? $form_data_types[ $name ]['type'] : 'text';

				array_push(
					$values,
					$form_id,
					get_current_user_id(),
					$session_id,
					$timestamp,
					"$name",
					"$value",
					"$type"
				);

				$place_holders[] = '(%d, NULLIF(%d, 0), %s, %d, %s, %s, %s)';
			}

			$plholder_str = implode( ', ', $place_holders );

			$query .= $plholder_str;
			//phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
			$sql = $wpdb->prepare( "$query ", $values );
			//phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$res = $wpdb->query( $sql );
			return $res;
		}

		return false;
	}

	/**
	 * Search or match from filed
	 *
	 * @param string $field_name search column name.
	 * @param string $match_text search column value.
	 * @return boolean
	 */
	private static function matchFormField( $field_name = '', $match_text = '' ) {
		if ( ! empty( $field_name ) && ! empty( $match_text ) && strtolower( preg_replace( '/\s|_|-/', '', $field_name ) ) === $match_text ) {
			return true;
		}
		return false;
	}
}