Файловый менеджер - Редактировать - /var/www/xthruster/html/wp-content/uploads/flags/forms.tar
Назад
classes/akismet.php 0000644 00000012634 14720522625 0010362 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Classes; use Elementor\Controls_Manager; use ElementorPro\Core\Utils; use ElementorPro\Modules\Forms\Classes\Ajax_Handler; use ElementorPro\Modules\Forms\Classes\Form_Record; use ElementorPro\Modules\Forms\Widgets\Form; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Akismet { public function __construct() { add_action( 'elementor/element/form/section_steps_settings/after_section_end', [ $this, 'register_settings_section' ] ); add_action( 'elementor_pro/forms/validation', [ $this, 'validation' ], 10, 2 ); } /** * @param Form $form */ public function register_settings_section( $form ) { if ( ! $this->is_akismet_active() ) { return; } $form->start_controls_section( 'section_akismet', [ 'label' => esc_html__( 'Akismet Spam Protection', 'elementor-pro' ), ] ); $form->add_control( 'akismet_enabled', [ 'label' => esc_html__( 'Akismet Spam Protection', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'label_off' => esc_html__( 'Off', 'elementor-pro' ), 'label_on' => esc_html__( 'On', 'elementor-pro' ), 'default' => 'yes', ] ); $form->add_control( 'akismet_info', [ 'type' => Controls_Manager::ALERT, 'alert_type' => 'info', 'content' => sprintf( /* translators: 1: Link opening tag, 2: Link closing tag. */ esc_html__( 'Assign shortcodes to the fields below to enable spam protection on your form. %1$sShow me how%2$s', 'elementor-pro' ), '<a href="http://go.elementor.com/widget-form-akismet/" target="_blank">', '</a>' ), 'condition' => [ 'akismet_enabled' => 'yes', ], ] ); $form->add_control( 'akismet_author', [ 'label' => esc_html__( 'Name', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'placeholder' => 'e.g. [field id="name"]', 'ai' => [ 'active' => false, ], 'label_block' => true, 'render_type' => 'none', 'condition' => [ 'akismet_enabled' => 'yes', ], ] ); $form->add_control( 'akismet_author_url', [ 'label' => esc_html__( 'URL', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'placeholder' => 'e.g. [field id="url"]', 'ai' => [ 'active' => false, ], 'label_block' => true, 'render_type' => 'none', 'condition' => [ 'akismet_enabled' => 'yes', ], ] ); $form->add_control( 'akismet_author_email', [ 'label' => esc_html__( 'Email', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'placeholder' => 'e.g. [field id="email"]', 'ai' => [ 'active' => false, ], 'label_block' => true, 'render_type' => 'none', 'condition' => [ 'akismet_enabled' => 'yes', ], ] ); $form->add_control( 'akismet_content', [ 'label' => esc_html__( 'Message', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'placeholder' => 'e.g. [field id="message"]', 'ai' => [ 'active' => false, ], 'label_block' => true, 'render_type' => 'none', 'condition' => [ 'akismet_enabled' => 'yes', ], ] ); $form->end_controls_section(); } private function is_akismet_active() : bool { $akismet_key = \Akismet::get_api_key(); return ! empty( $akismet_key ); } /** * @param Form_Record $record * @param Ajax_Handler $ajax_handler */ public function validation( $record, $ajax_handler ) { if ( ! $this->is_akismet_active() ) { return; } if ( ! $this->is_spammed( $record ) ) { return; } $ajax_handler->add_error_message( esc_html__( 'We couldn’t submit your responses because they seem like spam.', 'elementor-pro' ) ); $ajax_handler->add_error( 'akismet', esc_html__( 'Spam detected', 'elementor-pro' ) ); } private function is_spammed( Form_Record $record ) : bool { $settings = $record->get( 'form_settings' ); if ( empty( $settings['akismet_enabled'] ) ) { return false; } $params = []; $params['comment_author'] = $this->get_parsed_content( $record, $settings['akismet_author'] ); $params['comment_author_email'] = $this->get_parsed_content( $record, $settings['akismet_author_email'] ); $params['comment_author_url'] = $this->get_parsed_content( $record, $settings['akismet_author_url'] ); $params['comment_content'] = $this->get_parsed_content( $record, $settings['akismet_content'] ); $params['blog'] = get_option( 'home' ); $params['blog_lang'] = get_locale(); $params['blog_charset'] = get_option( 'blog_charset' ); $params['user_ip'] = Utils::get_client_ip(); $params['referrer'] = wp_get_referer(); if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) { $params['user_agent'] = sanitize_textarea_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); } // http://blog.akismet.com/2012/06/19/pro-tip-tell-us-your-comment_type/ $params['comment_type'] = 'contact-form'; $ignore = array( 'HTTP_COOKIE', 'HTTP_COOKIE2', 'PHP_AUTH_PW' ); foreach ( $_SERVER as $key => $value ) { if ( ! in_array( $key, $ignore ) && is_string( $value ) ) { $params[ $key ] = $value; } } return $this->remote_check_is_spam( $params ); } private function get_parsed_content( $record, $content ) { $setting = trim( $content ); return $record->replace_setting_shortcodes( $setting ); } private function remote_check_is_spam( $params ) { $response = \Akismet::http_post( _http_build_query( $params, '', '&' ), 'comment-check' ); return ( 'true' === $response[1] ); } } classes/convertkit-handler.php 0000644 00000005157 14720522625 0012532 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Convertkit_Handler { /* * @var Rest_Client */ private $rest_client = null; private $api_key = ''; /** * Convertkit_Handler constructor. * * @param $api_key * * @throws \Exception */ public function __construct( $api_key ) { if ( empty( $api_key ) ) { throw new \Exception( 'Invalid API key.' ); } $this->init_rest_client( $api_key ); if ( ! $this->is_valid_api_key() ) { throw new \Exception( 'Invalid API key.' ); } } private function init_rest_client( $api_key ) { $this->api_key = $api_key; $this->rest_client = new Rest_Client( 'https://api.convertkit.com/v3/' ); } /** * validate api key * * @return bool * @throws \Exception */ private function is_valid_api_key() { $forms = $this->get_forms(); if ( ! empty( $forms ) ) { return true; } $this->api_key = ''; return false; } public function get_forms_and_tags() { $forms = $this->get_forms(); $tags = $this->get_tags(); return [ 'data' => [ 'forms' => $forms['forms'], 'tags' => $tags['tags'], ], ]; } /** * get GetResponse lists associated with API key * @return array * @throws \Exception */ public function get_forms() { $results = $this->rest_client->get( 'forms/?api_key=' . $this->api_key ); $forms = [ '' => esc_html__( 'Select...', 'elementor-pro' ), ]; if ( ! empty( $results['body']['forms'] ) ) { foreach ( $results['body']['forms'] as $index => $form ) { if ( ! is_array( $form ) ) { continue; } $forms[ $form['id'] ] = $form['name']; } } $return_array = [ 'forms' => $forms, ]; return $return_array; } public function get_tags() { $results = $this->rest_client->get( 'tags/?api_key=' . $this->api_key ); $tags = [ '' => esc_html__( 'Select...', 'elementor-pro' ), ]; if ( ! empty( $results['body']['tags'] ) ) { foreach ( $results['body']['tags'] as $index => $tag ) { if ( ! is_array( $tag ) ) { continue; } $tags[ $tag['id'] ] = $tag['name']; } } $return_array = [ 'tags' => $tags, ]; return $return_array; } /** * create contact at ConvertKit via api * * @param array $subscriber_data * * @return array|mixed * @throws \Exception */ public function create_subscriber( $form_id, $subscriber_data = [] ) { $endpoint = sprintf( 'forms/' . $form_id . '/subscribe?api_key=%s', $this->api_key ); $this->rest_client->add_headers( 'Content-Type', 'application/json' ); return $this->rest_client->post( $endpoint, $subscriber_data ); } } classes/recaptcha-handler.php 0000644 00000021435 14720522625 0012271 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Classes; use Elementor\Settings; use Elementor\Widget_Base; use ElementorPro\Core\Utils; use ElementorPro\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Integration with Google reCAPTCHA */ class Recaptcha_Handler { const OPTION_NAME_SITE_KEY = 'elementor_pro_recaptcha_site_key'; const OPTION_NAME_SECRET_KEY = 'elementor_pro_recaptcha_secret_key'; const OPTION_NAME_RECAPTCHA_THRESHOLD = 'elementor_pro_recaptcha_threshold'; const V2_CHECKBOX = 'v2_checkbox'; protected static function get_recaptcha_name() { return 'recaptcha'; } public static function get_site_key() { return get_option( self::OPTION_NAME_SITE_KEY ); } public static function get_secret_key() { return get_option( self::OPTION_NAME_SECRET_KEY ); } public static function get_recaptcha_type() { return self::V2_CHECKBOX; } public static function is_enabled() { return static::get_site_key() && static::get_secret_key(); } public static function get_setup_message() { return esc_html__( 'To use reCAPTCHA, you need to add the API Key and complete the setup process in Dashboard > Elementor > Settings > Integrations > reCAPTCHA.', 'elementor-pro' ); } public function register_admin_fields( Settings $settings ) { $settings->add_section( Settings::TAB_INTEGRATIONS, static::get_recaptcha_name(), [ 'label' => esc_html__( 'reCAPTCHA', 'elementor-pro' ), 'callback' => function () { echo sprintf( /* translators: 1: Link opening tag, 2: Link closing tag. */ esc_html__( '%1$sreCAPTCHA%2$s is a free service by Google that protects your website from spam and abuse. It does this while letting your valid users pass through with ease.', 'elementor-pro' ), '<a href="https://www.google.com/recaptcha/" target="_blank">', '</a>' ); }, 'fields' => [ 'pro_recaptcha_site_key' => [ 'label' => esc_html__( 'Site Key', 'elementor-pro' ), 'field_args' => [ 'type' => 'text', ], ], 'pro_recaptcha_secret_key' => [ 'label' => esc_html__( 'Secret Key', 'elementor-pro' ), 'field_args' => [ 'type' => 'text', ], ], ], ] ); } public function localize_settings( $settings ) { $settings = array_replace_recursive( $settings, [ 'forms' => [ static::get_recaptcha_name() => [ 'enabled' => static::is_enabled(), 'type' => static::get_recaptcha_type(), 'site_key' => static::get_site_key(), 'setup_message' => static::get_setup_message(), ], ], ] ); return $settings; } protected static function get_script_render_param() { return 'explicit'; } protected static function get_script_name() { return 'elementor-' . static::get_recaptcha_name() . '-api'; } public function register_scripts() { $script_name = static::get_script_name(); $src = 'https://www.google.com/recaptcha/api.js?render=explicit'; wp_register_script( $script_name, $src, [], ELEMENTOR_PRO_VERSION, true ); } public function enqueue_scripts() { if ( Plugin::elementor()->preview->is_preview_mode() ) { return; } $script_name = static::get_script_name(); wp_enqueue_script( $script_name ); } /** * @param Form_Record $record * @param Ajax_Handler $ajax_handler */ public function validation( $record, $ajax_handler ) { $fields = $record->get_field( [ 'type' => static::get_recaptcha_name(), ] ); if ( empty( $fields ) ) { return; } $field = current( $fields ); // PHPCS - response protected by recaptcha secret $recaptcha_response = Utils::_unstable_get_super_global_value( $_POST, 'g-recaptcha-response' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( empty( $recaptcha_response ) ) { $ajax_handler->add_error( $field['id'], esc_html__( 'The Captcha field cannot be blank. Please enter a value.', 'elementor-pro' ) ); return; } $recaptcha_errors = [ 'missing-input-secret' => esc_html__( 'The secret parameter is missing.', 'elementor-pro' ), 'invalid-input-secret' => esc_html__( 'The secret parameter is invalid or malformed.', 'elementor-pro' ), 'missing-input-response' => esc_html__( 'The response parameter is missing.', 'elementor-pro' ), 'invalid-input-response' => esc_html__( 'The response parameter is invalid or malformed.', 'elementor-pro' ), ]; $recaptcha_secret = static::get_secret_key(); $client_ip = Utils::get_client_ip(); $request = [ 'body' => [ 'secret' => $recaptcha_secret, 'response' => $recaptcha_response, 'remoteip' => $client_ip, ], ]; $response = wp_remote_post( 'https://www.google.com/recaptcha/api/siteverify', $request ); $response_code = wp_remote_retrieve_response_code( $response ); if ( 200 !== (int) $response_code ) { /* translators: %d: Response code. */ $ajax_handler->add_error( $field['id'], sprintf( esc_html__( 'Can not connect to the reCAPTCHA server (%d).', 'elementor-pro' ), $response_code ) ); return; } $body = wp_remote_retrieve_body( $response ); $result = json_decode( $body, true ); if ( ! $this->validate_result( $result, $field ) ) { $message = esc_html__( 'Invalid form, reCAPTCHA validation failed.', 'elementor-pro' ); if ( isset( $result['error-codes'] ) ) { $result_errors = array_flip( $result['error-codes'] ); foreach ( $recaptcha_errors as $error_key => $error_desc ) { if ( isset( $result_errors[ $error_key ] ) ) { $message = $recaptcha_errors[ $error_key ]; break; } } } $this->add_error( $ajax_handler, $field, $message ); } // If success - remove the field form list (don't send it in emails and etc ) $record->remove_field( $field['id'] ); } /** * @param Ajax_Handler $ajax_handler * @param $field * @param $message */ protected function add_error( $ajax_handler, $field, $message ) { $ajax_handler->add_error( $field['id'], $message ); } protected function validate_result( $result, $field ) { if ( ! $result['success'] ) { return false; } return true; } /** * @param $item * @param $item_index * @param $widget Widget_Base */ public function render_field( $item, $item_index, $widget ) { $recaptcha_html = '<div class="elementor-field" id="form-field-' . $item['custom_id'] . '">'; $recaptcha_name = static::get_recaptcha_name(); if ( static::is_enabled() ) { $this->enqueue_scripts(); $this->add_render_attributes( $item, $item_index, $widget ); $recaptcha_html .= '<div ' . $widget->get_render_attribute_string( $recaptcha_name . $item_index ) . '></div>'; } elseif ( current_user_can( 'manage_options' ) ) { $recaptcha_html .= '<div class="elementor-alert elementor-alert-info">'; $recaptcha_html .= static::get_setup_message(); $recaptcha_html .= '</div>'; } $recaptcha_html .= '</div>'; // PHPCS - It's all escaped echo $recaptcha_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** * @param $item * @param $item_index * @param $widget Widget_Base */ protected function add_render_attributes( $item, $item_index, $widget ) { $recaptcha_name = static::get_recaptcha_name(); $widget->add_render_attribute( [ $recaptcha_name . $item_index => [ 'class' => 'elementor-g-recaptcha', 'data-sitekey' => static::get_site_key(), 'data-type' => static::get_recaptcha_type(), ], ] ); $this->add_version_specific_render_attributes( $item, $item_index, $widget ); } /** * @param $item * @param $item_index * @param $widget Widget_Base */ protected function add_version_specific_render_attributes( $item, $item_index, $widget ) { $recaptcha_name = static::get_recaptcha_name(); $widget->add_render_attribute( $recaptcha_name . $item_index, [ 'data-theme' => $item['recaptcha_style'], 'data-size' => $item['recaptcha_size'], ] ); } public function add_field_type( $field_types ) { $field_types['recaptcha'] = esc_html__( 'reCAPTCHA', 'elementor-pro' ); return $field_types; } public function filter_field_item( $item ) { if ( static::get_recaptcha_name() === $item['field_type'] ) { $item['field_label'] = false; } return $item; } public function __construct() { $this->register_scripts(); add_filter( 'elementor_pro/forms/field_types', [ $this, 'add_field_type' ] ); add_action( 'elementor_pro/forms/render_field/' . static::get_recaptcha_name(), [ $this, 'render_field' ], 10, 3 ); add_filter( 'elementor_pro/forms/render/item', [ $this, 'filter_field_item' ] ); add_filter( 'elementor_pro/editor/localize_settings', [ $this, 'localize_settings' ] ); if ( static::is_enabled() ) { add_action( 'elementor_pro/forms/validation', [ $this, 'validation' ], 10, 2 ); add_action( 'elementor/preview/enqueue_scripts', [ $this, 'enqueue_scripts' ] ); } if ( is_admin() ) { add_action( 'elementor/admin/after_create_settings/' . Settings::PAGE_ID, [ $this, 'register_admin_fields' ] ); } } } classes/getresponse-handler.php 0000644 00000006037 14720522625 0012676 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Getresponse_Handler { public $rest_client = null; private $api_key = ''; public function __construct( $api_key ) { if ( empty( $api_key ) ) { throw new \Exception( 'Invalid API key.' ); } $this->init_rest_client( $api_key ); if ( ! $this->is_valid_api_key() ) { throw new \Exception( 'Invalid API key.' ); } } private function init_rest_client( $api_key ) { $this->api_key = $api_key; $this->rest_client = new Rest_Client( 'https://api.getresponse.com/v3/' ); $this->rest_client->add_headers( [ 'X-Auth-Token' => 'api-key ' . $api_key, 'Content-Type' => 'application/json', ] ); } /** * validate api key * * @return bool * @throws \Exception */ private function is_valid_api_key() { $lists = $this->get_lists(); if ( ! empty( $lists ) ) { return true; } $this->api_key = ''; return false; } /** * get GetResponse lists associated with API key * @return array * @throws \Exception */ public function get_lists() { $results = $this->rest_client->get( 'campaigns' ); $lists = [ '' => esc_html__( 'Select...', 'elementor-pro' ), ]; if ( ! empty( $results['body'] ) ) { foreach ( $results['body'] as $index => $list ) { if ( ! is_array( $list ) ) { continue; } $lists[ $list['campaignId'] ] = $list['name']; } } $return_array = [ 'lists' => $lists, ]; return $return_array; } public function get_fields() { $results = $this->rest_client->get( 'custom-fields' ); $fields = [ [ 'remote_label' => esc_html__( 'Email', 'elementor-pro' ), 'remote_type' => 'email', 'remote_id' => 'email', 'remote_required' => true, ], [ 'remote_label' => esc_html__( 'Name', 'elementor-pro' ), 'remote_type' => 'text', 'remote_id' => 'name', 'remote_required' => false, ], ]; if ( ! empty( $results['body'] ) ) { foreach ( $results['body'] as $field ) { $fields[] = [ 'remote_label' => $field['name'], 'remote_type' => $this->normalize_type( $field['type'] ), 'remote_id' => $field['customFieldId'], 'remote_required' => false, ]; } } $return_array = [ 'fields' => $fields, ]; return $return_array; } private function normalize_type( $type ) { static $types = [ 'text' => 'text', 'number' => 'number', 'address' => 'text', 'phone' => 'text', 'date' => 'text', 'url' => 'url', 'imageurl' => 'url', 'radio' => 'radio', 'dropdown' => 'select', 'single_select' => 'select', 'textarea' => 'text', 'birthday' => 'text', 'zip' => 'text', 'country' => 'text', 'gender' => 'text', ]; return $types[ $type ]; } /** * create contact at GetResponse via api * * @param array $subscriber_data * * @return array|mixed * @throws \Exception */ public function create_subscriber( $subscriber_data = [] ) { return $this->rest_client->request( 'POST', 'contacts', wp_json_encode( $subscriber_data ), 202 ); } } classes/mailchimp-handler.php 0000644 00000010506 14720522625 0012277 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Mailchimp_Handler { private $api_base_url = ''; private $api_key = ''; private $api_request_args = []; /** * Mailchimp_Handler constructor. * * @param $api_key * * @throws \Exception */ public function __construct( $api_key ) { if ( empty( $api_key ) ) { throw new \Exception( 'Invalid API key.' ); } // The API key is in format XXXXXXXXXXXXXXXXXXXX-us2 where us2 is the server sub domain for this account $key_parts = explode( '-', $api_key ); if ( empty( $key_parts[1] ) || 0 !== strpos( $key_parts[1], 'us' ) ) { throw new \Exception( 'Invalid API key.' ); } $this->api_key = $api_key; $this->api_base_url = 'https://' . $key_parts[1] . '.api.mailchimp.com/3.0/'; $this->api_request_args = [ 'headers' => [ 'Authorization' => 'Basic ' . base64_encode( 'user:' . $this->api_key ), ], ]; } public function query( $end_point ) { $response = wp_remote_get( $this->api_base_url . $end_point, $this->api_request_args ); if ( is_wp_error( $response ) || 200 != (int) wp_remote_retrieve_response_code( $response ) ) { throw new \Exception( 'Mailchimp error.' ); } $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( ! is_array( $body ) ) { throw new \Exception( 'Mailchimp error.' ); } return $body; } public function post( $end_point, $data, $request_args = [] ) { $this->api_request_args += $request_args; $this->api_request_args['headers']['Content-Type'] = 'application/json; charset=utf-8'; $this->api_request_args['body'] = wp_json_encode( $data ); $response = wp_remote_post( $this->api_base_url . $end_point, $this->api_request_args ); if ( is_wp_error( $response ) ) { throw new \Exception( 'Mailchimp error.' ); } $body = json_decode( wp_remote_retrieve_body( $response ), true ); $code = (int) wp_remote_retrieve_response_code( $response ); // Throw an exception if there is no response body. // NOTE: HTTP 204 doesn't have a body. if ( 204 !== $code && ! is_array( $body ) ) { throw new \Exception( 'Mailchimp error.' ); } return [ 'code' => $code, 'body' => $body, ]; } public function get_lists() { $results = $this->query( 'lists?count=999' ); $lists = [ '' => 'Select...', ]; if ( ! empty( $results['lists'] ) ) { foreach ( $results['lists'] as $list ) { $lists[ $list['id'] ] = $list['name']; } } $return_array = [ 'lists' => $lists, ]; return $return_array; } public function get_groups( $list_id ) { $results = $this->query( 'lists/' . $list_id . '/interest-categories?count=999' ); $groups = []; if ( ! empty( $results['categories'] ) ) { foreach ( $results['categories'] as $category ) { $interests_results = $this->query( 'lists/' . $list_id . '/interest-categories/' . $category['id'] . '/interests?count=999' ); foreach ( $interests_results['interests'] as $interest ) { $groups[ $interest['id'] ] = $category['title'] . ' - ' . $interest['name']; } } } $return_array = [ 'groups' => $groups, ]; return $return_array; } public function get_fields( $list_id ) { $results = $this->query( 'lists/' . $list_id . '/merge-fields?count=999' ); $fields = [ [ 'remote_label' => 'Email', 'remote_type' => 'email', 'remote_id' => 'email', 'remote_required' => true, ], ]; if ( ! empty( $results['merge_fields'] ) ) { foreach ( $results['merge_fields'] as $field ) { $fields[] = [ 'remote_label' => $field['name'], 'remote_type' => $this->normalize_type( $field['type'] ), 'remote_id' => $field['tag'], 'remote_required' => $field['required'], ]; } } $return_array = [ 'fields' => $fields, ]; return $return_array; } public function get_list_details( $list_id ) { $groups = $this->get_groups( $list_id ); $fields = $this->get_fields( $list_id ); return [ 'list_details' => $groups + $fields, ]; } private function normalize_type( $type ) { static $types = [ 'text' => 'text', 'number' => 'number', 'address' => 'text', 'phone' => 'text', 'date' => 'text', 'url' => 'url', 'imageurl' => 'url', 'radio' => 'radio', 'dropdown' => 'select', 'birthday' => 'text', 'zip' => 'text', ]; return $types[ $type ]; } } classes/recaptcha-v3-handler.php 0000644 00000011067 14720522625 0012617 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Classes; use Elementor\Settings; use Elementor\Widget_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Integration with Google reCAPTCHA */ class Recaptcha_V3_Handler extends Recaptcha_Handler { const OPTION_NAME_V3_SITE_KEY = 'elementor_pro_recaptcha_v3_site_key'; const OPTION_NAME_V3_SECRET_KEY = 'elementor_pro_recaptcha_v3_secret_key'; const OPTION_NAME_RECAPTCHA_THRESHOLD = 'elementor_pro_recaptcha_v3_threshold'; const V3 = 'v3'; const V3_DEFAULT_THRESHOLD = 0.5; const V3_DEFAULT_ACTION = 'Form'; protected static function get_recaptcha_name() { return 'recaptcha_v3'; } public static function get_site_key() { return get_option( self::OPTION_NAME_V3_SITE_KEY ); } public static function get_secret_key() { return get_option( self::OPTION_NAME_V3_SECRET_KEY ); } public static function get_recaptcha_type() { return self::V3; } public static function get_recaptcha_threshold() { $threshold = get_option( self::OPTION_NAME_RECAPTCHA_THRESHOLD, self::V3_DEFAULT_THRESHOLD ); if ( 0 > $threshold || 1 < $threshold ) { return self::V3_DEFAULT_THRESHOLD; } return $threshold; } public static function is_enabled() { return static::get_site_key() && static::get_secret_key(); } public static function get_setup_message() { return esc_html__( 'To use reCAPTCHA V3, you need to add the API Key and complete the setup process in Dashboard > Elementor > Settings > Integrations > reCAPTCHA V3.', 'elementor-pro' ); } public function register_admin_fields( Settings $settings ) { $settings->add_section( Settings::TAB_INTEGRATIONS, 'recaptcha_v3', [ 'label' => esc_html__( 'reCAPTCHA V3', 'elementor-pro' ), 'callback' => function() { echo sprintf( /* translators: 1: Link opening tag, 2: Link closing tag. */ esc_html__( '%1$sreCAPTCHA V3%2$s is a free service by Google that protects your website from spam and abuse. It does this while letting your valid users pass through with ease.', 'elementor-pro' ), '<a href="https://www.google.com/recaptcha/intro/v3.html" target="_blank">', '</a>' ); }, 'fields' => [ 'pro_recaptcha_v3_site_key' => [ 'label' => esc_html__( 'Site Key', 'elementor-pro' ), 'field_args' => [ 'type' => 'text', ], ], 'pro_recaptcha_v3_secret_key' => [ 'label' => esc_html__( 'Secret Key', 'elementor-pro' ), 'field_args' => [ 'type' => 'text', ], ], 'pro_recaptcha_v3_threshold' => [ 'label' => esc_html__( 'Score Threshold', 'elementor-pro' ), 'field_args' => [ 'attributes' => [ 'min' => 0, 'max' => 1, 'placeholder' => '0.5', 'step' => '0.1', ], 'std' => 0.5, 'type' => 'number', 'desc' => esc_html__( 'Score threshold should be a value between 0 and 1, default: 0.5', 'elementor-pro' ), ], ], ], ] ); } /** * @param $item * @param $item_index * @param $widget Widget_Base */ protected function add_version_specific_render_attributes( $item, $item_index, $widget ) { $recaptcha_name = static::get_recaptcha_name(); $widget->add_render_attribute( $recaptcha_name . $item_index, [ 'data-action' => self::V3_DEFAULT_ACTION, 'data-badge' => $item['recaptcha_badge'], 'data-size' => 'invisible', ] ); } /** * @param Ajax_Handler $ajax_handler * @param $field * @param $message */ protected function add_error( $ajax_handler, $field, $message ) { parent::add_error( $ajax_handler, $field, $message ); $ajax_handler->add_error_message( esc_html__( 'reCAPTCHA V3 validation failed, suspected as abusive usage', 'elementor-pro' ) ); } protected function validate_result( $result, $field ) { $action = self::V3_DEFAULT_ACTION; $action_ok = ! isset( $result['action'] ) ? true : $action === $result['action']; return $action_ok && ( $result['score'] > self::get_recaptcha_threshold() ); } public function add_field_type( $field_types ) { $field_types['recaptcha_v3'] = esc_html__( 'reCAPTCHA V3', 'elementor-pro' ); return $field_types; } /** * @param $item * @param $item_index * @param Widget_Base $widget * * @return $item */ public function filter_recaptcha_item( $item, $item_index, $widget ) { $widget->add_render_attribute( 'field-group' . $item_index, 'class', [ self::get_recaptcha_name() . '-' . $item['recaptcha_badge'], ] ); return $item; } public function __construct() { parent::__construct(); add_filter( 'elementor_pro/forms/render/item/' . self::get_recaptcha_name(), [ $this, 'filter_recaptcha_item' ], 10, 3 ); } } classes/action-base.php 0000644 00000001454 14720522625 0011110 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Classes; use ElementorPro\Modules\Forms\Widgets\Form; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Action_Base { abstract public function get_name(); abstract public function get_label(); /** * Get the action ID. * * TODO: Make it an abstract function that will replace `get_name()`. * * @since 3.5.0 * * @return string */ public function get_id() { return $this->get_name(); } /** * @param Form_Record $record * @param Ajax_Handler $ajax_handler */ abstract public function run( $record, $ajax_handler ); /** * @param Form $form */ abstract public function register_settings_section( $form ); /** * @param array $element */ abstract public function on_export( $element ); } classes/mailerlite-handler.php 0000644 00000005074 14720522625 0012467 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Mailerlite_Handler { /** * @var Rest_Client */ private $rest_client = null; private $api_key = ''; /** * Mailerlite_Handler constructor. * * @param $api_key * * @throws \Exception */ public function __construct( $api_key ) { if ( empty( $api_key ) ) { throw new \Exception( 'Invalid API key.' ); } $this->init_rest_client( $api_key ); if ( ! $this->is_valid_api_key() ) { throw new \Exception( 'Invalid API key.' ); } } private function init_rest_client( $api_key ) { $this->api_key = $api_key; $this->rest_client = new Rest_Client( 'https://api.mailerlite.com/api/v2/' ); $this->rest_client->add_headers( [ 'X-MailerLite-ApiKey' => $api_key, 'Content-Type' => 'application/json', ] ); } /** * validate api key * * @return bool * @throws \Exception */ private function is_valid_api_key() { $groups = $this->rest_client->get( 'groups' ); if ( ! empty( $groups ) ) { return true; } $this->api_key = ''; return false; } /** * get MailerLite groups associated with API key * @return array * @throws \Exception */ public function get_groups() { $results = $this->rest_client->get( 'groups' ); $groups = [ '' => esc_html__( 'Select...', 'elementor-pro' ), ]; if ( 200 === $results['code'] ) { foreach ( $results['body'] as $index => $group ) { $groups[ $group['id'] ] = $group['name']; } } $return_array = [ 'groups' => $groups, 'fields' => $this->get_fields(), ]; return $return_array; } /** * get MailerLite fields associated with API key * @return array * @throws \Exception */ public function get_fields() { $results = $this->rest_client->get( 'fields' ); $fields = []; if ( ! empty( $results['body'] ) ) { foreach ( $results['body'] as $index => $field ) { if ( ! is_array( $field ) || empty( $field['date_updated'] ) ) { continue; } $fields[] = [ 'remote_label' => $field['title'], 'remote_type' => strtolower( $field['type'] ), 'remote_id' => $field['key'], 'remote_required' => false, ]; } } return $fields; } /** * create subscriber at drip via api * * @param string $group * @param array $subscriber_data * * @return array|mixed * @throws \Exception */ public function create_subscriber( $group = '', $subscriber_data = [] ) { $end_point = sprintf( 'groups/%s/subscribers', $group ); return $this->rest_client->post( $end_point, $subscriber_data ); } } classes/rest-client.php 0000644 00000010524 14720522625 0011152 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Rest_Client { private $api_base_url = ''; private $user_agent = 'Elementor Forms (elementor.com)'; public $request_cache = []; private $headers = []; private $request_args = []; public function __construct( $rest_base_url ) { $this->api_base_url = $rest_base_url; //setup defaults $this->set_request_arg( 'timeout', 30 ) ->set_request_arg( 'sslverify', false ) ->add_headers( 'User-Agent', $this->user_agent ); /** * Initiate Elementor form REST API client. * * Fires when Elementor forms are initiated on REST API client. * * @since 2.4.0 * * @param Rest_Client $this An instance of form REST API client. */ do_action( 'elementor-pro/forms/rest_client/init', $this ); return $this; } /** * Set REST API base url. * * @param string $url */ public function set_base_url( $url ) { $this->api_base_url = $url; } /** * Get REST API base url. * * @return string */ public function get_base_url() { return $this->api_base_url; } /** * Add headers to REST API. * * @param $key Header key. * @param $value Optional. Header value. Default is null. * * @return $this An instance of REST API client. */ public function add_headers( $key, $value = null ) { if ( ! is_array( $key ) ) { $this->headers[ $key ] = $value; return $this; } foreach ( $key as $header => $header_value ) { $this->headers[ $header ] = $header_value; } return $this; } /** * Set REST API request arguments. * * @param string $name Optional. Request argument name. Default is ''. * @param null $value Optional. Request argument value. Default is null. * * @return $this An instance of REST API client. */ public function set_request_arg( $name = '', $value = null ) { $this->request_args[ $name ] = $value; return $this; } /** * @uses request * * @param string $endpoint Optional. Default is ''. * @param null $data Optional. Default is null. * * @return array|mixed * @throws \Exception */ public function post( $endpoint = '', $data = null ) { $request_body = wp_json_encode( $data ); return $this->request( 'POST', $endpoint, $request_body ); } /** * @uses request * * @param string $endpoint Optional. Default is ''. * @param null $data Optional. Default is null. * * @return array|mixed * @throws \Exception */ public function get( $endpoint = '', $data = null ) { return $this->request( 'GET', $endpoint, $data ); } /** * @param string $method Optional. Default is 'GET'. * @param string $endpoint Optional. Default is ''. * @param null $request_body Optional. Default is null. * @param int $valid_response_code Optional. Default is '200'. * * @return array * @throws \Exception */ public function request( $method = 'GET', $endpoint = '', $request_body = null, $valid_response_code = 200 ) { $request_url = $this->api_base_url . $endpoint; $base_args = [ 'method' => $method, 'headers' => $this->headers, ]; $api_request_args = array_merge( $base_args, $this->request_args ); if ( null !== $request_body ) { if ( in_array( $method, [ 'POST', 'PUT' ] ) ) { $api_request_args['body'] = $request_body; } else { $request_url = add_query_arg( $request_body, $request_url ); } } $cache_key = md5( $method . $endpoint . json_encode( $api_request_args ) ); if ( isset( $this->request_cache[ $cache_key ] ) && isset( $this->request_cache[ $cache_key ]['parsed'] ) ) { $this->request_cache[ $cache_key ]['parsed']; } $response = wp_remote_request( $request_url, $api_request_args ); $response_code = (int) wp_remote_retrieve_response_code( $response ); $this->request_cache[ $cache_key ]['raw'] = $response; if ( is_wp_error( $response ) || $valid_response_code !== $response_code ) { throw new \Exception( "Rest Client Error: response code {$response_code}." ); } $response_body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( ! is_array( $response_body ) ) { throw new \Exception( 'Rest Client Error: unexpected response type.' ); } $return = [ 'code' => $response_code, 'body' => $response_body, ]; $this->request_cache[ $cache_key ]['parsed'] = $return; return $return; } } classes/honeypot-handler.php 0000644 00000005356 14720522625 0012210 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Classes; use Elementor\Widget_Base; use ElementorPro\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Honeypot field */ class Honeypot_Handler { public function add_field_type( $field_types ) { $field_types['honeypot'] = esc_html__( 'Honeypot', 'elementor-pro' ); return $field_types; } public function hide_label( $item, $item_index, $widget ) { if ( 'honeypot' === $item['field_type'] ) { $widget->set_render_attribute( 'field-group' . $item_index, 'class', 'elementor-field-type-text' ); $item['field_label'] = false; } return $item; } /** * @param string $item * @param integer $item_index * @param Widget_Base $widget */ public function render_field( $item, $item_index, $widget ) { $widget->set_render_attribute( 'input' . $item_index, 'type', 'text' ); $widget->add_render_attribute( 'input' . $item_index, 'style', 'display:none !important;' ); echo '<input size="1" '; $widget->print_render_attribute_string( 'input' . $item_index ); echo '>'; } /** * @param Form_Record $record * @param Ajax_Handler $ajax_handler */ public function validation( $record, $ajax_handler ) { $fields = $record->get_field( [ 'type' => 'honeypot', ] ); if ( empty( $fields ) ) { return; } foreach ( $fields as $field ) { if ( ! empty( $field['value'] ) ) { $ajax_handler->add_error( 'invalid_form', esc_html__( 'Invalid Form.', 'elementor-pro' ) ); } else { // If success - remove the field form list (don't send it in emails and etc ) $record->remove_field( $field['id'] ); } } } public function update_controls( Widget_Base $widget ) { $elementor = Plugin::elementor(); $control_data = $elementor->controls_manager->get_control_from_stack( $widget->get_unique_name(), 'form_fields' ); if ( is_wp_error( $control_data ) ) { return; } foreach ( $control_data['fields'] as $index => $field ) { if ( 'required' === $field['name'] || 'width' === $field['name'] ) { $control_data['fields'][ $index ]['conditions']['terms'][] = [ 'name' => 'field_type', 'operator' => '!in', 'value' => [ 'honeypot', ], ]; } } $widget->update_control( 'form_fields', $control_data ); } public function __construct() { add_filter( 'elementor_pro/forms/field_types', [ $this, 'add_field_type' ] ); add_action( 'elementor_pro/forms/render/item', [ $this, 'hide_label' ], 10, 3 ); add_action( 'elementor_pro/forms/render_field/honeypot', [ $this, 'render_field' ], 10, 3 ); add_action( 'elementor_pro/forms/validation', [ $this, 'validation' ], 10, 2 ); add_action( 'elementor/element/form/section_form_fields/before_section_end', [ $this, 'update_controls' ] ); } } classes/drip-handler.php 0000644 00000004017 14720522625 0011272 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Drip_Handler { private $rest_client = null; private $api_token = ''; /** * Drip_Handler constructor. * * @param $api_token * * @throws \Exception */ public function __construct( $api_token ) { if ( empty( $api_token ) ) { throw new \Exception( 'Invalid API key.' ); } $this->init_rest_client( $api_token ); if ( ! $this->is_valid_api_token() ) { throw new \Exception( 'Invalid API key.' ); } } private function init_rest_client( $api_token ) { $this->api_token = $api_token; $this->rest_client = new Rest_Client( 'https://api.getdrip.com/v2/' ); $this->rest_client->add_headers( [ 'Authorization' => 'Basic ' . base64_encode( $this->api_token ), 'Content-Type' => 'application/vnd.api+json', ] ); } /** * validate api token * * @return bool * @throws \Exception */ private function is_valid_api_token() { $accounts = $this->get_accounts(); if ( ! empty( $accounts ) ) { return true; } $this->api_token = ''; return false; } /** * get drip accounts associated with API token * @return array * @throws \Exception */ public function get_accounts() { $results = $this->rest_client->get( 'accounts' ); $accounts = [ '' => esc_html__( 'Select...', 'elementor-pro' ), ]; if ( ! empty( $results['body']['accounts'] ) ) { foreach ( $results['body']['accounts'] as $index => $account ) { $accounts[ $account['id'] ] = $account['name']; } } $return_array = [ 'accounts' => $accounts, ]; return $return_array; } /** * create subscriber at drip via api * * @param string $account_id * @param array $subscriber_data * * @return array|mixed * @throws \Exception */ public function create_subscriber( $account_id = '', $subscriber_data = [] ) { $end_point = sprintf( '%s/subscribers/', $account_id ); return $this->rest_client->post( $end_point, [ 'subscribers' => [ $subscriber_data ] ] ); } } classes/integration-base.php 0000644 00000004557 14720522625 0012165 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Classes; use Elementor\Controls_Manager; use Elementor\Repeater; use Elementor\Settings; use ElementorPro\Modules\Forms\Controls\Fields_Map; use ElementorPro\Modules\Forms\Widgets\Form; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Integration_Base extends Action_Base { public function handle_panel_request( array $data ) {} public static function global_api_control( $widget, $api_key = '', $label = '', $condition = [], $id = '' ) { if ( empty( $api_key ) ) { $content = sprintf( /* translators: 1: Integration label, 2: Link opening tag, 3: Link closing tag. */ esc_html__( 'Set your %1$s in the %2$sIntegrations Settings%3$s.', 'elementor-pro' ), $label, sprintf( '<a href="%s" target="_blank">', Settings::get_settings_tab_url( 'integrations' ) ), '</a>' ); $alert_type = 'warning'; } else { $content = sprintf( /* translators: 1: Integration label, 2: Link opening tag, 3: Link closing tag. */ esc_html__( 'You are using %1$s set in the %2$sIntegrations Settings%3$s.', 'elementor-pro' ), $label, sprintf( '<a href="%s" target="_blank">', Settings::get_settings_tab_url( 'integrations' ) ), '</a>' ); $alert_type = 'info'; } /* translators: %s: Integration label. */ $content .= ' ' . sprintf( esc_html__( 'You can also set a different %s by choosing "Custom".', 'elementor-pro' ), $label ); $widget->add_control( $id . '_api_key_msg', [ 'type' => Controls_Manager::ALERT, 'alert_type' => $alert_type, 'content' => $content, 'condition' => $condition, ] ); } protected function get_fields_map_control_options() { return []; } final protected function register_fields_map_control( Form $form ) { $repeater = new Repeater(); $repeater->add_control( 'remote_id', [ 'type' => Controls_Manager::HIDDEN ] ); $repeater->add_control( 'local_id', [ 'type' => Controls_Manager::SELECT ] ); $fields_map_control_options = [ 'label' => esc_html__( 'Field Mapping', 'elementor-pro' ), 'type' => Fields_Map::CONTROL_TYPE, 'separator' => 'before', 'fields' => $repeater->get_controls(), ]; $fields_map_control_options = array_merge( $fields_map_control_options, $this->get_fields_map_control_options() ); $form->add_control( $this->get_name() . '_fields_map', $fields_map_control_options ); } } classes/ajax-handler.php 0000644 00000021744 14720522625 0011265 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Classes; use ElementorPro\Core\Utils; use ElementorPro\Modules\Forms\Module; use ElementorPro\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Ajax_Handler { public $is_success = true; public $messages = [ 'success' => [], 'error' => [], 'admin_error' => [], ]; public $data = []; public $errors = []; private $current_form; const SUCCESS = 'success'; const ERROR = 'error'; const FIELD_REQUIRED = 'required_field'; const INVALID_FORM = 'invalid_form'; const SERVER_ERROR = 'server_error'; const SUBSCRIBER_ALREADY_EXISTS = 'subscriber_already_exists'; public static function is_form_submitted() { // PHPCS - No nonce is required, all visitors may send the form. return wp_doing_ajax() && isset( $_POST['action'] ) && 'elementor_pro_forms_send_form' === $_POST['action']; // phpcs:ignore WordPress.Security.NonceVerification.Missing } public static function get_default_messages() { return [ self::SUCCESS => esc_html__( 'Your submission was successful.', 'elementor-pro' ), self::ERROR => esc_html__( 'Your submission failed because of an error.', 'elementor-pro' ), self::FIELD_REQUIRED => esc_html__( 'This field is required.', 'elementor-pro' ), self::INVALID_FORM => esc_html__( 'Your submission failed because the form is invalid.', 'elementor-pro' ), self::SERVER_ERROR => esc_html__( 'Your submission failed because of a server error.', 'elementor-pro' ), self::SUBSCRIBER_ALREADY_EXISTS => esc_html__( 'Subscriber already exists.', 'elementor-pro' ), ]; } public static function get_default_message( $id, $settings ) { if ( ! empty( $settings['custom_messages'] ) ) { $field_id = $id . '_message'; if ( isset( $settings[ $field_id ] ) ) { return $settings[ $field_id ]; } } $default_messages = self::get_default_messages(); return isset( $default_messages[ $id ] ) ? $default_messages[ $id ] : esc_html__( 'Unknown error.', 'elementor-pro' ); } public function ajax_send_form() { $post_data = $_POST; // phpcs:ignore WordPress.Security.NonceVerification.Missing // $post_id that holds the form settings. $post_id = $post_data['post_id']; // $queried_id the post for dynamic values data. if ( isset( $post_data['queried_id'] ) ) { $queried_id = $post_data['queried_id']; } else { $queried_id = $post_id; } // Make the post as global post for dynamic values. Plugin::elementor()->db->switch_to_post( $queried_id ); $form_id = $post_data['form_id']; $elementor = Plugin::elementor(); $document = $elementor->documents->get( $post_id ); $form = null; $template_id = null; if ( $document ) { $form = Module::find_element_recursive( $document->get_elements_data(), $form_id ); } if ( ! empty( $form['templateID'] ) ) { $template = Plugin::elementor()->documents->get( $form['templateID'] ); if ( ! $template ) { return false; } $template_id = $template->get_id(); $form = $template->get_elements_data()[0]; } if ( empty( $form ) ) { $this ->add_error_message( self::get_default_message( self::INVALID_FORM, [] ) ) ->send(); } // restore default values $widget = $elementor->elements_manager->create_element_instance( $form ); $form['settings'] = $widget->get_settings_for_display(); $form['settings']['id'] = $form_id; $form['settings']['form_post_id'] = $template_id ? $template_id : $post_id; // TODO: Should be removed if there is an ability to edit "global widgets" $form['settings']['edit_post_id'] = $post_id; $this->current_form = $form; if ( empty( $form['settings']['form_fields'] ) ) { $this ->add_error_message( self::get_default_message( self::INVALID_FORM, $form['settings'] ) ) ->send(); } $record = new Form_Record( $post_data['form_fields'], $form ); if ( ! $record->validate( $this ) ) { $this ->add_error( $record->get( 'errors' ) ) ->add_error_message( self::get_default_message( self::ERROR, $form['settings'] ) ) ->send(); } $record->process_fields( $this ); //check for process errors if ( ! empty( $this->errors ) ) { $this->send(); } $module = Module::instance(); $actions = $module->actions_registrar->get(); $errors = array_merge( $this->messages['error'], $this->messages['admin_error'] ); /** * Filters the record before it sent to actions after submit. * * @since 3.3.0 * * @param Form_Record $record The form record. * @param Ajax_Handler $this The class that handle the submission of the record */ $record = apply_filters( 'elementor_pro/forms/record/actions_before', $record, $this ); foreach ( $actions as $action ) { if ( ! in_array( $action->get_name(), $form['settings']['submit_actions'], true ) ) { continue; } $exception = null; try { $action->run( $record, $this ); $this->handle_bc_errors( $errors ); } catch ( \Exception $e ) { $exception = $e; // Add an admin error. if ( ! in_array( $exception->getMessage(), $this->messages['admin_error'], true ) ) { $this->add_admin_error_message( "{$action->get_label()} {$exception->getMessage()}" ); } // Add a user error. $this->add_error_message( $this->get_default_message( self::ERROR, $this->current_form['settings'] ) ); } $errors = array_merge( $this->messages['error'], $this->messages['admin_error'] ); /** * After form actions run. * * Fires after Elementor forms run actions. This hook allows * developers to add functionality after certain actions run. * * @param Action_Base $action An instance of form action. * @param \Exception|null $exception An instance of the exception. */ do_action( 'elementor_pro/forms/actions/after_run', $action, $exception ); } $activity_log = $module->get_component( 'activity_log' ); if ( $activity_log ) { $activity_log->run( $record, $this ); } $cf7db = $module->get_component( 'cf7db' ); if ( $cf7db ) { $cf7db->run( $record, $this ); } /** * New Elementor form record. * * Fires before a new form record is sent by ajax. This hook allows * developers to add functionality before a new form record is sent. * * @since 1.0.0 * * @param Form_Record $record An instance of the form record. * @param Ajax_Handler $this An instance of the ajax handler. */ do_action( 'elementor_pro/forms/new_record', $record, $this ); $this->send(); } public function add_success_message( $message ) { $this->messages['success'][] = $message; return $this; } public function add_response_data( $key, $data ) { $this->data[ $key ] = $data; return $this; } public function add_error_message( $message ) { $this->messages['error'][] = $message; $this->set_success( false ); return $this; } public function add_error( $field, $message = '' ) { if ( is_array( $field ) ) { $this->errors += $field; } else { $this->errors[ $field ] = $message; } $this->set_success( false ); return $this; } public function add_admin_error_message( $message ) { $this->messages['admin_error'][] = $message; $this->set_success( false ); return $this; } public function set_success( $bool ) { $this->is_success = $bool; return $this; } public function send() { if ( $this->is_success ) { wp_send_json_success( [ 'message' => $this->get_default_message( self::SUCCESS, $this->current_form['settings'] ), 'data' => $this->data, ] ); } if ( empty( $this->messages['error'] ) && ! empty( $this->errors ) ) { $this->add_error_message( $this->get_default_message( self::INVALID_FORM, $this->current_form['settings'] ) ); } $post_id = Utils::_unstable_get_super_global_value( $_POST, 'post_id' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing $error_msg = implode( '<br>', $this->messages['error'] ); if ( current_user_can( 'edit_post', $post_id ) && ! empty( $this->messages['admin_error'] ) ) { $this->add_admin_error_message( esc_html__( 'This message is not visible to site visitors.', 'elementor-pro' ) ); $error_msg .= '<div class="elementor-forms-admin-errors">' . implode( '<br>', $this->messages['admin_error'] ) . '</div>'; } wp_send_json_error( [ 'message' => $error_msg, 'errors' => $this->errors, 'data' => $this->data, ] ); } public function get_current_form() { return $this->current_form; } /** * BC: checks if the current action add some errors to the errors array * if it add an error the "run" method treat it as a failed action. * * @param $errors * * @throws \Exception */ private function handle_bc_errors( $errors ) { $current_errors = array_merge( $this->messages['error'], $this->messages['admin_error'] ); $errors_diff = array_diff( $current_errors, $errors ); if ( count( $errors_diff ) > 0 ) { throw new \Exception( implode( ', ', $errors_diff ) ); } } public function __construct() { add_action( 'wp_ajax_elementor_pro_forms_send_form', [ $this, 'ajax_send_form' ] ); add_action( 'wp_ajax_nopriv_elementor_pro_forms_send_form', [ $this, 'ajax_send_form' ] ); } } classes/activecampaign-handler.php 0000644 00000006105 14720522625 0013307 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Activecampaign_Handler { private $rest_client = null; private $api_key = ''; public function __construct( $api_key, $base_url ) { if ( empty( $api_key ) ) { throw new \Exception( 'Invalid API key.' ); } if ( empty( $base_url ) ) { throw new \Exception( 'Invalid API URL.' ); } $this->init_rest_client( $api_key, $base_url ); if ( ! $this->is_valid_api_key() ) { throw new \Exception( 'Invalid API key or URL.' ); } } private function init_rest_client( $api_key, $base_url ) { $this->api_key = $api_key; $this->rest_client = new Rest_Client( trailingslashit( $base_url ) . 'admin/api.php' ); } /** * validate api key * * @return bool * @throws \Exception */ private function is_valid_api_key() { $lists = $this->get_lists(); if ( ! empty( $lists ) ) { return true; } $this->api_key = ''; return false; } /** * get ActiveCampaign lists associated with API key * @return array * @throws \Exception */ public function get_lists() { $results = $this->rest_client->get( '?api_action=list_list', [ 'api_key' => $this->api_key, 'ids' => 'all', 'api_output' => 'json', ] ); $lists = [ '' => esc_html__( 'Select...', 'elementor-pro' ), ]; if ( ! empty( $results['body'] ) ) { foreach ( $results['body'] as $index => $list ) { if ( ! is_array( $list ) ) { continue; } $lists[ $list['id'] ] = $list['name']; } } $return_array = [ 'lists' => $lists, 'fields' => $this->get_fields(), ]; return $return_array; } /** * get ActiveCampaign custom fields associated with API key * @return array * @throws \Exception */ private function get_fields() { $results = $this->rest_client->get( '?api_action=list_field_view', [ 'api_key' => $this->api_key, 'ids' => 'all', 'api_output' => 'json', ] ); $fields = []; if ( ! empty( $results['body'] ) ) { foreach ( $results['body'] as $index => $field ) { if ( ! is_array( $field ) ) { continue; } $fields[] = [ 'remote_label' => $field['title'], 'remote_type' => $this->normalize_type( $field['type'] ), 'remote_id' => 'field[' . $field['id'] . ',0]', 'remote_required' => (bool) $field['isrequired'], ]; } } return $fields; } private function normalize_type( $type ) { static $types = [ 'text' => 'text', 'number' => 'number', 'address' => 'text', 'phone' => 'text', 'date' => 'text', 'url' => 'url', 'imageurl' => 'url', 'radio' => 'radio', 'dropdown' => 'select', 'birthday' => 'text', 'zip' => 'text', ]; return $types[ $type ]; } /** * create contact at Activecampaign via api * * @param array $subscriber_data * * @return array|mixed * @throws \Exception */ public function create_subscriber( $subscriber_data = [] ) { $end_point = '?api_action=contact_sync&api_key=' . $this->api_key . '&api_output=json'; return $this->rest_client->request( 'POST', $end_point, $subscriber_data ); } } classes/form-base.php 0000644 00000020331 14720522625 0010571 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Classes; use Elementor\Utils; use ElementorPro\Base\Base_Widget; use ElementorPro\Modules\Forms\Module; use Elementor\Icons_Manager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class Form_Base extends Base_Widget { public function on_export( $element ) { /** @var \ElementorPro\Modules\Forms\Classes\Action_Base[] $actions */ $actions = Module::instance()->actions_registrar->get(); foreach ( $actions as $action ) { $new_element_data = $action->on_export( $element ); if ( null !== $new_element_data ) { $element = $new_element_data; } } return $element; } public static function get_button_sizes() { return [ 'xs' => esc_html__( 'Extra Small', 'elementor-pro' ), 'sm' => esc_html__( 'Small', 'elementor-pro' ), 'md' => esc_html__( 'Medium', 'elementor-pro' ), 'lg' => esc_html__( 'Large', 'elementor-pro' ), 'xl' => esc_html__( 'Extra Large', 'elementor-pro' ), ]; } protected function make_textarea_field( $item, $item_index ) { $this->add_render_attribute( 'textarea' . $item_index, [ 'class' => [ 'elementor-field-textual', 'elementor-field', esc_attr( $item['css_classes'] ), 'elementor-size-' . $item['input_size'], ], 'name' => $this->get_attribute_name( $item ), 'id' => $this->get_attribute_id( $item ), 'rows' => $item['rows'], ] ); if ( $item['placeholder'] ) { $this->add_render_attribute( 'textarea' . $item_index, 'placeholder', $item['placeholder'] ); } if ( $item['required'] ) { $this->add_required_attribute( 'textarea' . $item_index ); } $value = empty( $item['field_value'] ) ? '' : $item['field_value']; return '<textarea ' . $this->get_render_attribute_string( 'textarea' . $item_index ) . '>' . $value . '</textarea>'; } protected function make_select_field( $item, $i ) { $this->add_render_attribute( [ 'select-wrapper' . $i => [ 'class' => [ 'elementor-field', 'elementor-select-wrapper', 'remove-before', esc_attr( $item['css_classes'] ), ], ], 'select' . $i => [ 'name' => $this->get_attribute_name( $item ) . ( ! empty( $item['allow_multiple'] ) ? '[]' : '' ), 'id' => $this->get_attribute_id( $item ), 'class' => [ 'elementor-field-textual', 'elementor-size-' . $item['input_size'], ], ], ] ); if ( $item['required'] ) { $this->add_required_attribute( 'select' . $i ); } if ( $item['allow_multiple'] ) { $this->add_render_attribute( 'select' . $i, 'multiple' ); if ( ! empty( $item['select_size'] ) ) { $this->add_render_attribute( 'select' . $i, 'size', $item['select_size'] ); } } $options = preg_split( "/\\r\\n|\\r|\\n/", $item['field_options'] ); if ( ! $options ) { return ''; } ob_start(); ?> <div <?php $this->print_render_attribute_string( 'select-wrapper' . $i ); ?>> <div class="select-caret-down-wrapper"> <?php if ( ! $item['allow_multiple'] ) { $icon = [ 'library' => 'eicons', 'value' => 'eicon-caret-down', 'position' => 'right', ]; Icons_Manager::render_icon( $icon, [ 'aria-hidden' => 'true' ] ); } ?> </div> <select <?php $this->print_render_attribute_string( 'select' . $i ); ?>> <?php foreach ( $options as $key => $option ) { $option_id = esc_attr( $item['custom_id'] . $key ); $option_value = esc_attr( $option ); $option_label = esc_html( $option ); if ( false !== strpos( $option, '|' ) ) { list( $label, $value ) = explode( '|', $option ); $option_value = esc_attr( $value ); $option_label = esc_html( $label ); } $this->add_render_attribute( $option_id, 'value', $option_value ); // Support multiple selected values if ( ! empty( $item['field_value'] ) && in_array( $option_value, explode( ',', $item['field_value'] ) ) ) { $this->add_render_attribute( $option_id, 'selected', 'selected' ); } ?> <option <?php $this->print_render_attribute_string( $option_id ); ?>><?php // PHPCS - $option_label is already escaped echo $option_label; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></option> <?php } ?> </select> </div> <?php $select = ob_get_clean(); return $select; } protected function make_radio_checkbox_field( $item, $item_index, $type ) { $options = preg_split( "/\\r\\n|\\r|\\n/", $item['field_options'] ); $html = ''; if ( $options ) { $html .= '<div class="elementor-field-subgroup ' . esc_attr( $item['css_classes'] ) . ' ' . esc_attr( $item['inline_list'] ) . '">'; foreach ( $options as $key => $option ) { $element_id = esc_attr( $item['custom_id'] ) . $key; $html_id = $this->get_attribute_id( $item ) . '-' . $key; $option_label = $option; $option_value = $option; if ( false !== strpos( $option, '|' ) ) { list( $option_label, $option_value ) = explode( '|', $option ); } $this->add_render_attribute( $element_id, [ 'type' => $type, 'value' => $option_value, 'id' => $html_id, 'name' => $this->get_attribute_name( $item ) . ( ( 'checkbox' === $type && count( $options ) > 1 ) ? '[]' : '' ), ] ); if ( ! empty( $item['field_value'] ) && $option_value === $item['field_value'] ) { $this->add_render_attribute( $element_id, 'checked', 'checked' ); } if ( $item['required'] && 'radio' === $type ) { $this->add_required_attribute( $element_id ); } $html .= '<span class="elementor-field-option"><input ' . $this->get_render_attribute_string( $element_id ) . '> <label for="' . $html_id . '">' . $option_label . '</label></span>'; } $html .= '</div>'; } return $html; } protected function form_fields_render_attributes( $i, $instance, $item ) { $this->add_render_attribute( [ 'field-group' . $i => [ 'class' => [ 'elementor-field-type-' . $item['field_type'], 'elementor-field-group', 'elementor-column', 'elementor-field-group-' . $item['custom_id'], ], ], 'input' . $i => [ 'type' => $item['field_type'], 'name' => $this->get_attribute_name( $item ), 'id' => $this->get_attribute_id( $item ), 'class' => [ 'elementor-field', 'elementor-size-' . $item['input_size'], empty( $item['css_classes'] ) ? '' : esc_attr( $item['css_classes'] ), ], ], 'label' . $i => [ 'for' => $this->get_attribute_id( $item ), 'class' => 'elementor-field-label', ], ] ); if ( empty( $item['width'] ) ) { $item['width'] = '100'; } $this->add_render_attribute( 'field-group' . $i, 'class', 'elementor-col-' . $item['width'] ); if ( ! empty( $item['width_tablet'] ) ) { $this->add_render_attribute( 'field-group' . $i, 'class', 'elementor-md-' . $item['width_tablet'] ); } if ( $item['allow_multiple'] ) { $this->add_render_attribute( 'field-group' . $i, 'class', 'elementor-field-type-' . $item['field_type'] . '-multiple' ); } if ( ! empty( $item['width_mobile'] ) ) { $this->add_render_attribute( 'field-group' . $i, 'class', 'elementor-sm-' . $item['width_mobile'] ); } // Allow zero as placeholder. if ( ! Utils::is_empty( $item['placeholder'] ) ) { $this->add_render_attribute( 'input' . $i, 'placeholder', $item['placeholder'] ); } if ( ! empty( $item['field_value'] ) ) { $this->add_render_attribute( 'input' . $i, 'value', $item['field_value'] ); } if ( ! $instance['show_labels'] ) { $this->add_render_attribute( 'label' . $i, 'class', 'elementor-screen-only' ); } if ( ! empty( $item['required'] ) ) { $class = 'elementor-field-required'; if ( ! empty( $instance['mark_required'] ) ) { $class .= ' elementor-mark-required'; } $this->add_render_attribute( 'field-group' . $i, 'class', $class ); $this->add_required_attribute( 'input' . $i ); } } public function render_plain_content() {} public function get_attribute_name( $item ) { return "form_fields[{$item['custom_id']}]"; } public function get_attribute_id( $item ) { return 'form-field-' . esc_attr( $item['custom_id'] ); } private function add_required_attribute( $element ) { $this->add_render_attribute( $element, 'required', 'required' ); $this->add_render_attribute( $element, 'aria-required', 'true' ); } } classes/form-record.php 0000644 00000023074 14720522625 0011144 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Classes; use ElementorPro\Core\Utils; use ElementorPro\Modules\Forms\Fields\Upload; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Form_Record { protected $sent_data; protected $fields; protected $form_type; protected $form_settings; protected $files = []; protected $meta = []; public function get_formatted_data( $with_meta = false ) { $formatted = []; $no_label = esc_html__( 'No Label', 'elementor-pro' ); $fields = $this->fields; if ( $with_meta ) { $fields = array_merge( $fields, $this->meta ); } foreach ( $fields as $key => $field ) { if ( empty( $field['title'] ) ) { $formatted[ $no_label . ' ' . $key ] = $field['value']; } else { $formatted[ $field['title'] ] = $field['value']; } } return $formatted; } /** * @param Ajax_Handler $ajax_handler An instance of the ajax handler. * * @return bool */ public function validate( $ajax_handler ) { foreach ( $this->fields as $id => $field ) { $field_type = $field['type']; if ( ! empty( $field['required'] ) && '' === $field['value'] && 'upload' !== $field_type ) { $ajax_handler->add_error( $id, Ajax_Handler::get_default_message( Ajax_Handler::FIELD_REQUIRED, $this->form_settings ) ); } /** * Elementor form field validation. * * Fires when a single form field is being validated. This hook allows developers * to validate individual field types. * * The dynamic portion of the hook name, `$field_type`, refers to the field type. * * @since 2.0.0 * * @param array $field Form field. * @param Form_Record $this An instance of the form record. * @param Ajax_Handler $ajax_handler An instance of the ajax handler. */ do_action( "elementor_pro/forms/validation/{$field_type}", $field, $this, $ajax_handler ); } /** * Elementor form validation. * * Fires when form fields are being validated. This hook allows developers * to validate all form fields. * * @since 2.0.0 * * @param Form_Record $this An instance of the form record. * @param Ajax_Handler $ajax_handler An instance of the ajax handler. */ do_action( 'elementor_pro/forms/validation', $this, $ajax_handler ); return empty( $ajax_handler->errors ); } /** * @param Ajax_Handler $ajax_handler An instance of the ajax handler. * */ public function process_fields( $ajax_handler ) { foreach ( $this->fields as $id => $field ) { $field_type = $field['type']; /** * Elementor form field process. * * Fires when a single form field is being processed. This hook allows developers * to process individual field types. * * The dynamic portion of the hook name, `$field_type`, refers to the field type. * * @since 2.0.0 * * @param array $field Form field. * @param Form_Record $this An instance of the form record. * @param Ajax_Handler $ajax_handler An instance of the ajax handler. */ do_action( "elementor_pro/forms/process/{$field_type}", $field, $this, $ajax_handler ); } /** * Elementor form process. * * Fires when form fields are being processed. This hook allows developers * to process all form fields. * * @since 2.0.0 * * @param Form_Record $this An instance of the form record. * @param Ajax_Handler $ajax_handler An instance of the ajax handler. */ do_action( 'elementor_pro/forms/process', $this, $ajax_handler ); } public function get( $property ) { if ( isset( $this->{$property} ) ) { return $this->{$property}; } return null; } public function set( $property, $value ) { $this->{$property} = $value; } public function get_form_settings( $setting ) { if ( isset( $this->form_settings[ $setting ] ) ) { return $this->form_settings[ $setting ]; } return null; } public function get_field( $args ) { return wp_list_filter( $this->fields, $args ); } public function remove_field( $id ) { unset( $this->fields[ $id ] ); } public function update_field( $field_id, $property, $value ) { if ( ! isset( $this->fields[ $field_id ] ) || ! isset( $this->fields[ $field_id ][ $property ] ) ) { return; } $this->fields[ $field_id ][ $property ] = $value; } public function get_form_meta( $meta_keys = [] ) { $result = []; foreach ( $meta_keys as $metadata_type ) { switch ( $metadata_type ) { case 'date': $result['date'] = [ 'title' => esc_html__( 'Date', 'elementor-pro' ), 'value' => date_i18n( get_option( 'date_format' ) ), ]; break; case 'time': $result['time'] = [ 'title' => esc_html__( 'Time', 'elementor-pro' ), 'value' => date_i18n( get_option( 'time_format' ) ), ]; break; case 'page_url': $result['page_url'] = [ 'title' => esc_html__( 'Page URL', 'elementor-pro' ), 'value' => isset( $_POST['referrer'] ) ? esc_url_raw( wp_unslash( $_POST['referrer'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Missing ]; break; case 'page_title': $result['page_title'] = [ 'title' => esc_html__( 'Page Title', 'elementor-pro' ), 'value' => isset( $_POST['referer_title'] ) ? sanitize_text_field( wp_unslash( $_POST['referer_title'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Missing ]; break; case 'user_agent': $result['user_agent'] = [ 'title' => esc_html__( 'User Agent', 'elementor-pro' ), 'value' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_textarea_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '', ]; break; case 'remote_ip': $result['remote_ip'] = [ 'title' => esc_html__( 'Remote IP', 'elementor-pro' ), 'value' => Utils::get_client_ip(), ]; break; case 'credit': $result['credit'] = [ 'title' => esc_html__( 'Powered by', 'elementor-pro' ), 'value' => esc_html__( 'Elementor', 'elementor-pro' ), ]; break; } } return $result; } private function set_meta() { $form_metadata = $this->form_settings['form_metadata']; if ( empty( $form_metadata ) ) { return; } $this->meta = $this->get_form_meta( $form_metadata ); } private function set_fields() { foreach ( $this->form_settings['form_fields'] as $form_field ) { $field = [ 'id' => $form_field['custom_id'], 'type' => $form_field['field_type'], 'title' => $form_field['field_label'], 'value' => '', 'raw_value' => '', 'required' => ! empty( $form_field['required'] ), ]; if ( 'upload' === $field['type'] ) { $field['file_sizes'] = $form_field['file_sizes'] ?? ''; $field['file_types'] = $form_field['file_types'] ?? ''; $field['max_files'] = $form_field['max_files'] ?? ''; $field['attachment_type'] = $form_field['attachment_type'] ?? ''; } if ( isset( $this->sent_data[ $form_field['custom_id'] ] ) ) { $field['raw_value'] = $this->sent_data[ $form_field['custom_id'] ]; $value = $field['raw_value']; if ( is_array( $value ) ) { $value = implode( ', ', $value ); } $field['value'] = $this->sanitize_field( $field, $value ); } $this->fields[ $form_field['custom_id'] ] = $field; } } private function sanitize_field( $field, $value ) { $field_type = $field['type']; switch ( $field_type ) { case 'text': case 'password': case 'hidden': case 'search': case 'checkbox': case 'radio': case 'select': $value = sanitize_text_field( $value ); break; case 'url': $value = esc_url_raw( $value ); break; case 'textarea': $value = sanitize_textarea_field( $value ); break; case 'email': $value = sanitize_email( $value ); break; default: /** * Sanitize field value. * * Filters the value of the form field for sanitization purpose. This hook allows * developers to add custom sanitization for field values. * * The dynamic portion of the hook name, `$field_type`, refers to the field type. * * @since 1.0.0 * * @param string $value The field value. * @param array $field The field array. */ $value = apply_filters( "elementor_pro/forms/sanitize/{$field_type}", $value, $field ); } return $value; } public function replace_setting_shortcodes( $setting, $urlencode = false ) { // Shortcode can be `[field id="fds21fd"]` or `[field title="Email" id="fds21fd"]`, multiple shortcodes are allowed return preg_replace_callback( '/(\[field[^]]*id="(\w+)"[^]]*\])/', function( $matches ) use ( $urlencode ) { $value = ''; if ( isset( $this->fields[ $matches[2] ] ) ) { $value = $this->fields[ $matches[2] ]['value']; } if ( $urlencode ) { $value = urlencode( $value ); } return $value; }, $setting ); } public function add_file( $id, $index, $filename ) { if ( ! isset( $this->files[ $id ] ) || ! is_array( $this->files[ $id ] ) ) { $this->files[ $id ] = [ 'url' => [], 'path' => [], ]; } $attachment_type = $this->fields[ $id ]['attachment_type']; $this->files[ $id ]['url'][ $index ] = Upload::MODE_ATTACH === $attachment_type ? 'attached' : $filename['url']; $this->files[ $id ]['path'][ $index ] = $filename['path']; } public function has_field_type( $type ) { foreach ( $this->fields as $id => $field ) { if ( $type === $field['field_type'] ) { return true; } } return false; } public function __construct( $sent_data, $form ) { $this->form_type = $form['widgetType']; $this->form_settings = $form['settings']; $this->sent_data = stripslashes_deep( $sent_data ); $this->set_fields(); $this->set_meta(); } } data/controller.php 0000644 00000000000 14720522625 0010344 0 ustar 00 registrars/form-actions-registrar.php 0000644 00000004650 14720522625 0014055 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Registrars; use ElementorPro\Core\Utils\Registrar; use ElementorPro\Modules\Forms\Actions; use ElementorPro\Plugin; use ElementorPro\License\API; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Basic form actions registration manager. */ class Form_Actions_Registrar extends Registrar { const FEATURE_NAME_CLASS_NAME_MAP = [ 'email' => 'Email', 'email2' => 'Email2', 'redirect' => 'Redirect', 'webhook' => 'Webhook', 'mailchimp' => 'Mailchimp', 'drip' => 'Drip', 'activecampaign' => 'Activecampaign', 'getresponse' => 'Getresponse', 'convertkit' => 'Convertkit', 'mailerlite' => 'Mailerlite', 'slack' => 'Slack', 'discord' => 'Discord', ]; /** * Form_Actions_Registrar constructor. * * @return void */ public function __construct() { parent::__construct(); $this->init(); } /** * Initialize the default fields. * * @return void */ public function init() { // Register the actions handlers using a hook since some actions need to be registered before those actions (e.g: save-to-database). add_action( 'elementor_pro/forms/actions/register', function ( Form_Actions_Registrar $actions_registrar ) { $form_actions = API::filter_active_features( static::FEATURE_NAME_CLASS_NAME_MAP ); foreach ( $form_actions as $action ) { $class_name = 'ElementorPro\Modules\Forms\Actions\\' . $action; $actions_registrar->register( new $class_name() ); } } ); /** * Deprecated actions registration hook. * * @deprecated 3.5.0 Use `elementor_pro/forms/actions/register` instead. */ Plugin::elementor()->modules_manager->get_modules( 'dev-tools' )->deprecation->do_deprecated_action( 'elementor_pro/forms/register_action', [ $this ], '3.5.0', 'elementor_pro/forms/actions/register' ); /** * Elementor Pro form actions registration. * * Fires when a new form action is registered. This hook allows developers to * register new form actions. * * @since 3.5.0 * * @param Form_Actions_Registrar $this An instance of form actions registration * manager. */ do_action( 'elementor_pro/forms/actions/register', $this ); // MailPoet if ( class_exists( '\WYSIJA' ) ) { $this->register( new Actions\Mailpoet() ); } // MailPoet if ( class_exists( '\MailPoet\API\API' ) ) { $this->register( new Actions\Mailpoet3() ); } } } registrars/form-fields-registrar.php 0000644 00000002325 14720522625 0013660 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Registrars; use ElementorPro\Core\Utils\Registrar; use ElementorPro\Modules\Forms\Fields; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Basic form fields registration manager. */ class Form_Fields_Registrar extends Registrar { /** * Form_Fields_Registrar constructor. * * @return void */ public function __construct() { parent::__construct(); $this->init(); } /** * Initialize the default fields. * * @return void */ public function init() { $this->register( new Fields\Time() ); $this->register( new Fields\Date() ); $this->register( new Fields\Tel() ); $this->register( new Fields\Number() ); $this->register( new Fields\Acceptance() ); $this->register( new Fields\Upload() ); $this->register( new Fields\Step() ); /** * Elementor Pro form fields registration. * * Fires when a new form field is registered. This hook allows developers to * register new form fields. * * @since 3.5.0 * * @param Form_Actions_Registrar $this An instance of form fields registration * manager. */ do_action( 'elementor_pro/forms/fields/register', $this ); } } submissions/data/controller.php 0000644 00000021120 14720522625 0012730 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\Data; use Elementor\Core\Utils\Collection; use Elementor\Data\Base\Controller as Controller_Base; use ElementorPro\Modules\Forms\Submissions\Database\Query; use ElementorPro\Modules\Forms\Submissions\Data\Responses\Query_Failed_Response; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Controller extends Controller_Base { /** * @var \ElementorPro\Modules\Forms\Submissions\Database\Query */ private $query; public function get_name() { return 'form-submissions'; } /** * Get collection params by 'additionalProperties' context. * * TODO Should move to base after merge with 'Sub controllers & Sub endpoints'. * * @param string $context * * @return array */ private function get_collection_params_by_additional_props_context( $context ) { $result = []; $collection_params = $this->get_collection_params(); foreach ( $collection_params as $collection_param_key => $collection_param ) { if ( isset( $collection_param['additionalProperties']['context'] ) && $context === $collection_param['additionalProperties']['context'] ) { $result[ $collection_param_key ] = $collection_param; } } return $result; } public function get_collection_params() { $default_collection_params = parent::get_collection_params(); return array_merge( $default_collection_params, [ 'page' => [ 'description' => 'Current page of the collection.', 'type' => 'integer', 'default' => 1, 'minimum' => 1, 'required' => false, ], 'per_page' => [ 'description' => 'Maximum number of items to be returned in result set.', 'type' => 'integer', 'default' => 50, 'minimum' => 1, 'maximum' => 100, 'required' => false, ], 'order' => [ 'description' => 'Order sort attribute ascending or descending.', 'type' => 'string', 'default' => 'desc', 'enum' => [ 'asc', 'desc', ], 'required' => false, ], 'order_by' => [ 'description' => 'Sort collection by object attribute.', 'type' => 'string', 'default' => 'created_at', 'enum' => [ 'created_at', 'id', 'main_meta_id', ], 'required' => false, ], 'status' => [ 'description' => 'Limit result set to submissions assigned one or more statuses.', 'type' => 'string', 'default' => 'all', 'enum' => [ 'all', 'unread', 'read', 'trash', ], 'additionalProperties' => [ 'context' => 'filter', ], 'required' => false, ], 'search' => [ 'description' => 'Limit results to those matching a string.', 'type' => 'string', 'required' => false, 'additionalProperties' => [ 'context' => 'filter', ], ], 'form' => [ 'description' => 'Limit result set to submissions assigned to specific forms. The form id should follow this pattern {post_id}_{element_id} e.g: 10_476d0ce', 'type' => 'string', 'required' => false, 'additionalProperties' => [ 'context' => 'filter', ], ], 'referer' => [ 'description' => 'Limit result set to submissions assigned to specific referer.', 'type' => 'string', 'required' => false, 'additionalProperties' => [ 'context' => 'filter', ], ], 'after' => [ 'description' => 'Limit response to submissions sent after a given ISO8601 compliant date.', 'type' => 'string', 'format' => 'date', 'required' => false, 'additionalProperties' => [ 'context' => 'filter', ], ], 'before' => [ 'description' => 'Limit response to submissions sent before a given ISO8601 compliant date.', 'type' => 'string', 'format' => 'date', 'required' => false, 'additionalProperties' => [ 'context' => 'filter', ], ], ] ); } public function get_items( $request ) { $filters = []; // Get & set filters with values. foreach ( $this->get_collection_params_by_additional_props_context( 'filter' ) as $collection_param_name => $collection_param_values ) { $request_filter_value = $request->get_param( $collection_param_name ); if ( null !== $request_filter_value ) { $collection_param_values['value'] = $request_filter_value; $filters[ $collection_param_name ] = $collection_param_values; } } $result = $this->query->get_submissions( [ 'page' => $request->get_param( 'page' ), 'per_page' => $request->get_param( 'per_page' ), 'filters' => $filters, 'order' => [ 'order' => $request->get_param( 'order' ), 'by' => $request->get_param( 'order_by' ), ], ] ); $result['meta']['count'] = $this->query ->count_submissions_by_status( $filters ) ->all(); return $result; } public function get_item( $request ) { return $this->query->get_submission( (int) $request->get_param( 'id' ) ); } /** * @param \WP_REST_Request $request * * @return \WP_Error|\WP_REST_Response */ public function delete_items( $request ) { return $this->delete( $request->get_param( 'ids' ), $request->get_param( 'force' ) ); } /** * Delete single submission * * @param \WP_REST_Request $request * * @return \WP_Error|\WP_REST_Response */ public function delete_item( $request ) { return $this->delete( [ $request->get_param( 'id' ) ], $request->get_param( 'force' ) ); } /** * Update a single submission. * * @param \WP_REST_Request $request * * @return \WP_Error|\WP_REST_Response */ public function update_item( $request ) { return $this->update( [ (int) $request->get_param( 'id' ) ], $request ); } /** * Update multiple submissions. * * @param $request * * @return \WP_Error|\WP_REST_Response */ public function update_items( $request ) { return $this->update( $request->get_param( 'ids' ), $request ); } public function get_permission_callback( $request ) { return current_user_can( 'manage_options' ); } public function register_endpoints() { $this->register_endpoint( Endpoints\Restore::class ); $this->register_endpoint( Endpoints\Export::class ); $this->register_endpoint( Endpoints\Referer::class ); } protected function register_internal_endpoints() { // Register as internal to remove the default endpoint generated by the base controller. $this->register_endpoint( Endpoints\Index::class ); } protected function register() { parent::register(); $this->query = Query::get_instance(); } /** * Delete one or more submissions. * * @param array $ids * @param false $force * * @return Query_Failed_Response|\WP_Error|\WP_REST_Response */ private function delete( array $ids, $force = false ) { $affected = 0; $failed = 0; foreach ( $ids as $id ) { if ( $force ) { $affected_rows = $this->query->delete_submission( $id ); } else { $affected_rows = $this->query->move_to_trash_submission( $id ); } if ( false === $affected_rows ) { $failed++; } else { $affected += $affected_rows; } } if ( count( $ids ) === $failed ) { return new Query_Failed_Response( $this->query->get_last_error() ); } if ( 1 === count( $ids ) && 0 === $affected ) { return new \WP_Error( 'rest_not_found', __( 'Submission not found.', 'elementor-pro' ), [ 'status' => 404 ] ); } return new \WP_REST_Response( [ 'data' => [], 'meta' => [ 'affected' => $affected, 'failed' => $failed, ], ], 200 ); } /** * Update one or more submissions. * * @param array $ids * @param \WP_REST_Request $request * * @return Query_Failed_Response|\WP_Error|\WP_REST_Response */ private function update( array $ids, \WP_REST_Request $request ) { $allowed_args = ( new Collection( $request->get_attributes()['args'] ) ) ->except( [ 'id', 'ids', 'values' ] ) ->keys(); $data = ( new Collection( $request->get_body_params() ) ) ->only( $allowed_args ) ->all(); $values = $request->get_param( 'values' ); $affected = 0; $failed = 0; foreach ( $ids as $id ) { $affected_rows = $this->query->update_submission( $id, $data, $values ); if ( false === $affected_rows ) { $failed++; } else { $affected += $affected_rows; } } if ( count( $ids ) === $failed ) { return new Query_Failed_Response( $this->query->get_last_error() ); } if ( 1 === count( $ids ) ) { if ( 0 === $affected ) { return new \WP_Error( 'rest_not_found', __( 'Submission not found.', 'elementor-pro' ), [ 'status' => 404 ] ); } return new \WP_REST_Response( $this->query->get_submission( $ids[0] ) ); } return new \WP_REST_Response( [ 'data' => [], 'meta' => [ 'affected' => $affected, 'failed' => $failed, ], ], 200 ); } } submissions/data/endpoints/restore.php 0000644 00000005375 14720522625 0014251 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\Data\Endpoints; use ElementorPro\Modules\Forms\Submissions\Database\Query; use Elementor\Data\Base\Endpoint; use ElementorPro\Modules\Forms\Submissions\Data\Responses\Query_Failed_Response; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Restore extends Endpoint { /** * @var Query */ private $query; public function get_name() { return 'restore'; } /** * Restore a single trashed submission. * * @param string $id * @param \WP_REST_Request $request * * @return \WP_Error|\WP_REST_Response */ public function update_item( $id, $request ) { return $this->restore( [ $request->get_param( 'id' ) ] ); } /** * Restore multiple trashed submissions. * * @param \WP_REST_Request $request * * @return \WP_Error|\WP_REST_Response */ public function update_items( $request ) { return $this->restore( $request->get_param( 'ids' ) ); } protected function register() { $this->register_route( '/(?P<id>[\d]+)/', \WP_REST_Server::EDITABLE, function ( $request ) { return $this->base_callback( \WP_REST_Server::EDITABLE, $request ); }, [ 'id' => [ 'description' => 'Unique identifier for the object.', 'type' => 'string', 'required' => true, ], ] ); $this->register_route( '', \WP_REST_Server::EDITABLE, function ( $request ) { return $this->base_callback( \WP_REST_Server::EDITABLE, $request, true ); }, [ 'ids' => [ 'description' => 'Unique identifiers for the objects.', 'type' => 'array', 'items' => [ 'type' => 'integer', ], 'validate_callback' => function ( $param ) { return ! empty( $param ); }, 'required' => true, ], ] ); } /** * Restore on or more submissions. * * @param array $ids * * @return Query_Failed_Response|\WP_Error|\WP_REST_Response */ private function restore( array $ids ) { $affected = 0; $failed = 0; foreach ( $ids as $id ) { $affected_rows = $this->query->restore( $id ); if ( false === $affected_rows ) { $failed++; } else { $affected += $affected_rows; } } if ( count( $ids ) === $failed ) { return new Query_Failed_Response( $this->query->get_last_error() ); } if ( 1 === count( $ids ) && 0 === $affected ) { return new \WP_Error( 'rest_not_found', __( 'Submission not found or not in trash.', 'elementor-pro' ), [ 'status' => 404 ] ); } return new \WP_REST_Response( [ 'data' => [], 'meta' => [ 'affected' => $affected, 'failed' => $failed, ], ], 200 ); } public function __construct( $controller ) { $this->query = Query::get_instance(); parent::__construct( $controller ); } } submissions/data/endpoints/referer.php 0000644 00000003203 14720522625 0014204 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\Data\Endpoints; use Elementor\Data\Base\Endpoint; use ElementorPro\Modules\Forms\Submissions\Database\Query; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Referer extends Endpoint { public function get_name() { return 'referer'; } protected function register() { $this->register_route( '', \WP_REST_Server::READABLE, function ( $request ) { return $this->base_callback( \WP_REST_Server::READABLE, $request, true ); }, [ 'context' => [ 'description' => 'Scope under which the request is made, determines fields present in response. (only "options" available for now)', 'type' => 'string', 'enum' => [ 'options', ], 'default' => 'options', 'required' => false, ], 'search' => [ 'description' => 'Limit results to those matching a string.', 'type' => 'string', 'required' => false, 'additionalProperties' => [ 'context' => 'filter', ], ], 'value' => [ 'description' => 'Limit results specific referer.', 'type' => 'string', 'required' => false, 'additionalProperties' => [ 'context' => 'filter', ], ], ] ); } public function get_items( $request ) { $referrers = Query::get_instance()->get_referrers( $request->get_param( 'search' ), $request->get_param( 'value' ) ); // For now return only as "options" return [ 'data' => $referrers->map(function ( $referer ) { return [ 'label' => $referer['referer_title'], 'value' => $referer['referer'], ]; })->values(), 'meta' => [], ]; } } submissions/data/endpoints/export.php 0000644 00000006453 14720522625 0014105 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\Data\Endpoints; use Elementor\Data\Base\Endpoint; use Elementor\Core\Utils\Collection; use ElementorPro\Modules\Forms\Submissions\Export\CSV_Export; use ElementorPro\Modules\Forms\Submissions\Database\Query; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * This logic should be under index.php::get_items method, but for now * the Data JS API does not support sending Headers like `Accept: text/csv`. */ class Export extends Endpoint { const EXPORT_BY_IDS = 'ids'; const EXPORT_BY_FILTER = 'filter'; public function get_name() { return 'export'; } protected function register() { $this->register_route( '', \WP_REST_Server::READABLE, function ( $request ) { return $this->base_callback( \WP_REST_Server::READABLE, $request, true ); }, array_merge( $this->controller->get_collection_params(), [ 'ids' => [ 'description' => 'Unique identifiers for the objects.', 'type' => 'array', 'items' => [ 'type' => 'integer', ], 'required' => false, 'additionalProperties' => [ 'context' => 'filter', ], ], 'format' => [ 'description' => 'The format of the export (for now only csv).', 'types' => 'string', 'enum' => [ 'csv', ], 'default' => 'csv', 'required' => false, ], 'per_page' => [ 'description' => 'Maximum number of items to be returned in result set.', 'type' => 'integer', 'default' => 10, 'minimum' => 1, 'maximum' => 10000, 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ], ] ) ); } /** * @param \WP_REST_Request $request * * @return \WP_Error|\WP_REST_Response */ public function get_items( $request ) { wp_raise_memory_limit( 'admin' ); $submissions = new Collection( $this->get_submissions_by_filter( $request ) ); if ( 0 === $submissions->count() ) { return new \WP_Error( 'nothing_to_export', __( 'There is nothing to export.', 'elementor-pro' ), [ 'status' => 400 ] ); } $response = $submissions ->group_by( 'element_id' ) ->map( function ( array $submissions_by_form ) { $exporter = new CSV_Export( new Collection( $submissions_by_form ) ); return $exporter->prepare_for_json_response(); } ); return new \WP_REST_Response([ 'data' => $response->values(), ] ); } /** * Get submissions by filter. * * @param $request * * @return array|mixed */ private function get_submissions_by_filter( $request ) { $args = $request->get_attributes()['args']; $filters = ( new Collection( $request->get_query_params() ) ) ->filter(function ( $value, $key ) use ( $args ) { return isset( $args[ $key ]['additionalProperties']['context'] ) && 'filter' === $args[ $key ]['additionalProperties']['context']; }) ->map( function ( $value ) use ( $request ) { return [ 'value' => $value ]; } ) ->all(); return Query::get_instance()->get_submissions( [ 'page' => $request->get_param( 'page' ), 'per_page' => $request->get_param( 'per_page' ), 'filters' => $filters, 'order' => [ 'order' => $request->get_param( 'order' ), 'by' => $request->get_param( 'order_by' ), ], 'with_meta' => true, ] )['data']; } } submissions/data/endpoints/index.php 0000644 00000006537 14720522625 0013676 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\Data\Endpoints; use Elementor\Data\Base\Endpoint; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Index extends Endpoint { public function get_name() { return 'index'; } protected function register() { $this->register_route( '', \WP_REST_Server::READABLE, function ( $request ) { return $this->base_callback( \WP_REST_Server::READABLE, $request, true ); }, $this->controller->get_collection_params() ); $this->register_route( '(?P<id>[\d]+)/', \WP_REST_Server::READABLE, function ( $request ) { return $this->base_callback( \WP_REST_Server::READABLE, $request ); }, [ 'id' => [ 'description' => 'Unique identifier for the object.', 'type' => 'string', 'required' => true, ], ] ); $this->register_route( '(?P<id>[\d]+)/', \WP_REST_Server::DELETABLE, function ( $request ) { return $this->base_callback( \WP_REST_Server::DELETABLE, $request ); }, [ 'id' => [ 'description' => 'Unique identifier for the object.', 'type' => 'string', 'required' => true, ], 'force' => [ 'description' => 'Delete the object permanently.', 'type' => 'boolean', 'required' => false, ], ] ); $this->register_route( '', \WP_REST_Server::DELETABLE, function ( $request ) { return $this->base_callback( \WP_REST_Server::DELETABLE, $request, true ); }, [ 'ids' => [ 'description' => 'Unique identifiers for the objects.', 'type' => 'array', 'items' => [ 'type' => 'integer', ], 'validate_callback' => function ( $param ) { return ! empty( $param ); }, 'required' => true, ], 'force' => [ 'description' => 'Delete the object permanently.', 'type' => 'boolean', 'required' => false, ], ] ); $this->register_route( '(?P<id>[\d]+)/', \WP_REST_Server::EDITABLE, function ( $request ) { return $this->base_callback( \WP_REST_Server::EDITABLE, $request ); }, [ 'id' => [ 'description' => 'Unique identifier for the object.', 'type' => 'string', 'required' => true, ], 'is_read' => [ 'description' => 'mark whether the submission was read or not', 'type' => 'boolean', 'required' => false, ], 'values' => [ 'description' => 'Form field values, receive an array, the key should be the form field id and the value should be the value.', 'type' => 'object', 'required' => false, 'sanitize_callback' => function ( $values ) { $result = []; foreach ( $values as $key => $value ) { $result[ $key ] = sanitize_text_field( $value ); } return $result; }, ], ] ); $this->register_route( '', \WP_REST_Server::EDITABLE, function ( $request ) { return $this->base_callback( \WP_REST_Server::EDITABLE, $request, true ); }, [ 'ids' => [ 'description' => 'Unique identifiers for the objects.', 'type' => 'array', 'items' => [ 'type' => 'integer', ], 'validate_callback' => function ( $param ) { return ! empty( $param ); }, 'required' => true, ], 'is_read' => [ 'description' => 'mark whether the submission was read or not', 'type' => 'boolean', 'required' => false, ], ] ); } } submissions/data/endpoints/forms-index.php 0000644 00000001414 14720522625 0015007 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\Data\Endpoints; use Elementor\Data\Base\Endpoint; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Forms_Index extends Endpoint { public function get_name() { return 'index'; } protected function register() { $this->register_route( '', \WP_REST_Server::READABLE, function ( $request ) { return $this->base_callback( \WP_REST_Server::READABLE, $request, true ); }, [ 'context' => [ 'description' => 'Scope under which the request is made, determines fields present in response. (only "options" available for now)', 'type' => 'string', 'enum' => [ 'options', ], 'default' => 'options', 'required' => false, ], ] ); } } submissions/data/forms-controller.php 0000644 00000002211 14720522625 0014054 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\Data; use Elementor\Data\Base\Controller as Controller_Base; use ElementorPro\Modules\Forms\Submissions\Database\Entities\Form_Snapshot; use ElementorPro\Modules\Forms\Submissions\Database\Repositories\Form_Snapshot_Repository; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Forms_Controller extends Controller_Base { public function get_name() { return 'forms'; } public function get_items( $request ) { $forms = Form_Snapshot_Repository::instance()->all(); // For now return only as "options" return [ 'data' => $forms->map(function ( Form_Snapshot $form ) { return [ 'label' => $form->get_label(), 'value' => $form->get_key(), ]; })->values(), 'meta' => [], ]; } public function register_endpoints() { // } protected function register_internal_endpoints() { // Register as internal to remove the default endpoint generated by the base controller. $this->register_endpoint( Endpoints\Forms_Index::class ); } public function get_permission_callback( $request ) { return current_user_can( 'manage_options' ); } } submissions/data/responses/query-failed-response.php 0000644 00000001221 14720522625 0017011 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\Data\Responses; use ElementorPro\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Query_Failed_Response extends \WP_Error { public function __construct( $query_error_message, $message = null ) { if ( ! $message ) { $message = esc_html__( 'Could not retrieve query data.', 'elementor-pro' ); } $this->log_error( $query_error_message ); parent::__construct( 'rest_internal_error', $message, [ 'status' => 500 ] ); } private function log_error( $query_error_message ) { Plugin::elementor()->logger->error( $query_error_message ); } } submissions/component.php 0000644 00000013227 14720522625 0011647 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions; use Elementor\Core\Admin\Menu\Admin_Menu_Manager; use Elementor\Core\Admin\Menu\Main as MainMenu; use Elementor\Settings; use ElementorPro\License\API; use ElementorPro\Modules\Forms\Registrars\Form_Actions_Registrar; use ElementorPro\Modules\Forms\Submissions\AdminMenuItems\Submissions_Menu_Item; use ElementorPro\Modules\Forms\Submissions\AdminMenuItems\Submissions_Promotion_Menu_Item; use ElementorPro\Plugin; use ElementorPro\Base\Module_Base; use ElementorPro\Modules\Forms\Submissions\Database\Query; use ElementorPro\Modules\Forms\Submissions\Data\Controller; use ElementorPro\Modules\Forms\Submissions\Database\Migration; use ElementorPro\Modules\Forms\Submissions\Data\Forms_Controller; use ElementorPro\Modules\Forms\Submissions\Actions\Save_To_Database; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Component extends Module_Base { const NAME = 'form-submissions'; const PAGE_ID = 'e-form-submissions'; /** * @return string */ public function get_name() { return static::NAME; } /** * @return string */ public function get_assets_base_url() { return ELEMENTOR_PRO_URL; } /** * Check if the current admin page is the component page. * * @return bool */ private function is_current() { // Nonce verification not required here. // phpcs:ignore WordPress.Security.NonceVerification.Recommended return ( ! empty( $_GET['page'] ) && self::PAGE_ID === $_GET['page'] ); } private function register_admin_menu( MainMenu $menu ) { $menu->add_submenu( [ 'menu_title' => $this->get_title(), 'menu_slug' => self::PAGE_ID, 'function' => function () { $this->render_admin_page(); }, 'index' => 35, ] ); } /** * Register admin menu */ private function register_admin_menu_legacy( Admin_Menu_Manager $admin_menu ) { $admin_menu->register( static::PAGE_ID, $this->can_use_submissions() ? new Submissions_Menu_Item() : new Submissions_Promotion_Menu_Item() ); } private function can_use_submissions() : bool { return API::is_license_active() && API::active_licence_has_feature( static::NAME ); } private function render_admin_page() { ?> <div class="wrap"> <h1 class="wp-heading-inline"><?php echo esc_html__( 'Submissions', 'elementor-pro' ); ?></h1> <hr class="wp-header-end"> <div id="e-form-submissions"></div> </div> <?php } /** * Enqueue admin scripts */ private function enqueue_scripts() { wp_register_style( 'select2', $this->get_css_assets_url( 'e-select2', '../elementor/assets/lib/e-select2/css/' ), [], '4.0.6-rc.1' ); wp_enqueue_style( 'elementor-app-base', $this->get_css_assets_url( 'modules/forms/submissions/admin', null, 'default', true ), [ 'select2' ], ELEMENTOR_PRO_VERSION ); wp_register_script( 'select2', $this->get_js_assets_url( 'e-select2.full', '../elementor/assets/lib/e-select2/js/' ), [ 'jquery', ], '4.0.6-rc.1', true ); wp_enqueue_script( 'form-submission-admin', $this->get_js_assets_url( 'form-submission-admin' ), [ 'select2', 'wp-url', 'wp-i18n', 'wp-date', 'react', 'react-dom', ], ELEMENTOR_PRO_VERSION, true ); $is_trash_enabled = (int) ( EMPTY_TRASH_DAYS !== 0 ); wp_add_inline_script( 'form-submission-admin', "window.elementorSubmissionsConfig = { isTrashEnabled: {$is_trash_enabled} };", 'before' ); wp_set_script_translations( 'form-submission-admin', 'elementor-pro' ); } private function scheduled_submissions_delete() { $query = Query::get_instance(); $delete_timestamp = time() - ( DAY_IN_SECONDS * EMPTY_TRASH_DAYS ); $ids = $query->get_trashed_submission_ids_to_delete( $delete_timestamp ); foreach ( $ids as $id ) { $query->delete_submission( $id ); } } private function get_title() { return esc_html__( 'Submissions', 'elementor-pro' ); } /** * Component constructor. */ public function __construct() { parent::__construct(); Plugin::elementor()->data_manager->register_controller( Controller::class ); Plugin::elementor()->data_manager->register_controller( Forms_Controller::class ); new Personal_Data(); add_action( 'admin_init', function () { Migration::install(); } ); add_action( 'elementor_pro/forms/actions/register', function ( Form_Actions_Registrar $actions_registrar ) { $actions_registrar->register( new Save_To_Database() ); }, 0 /* Before all the actions */ ); add_filter( 'elementor_pro/forms/default_submit_actions', function ( $actions ) { return array_merge( $actions, [ 'save-to-database' ] ); } ); add_action( 'wp_scheduled_delete', function () { $this->scheduled_submissions_delete(); } ); if ( Plugin::elementor()->experiments->is_feature_active( 'admin_menu_rearrangement' ) ) { add_action( 'elementor/admin/menu_registered/elementor', function( MainMenu $menu ) { $this->register_admin_menu( $menu ); } ); } else { add_action( 'elementor/admin/menu/register', function( Admin_Menu_Manager $admin_menu ) { $this->register_admin_menu_legacy( $admin_menu ); }, 9 /* After "Settings" */ ); // TODO: BC - Remove after `Admin_Menu_Manager` will be the standard. add_action( 'admin_menu', function () { if ( did_action( 'elementor/admin/menu/register' ) ) { return; } $title = $this->get_title(); add_submenu_page( Settings::PAGE_ID, $title, $title, 'manage_options', self::PAGE_ID, function () { $this->render_admin_page(); } ); }, 21 /* after Elementor page */ ); } if ( $this->is_current() ) { add_action( 'admin_enqueue_scripts', function () { $this->enqueue_scripts(); } ); } } } submissions/personal-data.php 0000644 00000007726 14720522625 0012406 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions; use Elementor\Core\Utils\Collection; use Elementor\Core\Base\Base_Object; use ElementorPro\Modules\Forms\Submissions\Database\Query; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Personal_Data extends Base_Object { const WP_KEY = 'elementor-form-submissions'; /** * @return string */ private function get_title() { return esc_html__( 'Elementor Submissions', 'elementor-pro' ); } /** * @return string */ private function get_key() { return self::WP_KEY; } /** * Export all the submissions related to specific email. * * WordPress send always an email even if the user choose to erase by username. * * @param $email * * @return array */ private function export_data( $email ) { $data = Query::get_instance() ->get_submissions_by_email( $email, true ) ->map(function ( $submission ) { $submission_data = ( new Collection( $submission->values ) ) ->map( function ( $value ) { return [ 'name' => $value->key, 'value' => $value->value, ]; } ) ->merge([ [ 'name' => esc_html__( 'User IP', 'elementor-pro' ), 'value' => $submission->user_ip, ], [ 'name' => esc_html__( 'Referer', 'elementor-pro' ), 'value' => $submission->referer, ], [ 'name' => esc_html__( 'User Agent', 'elementor-pro' ), 'value' => $submission->user_agent, ], [ 'name' => esc_html__( 'Created At', 'elementor-pro' ), 'value' => $submission->created_at, ], [ 'name' => esc_html__( 'Created At GMT', 'elementor-pro' ), 'value' => $submission->created_at_gmt, ], [ 'name' => esc_html__( 'Updated At', 'elementor-pro' ), 'value' => $submission->updated_at, ], [ 'name' => esc_html__( 'Updated At GMT', 'elementor-pro' ), 'value' => $submission->updated_at_gmt, ], ]) ->all(); return [ 'group_id' => $this->get_key(), 'group_label' => $this->get_title(), 'item_id' => "{$this->get_key()}-{$submission->id}", 'data' => $submission_data, ]; }) ->all(); return [ 'data' => $data, 'done' => true, ]; } /** * Erase all the submissions related to specific email. * * WordPress send always an email even if the user choose to erase by username. * * @param $email * * @return array */ private function erase_data( $email ) { $query = Query::get_instance(); $submissions = $query->get_submissions_by_email( $email, true ); $affected = 0; $failed = 0; foreach ( $submissions as $submission ) { $affected_rows = $query->delete_submission( $submission->id ); if ( false === $affected_rows ) { $failed++; } else { $affected += $affected_rows; } } return [ 'items_removed' => count( $submissions ) === $affected, 'items_retained' => $failed > 0, 'messages' => [], 'done' => true, ]; } /** * Add exporter to the list of exporters * * @param $exporters * * @return mixed */ private function add_exporter( $exporters ) { $exporters[ $this->get_key() ] = [ 'exporter_friendly_name' => $this->get_title(), 'callback' => function ( $email ) { return $this->export_data( $email ); }, ]; return $exporters; } /** * Add eraser to the list of erasers. * * @param $erasers * * @return array[] */ private function add_eraser( $erasers ) { return $erasers + [ $this->get_key() => [ 'eraser_friendly_name' => $this->get_title(), 'callback' => function ( $email ) { return $this->erase_data( $email ); }, ], ]; } /** * Personal_Data constructor. */ public function __construct() { add_filter( 'wp_privacy_personal_data_exporters', function ( $exporters ) { return $this->add_exporter( $exporters ); } ); add_filter( 'wp_privacy_personal_data_erasers', function ( $exporters ) { return $this->add_eraser( $exporters ); } ); } } submissions/database/migration.php 0000644 00000003105 14720522625 0013374 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\Database; use Elementor\Core\Base\Base_Object; use Elementor\Core\Utils\Collection; class Migration extends Base_Object { const OPTION_DB_VERSION = 'elementor_submissions_db_version'; // This version must be updated when new migration created. const CURRENT_DB_VERSION = 5; private static $migrations = [ 1 => Migrations\Initial::class, // It jumps from version 1 to 4 because some users already migrated the DB when the migrations system worked with the Elementor Pro version // when the int value of the version "3.2.0" was 3. 4 => Migrations\Referer_Extra::class, 5 => Migrations\Fix_Indexes::class, ]; /** * Checks if there is a need to run migrations. */ public static function install() { $installed_version = intval( get_option( self::OPTION_DB_VERSION ) ); // Up to date. Nothing to do. if ( static::CURRENT_DB_VERSION <= $installed_version ) { return; } global $wpdb; ( new Collection( static::$migrations ) ) ->filter( function ( $_, $version ) use ( $installed_version ) { // Filter all the migrations that already done. return $version > $installed_version; } ) ->map( function ( $migration_class_name, $version ) use ( $wpdb ) { /** @var Migrations\Base_Migration $migration */ $migration = new $migration_class_name( $wpdb ); $migration->run(); // In case some migration failed it updates version every migration. update_option( static::OPTION_DB_VERSION, $version ); } ); update_option( static::OPTION_DB_VERSION, self::CURRENT_DB_VERSION ); } } submissions/database/query.php 0000644 00000055336 14720522625 0012565 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\Database; use Elementor\Core\Base\Base_Object; use ElementorPro\Plugin; use Elementor\Core\Utils\Collection; use Exception; use ElementorPro\Modules\Forms\Classes\Action_Base; use ElementorPro\Modules\Forms\Submissions\Database\Repositories\Form_Snapshot_Repository; class Query extends Base_Object { const STATUS_NEW = 'new'; const STATUS_TRASH = 'trash'; const ACTIONS_LOG_STATUS_SUCCESS = 'success'; const ACTIONS_LOG_STATUS_FAILED = 'failed'; const TYPE_SUBMISSION = 'submission'; /** * @var Query */ private static $instance = null; const E_SUBMISSIONS_ACTIONS_LOG = 'e_submissions_actions_log'; const E_SUBMISSIONS_VALUES = 'e_submissions_values'; const E_SUBMISSIONS = 'e_submissions'; /** * @var \wpdb */ private $wpdb; /** * @var string */ private $table_submissions; /** * @var string */ private $table_submissions_values; /** * @var string */ private $table_submissions_actions_log; public static function get_instance() { if ( ! self::$instance ) { self::$instance = new Query(); } return self::$instance; } public function get_submissions( $args = [] ) { $args = wp_parse_args( $args, [ 'page' => 1, 'per_page' => 10, 'filters' => [], 'order' => [], 'with_meta' => false, 'with_form_fields' => false, ] ); $page = $args['page']; $per_page = $args['per_page']; $filters = $args['filters']; $order = $args['order']; $with_meta = $args['with_meta']; $with_form_fields = $args['with_form_fields']; $result = [ 'data' => [], 'meta' => [], ]; $where_sql = $this->apply_filter( $filters ); $order_sql = ''; $total = (int) $this->wpdb->get_var("SELECT COUNT(*) FROM `{$this->get_table_submissions()}` t_submissions {$where_sql}" );// phpcs:ignore $last_page = 0 < $total && 0 < $per_page ? (int) ceil( $total / $per_page ) : 1; if ( $page > $last_page ) { $page = $last_page; } $offset = (int) ceil( ( $page - 1 ) * $per_page ); $result['meta']['pagination'] = [ 'current_page' => $page, 'per_page' => $per_page, 'total' => $total, 'last_page' => $last_page, ]; $this->handle_order( $order, $order_sql ); $per_page = (int) $per_page; $q = "SELECT t_submissions.* FROM `{$this->get_table_submissions()}` t_submissions {$where_sql} {$order_sql} LIMIT {$per_page} OFFSET {$offset}"; $submissions = $this->wpdb->get_results( $q );// phpcs:ignore $data = new Collection( [] ); foreach ( $submissions as $current_submission ) { $data[] = $this->get_submission_body( $current_submission, $with_form_fields ); } $submissions_meta = $this ->get_submissions_meta( $data, ! $with_meta ) ->group_by( 'submission_id' ); $result['data'] = $data ->map( function ( $submission ) use ( $submissions_meta ) { $current_submission_meta = $submissions_meta->get( $submission['id'], [] ); foreach ( $current_submission_meta as $meta ) { $extract_meta = $this->extract( $meta, [ 'id', 'key', 'value' ], [ 'id:int' ] ); if ( $meta->id === $submission['main_meta_id'] ) { $submission['main'] = $extract_meta; } $submission['values'][] = $extract_meta; } return $submission; } ) ->all(); return $result; } /** * Get count by status. * * @param $filter * * @return Collection */ public function count_submissions_by_status( $filter = [] ) { // Should not filter by status. unset( $filter['status'] ); $where_sql = $this->apply_filter( $filter ); $trash_status = '"' . static::STATUS_TRASH . '"'; $sql = " SELECT SUM(IF(`status` != {$trash_status}, 1, 0)) AS `all`, SUM(IF(`status` = {$trash_status}, 1, 0)) AS `trash`, SUM(IF(is_read = 1 AND `status` != {$trash_status}, 1, 0)) AS `read`, SUM(IF(is_read = 0 AND `status` != {$trash_status}, 1, 0)) AS `unread` FROM {$this->get_table_submissions()} AS `t_submissions` {$where_sql}; "; $sql_result = $this->wpdb->get_row( $sql, ARRAY_A ); // phpcs:ignore $result = new Collection( [ 'all' => 0, 'trash' => 0, 'read' => 0, 'unread' => 0, ] ); if ( ! $sql_result ) { return $result; } return $result->map( function ( $count, $key ) use ( $sql_result ) { if ( ! isset( $sql_result[ $key ] ) ) { return $count; } return intval( $sql_result[ $key ] ); } ); } public function get_submissions_by_email( $email, $include_submission_values = false ) { $user = get_user_by( 'email', $email ); $user_filter = ''; if ( $user ) { $user_filter = $this->wpdb->prepare( 't_submissions.user_id = %s OR', $user->ID ); } $query = " SELECT DISTINCT(t_submissions.id), t_submissions.* FROM `{$this->get_table_submissions()}` t_submissions INNER JOIN {$this->get_table_submissions_values()} t_submissions_meta ON t_submissions.id = t_submissions_meta.submission_id WHERE {$user_filter} t_submissions_meta.value = %s ; "; $data = $this->wpdb->get_results( $this->wpdb->prepare( $query, $email ) // phpcs:ignore ); if ( ! $data ) { return new Collection( [] ); } $data = new Collection( $data ); if ( $include_submission_values ) { $submissions_meta = $this->get_submissions_meta( $data ) ->group_by( 'submission_id' ); $data->map( function ( $submission ) use ( $submissions_meta ) { $submission->values = $submissions_meta->get( $submission->id, [] ); return $submission; } ); } return $data; } /** * @param int $delete_timestamp * * @return array */ public function get_trashed_submission_ids_to_delete( $delete_timestamp ) { $date = gmdate( 'Y-m-d H:i:s', $delete_timestamp ); $sql = " SELECT s.id FROM `{$this->get_table_submissions()}` s WHERE s.status = %s AND s.updated_at_gmt < %s; "; return $this->wpdb->get_col( $this->wpdb->prepare( $sql, static::STATUS_TRASH, $date ) // phpcs:ignore ); } public function get_submission( $id ) { $result = [ 'data' => [], 'meta' => [], ]; $q = "SELECT * FROM `{$this->get_table_submissions()}` WHERE id=%d"; $submission = $this->wpdb->get_row( $this->wpdb->prepare( $q, [ $id ] ) );// phpcs:ignore if ( ! $submission ) { return null; } $result['data'] = $this->get_submission_body( $submission, true ); $current_submission_meta = $this->get_submissions_meta( $submission, false )->all(); foreach ( $current_submission_meta as $meta ) { $extract_meta = $this->extract( $meta, [ 'id', 'key', 'value' ], [ 'id:int' ] ); if ( $meta->id === $result['data']['main_meta_id'] ) { $result['data']['main'] = $extract_meta; } $result['data']['values'][] = $extract_meta; } $result['data']['form_actions_log'] = ( new Collection( $this->get_submissions_actions_log( $id ) ) ) ->map( function ( $value ) { return $this->extract( $value, [ 'id', 'action_name', 'action_label', 'status', 'log', 'created_at', 'updated_at' ], [ 'id:int', 'name', 'label' ] ); } ) ->all(); return $result; } public function get_referrers( $search = '', $value = '' ) { $where = ''; if ( $search ) { $search = '%' . $this->wpdb->esc_like( $search ) . '%'; $where = $this->wpdb->prepare( ' AND s.referer_title LIKE %s', $search ); } if ( $value ) { $where = $this->wpdb->prepare( ' AND s.referer = %s', $value ); // phpcs:ignore } $where = 'WHERE 1=1' . $where; $query = " SELECT DISTINCT (s.referer), s.referer_title FROM {$this->get_table_submissions()} s {$where}; "; $result = $this->wpdb->get_results( $query, ARRAY_A ); // phpcs:ignore if ( ! $result ) { return new Collection( [] ); } return new Collection( $result ); } /** * @param $submissions * @param false $only_main * * @return Collection */ public function get_submissions_meta( $submissions, $only_main = false ) { if ( ! ( $submissions instanceof Collection ) ) { $submissions = new Collection( is_array( $submissions ) ? $submissions : [ $submissions ] ); } if ( $submissions->is_empty() ) { return new Collection( [] ); } if ( $only_main ) { $column = 'id'; $ids = $submissions->pluck( 'main_meta_id' ); } else { $column = 'submission_id'; $ids = $submissions->pluck( 'id' ); } $placeholders = $ids->map( function () { return '%d'; } )->implode( ',' ); $q = "SELECT * FROM `{$this->get_table_submissions_values()}` WHERE `{$column}` IN ({$placeholders})"; $result = $this->wpdb->get_results( $this->wpdb->prepare( $q, $ids->all() ) ); // phpcs:ignore if ( ! $result ) { return new Collection( [] ); } return new Collection( $result ); } /** * @param $post_id * @param $element_id * * @return Collection */ public function get_submissions_value_keys( $post_id, $element_id ) { $sql = " SELECT DISTINCT(wesv.`key`) FROM {$this->get_table_submissions_values()} wesv INNER JOIN {$this->get_table_submissions()} wes ON wes.id = wesv.submission_id WHERE wes.post_id = %s AND wes.element_id = %s; "; $result = $this->wpdb->get_col( $this->wpdb->prepare( $sql, $post_id, $element_id ) ); // phpcs:ignore $form = Form_Snapshot_Repository::instance()->find( $post_id, $element_id ); if ( $form ) { $ordered_keys = array_map( function( $field ) { return $field['id']; }, $form->fields ); $result = array_merge( array_intersect( $ordered_keys, $result ), array_diff( $result, $ordered_keys ) ); } if ( ! $result ) { return new Collection( [] ); } return new Collection( $result ); } /** * @param $submission_id * * @return array|null */ public function get_submissions_actions_log( $submission_id ) { $query = "SELECT * FROM `{$this->get_table_form_actions_log()}` WHERE `submission_id` = %d;"; return $this->wpdb->get_results( $this->wpdb->prepare( $query, (int) $submission_id ), // phpcs:ignore ARRAY_A ); } /** * Add submission. * * @param array $submission_data * @param array $fields_data * * @return int id or 0 */ public function add_submission( array $submission_data, array $fields_data ) { global $wpdb; $result = 0; $submission_data = $this->get_new_submission_initial_data( $submission_data ); try { $wpdb->query( 'START TRANSACTION' ); $submission_success = $wpdb->insert( $this->get_table_submissions(), $submission_data ); if ( ! $submission_success ) { throw new Exception( $wpdb->last_error ); } $submission_id = $wpdb->insert_id; // Meta's keys/values. $main_meta_id = 0; $first_submissions_meta_id = 0; foreach ( $fields_data as $field ) { $wpdb->insert( $this->get_table_submissions_values(), [ 'submission_id' => $submission_id, 'key' => $field['id'], 'value' => $field['value'], ] ); if ( ! $first_submissions_meta_id ) { $first_submissions_meta_id = $wpdb->insert_id; } if ( 0 === $main_meta_id && 'email' === $field['type'] ) { $main_meta_id = $wpdb->insert_id; } } // If `$main_meta_id` not determined. if ( ! $main_meta_id ) { $main_meta_id = $first_submissions_meta_id; } // Update main_meta_id. $wpdb->update( $this->get_table_submissions(), [ 'main_meta_id' => $main_meta_id, ], [ 'id' => $submission_id, ] ); $wpdb->query( 'COMMIT' ); $result = $submission_id; } catch ( Exception $e ) { $wpdb->query( 'ROLLBACK' ); Plugin::elementor()->logger->get_logger()->error( 'Save submission failed, db error: ' . $e->getMessage() ); } return $result; } /** * @param $submission_id * @param array $data * @param array $values * * @return bool|int affected rows */ public function update_submission( $submission_id, array $data, $values = [] ) { if ( $values ) { foreach ( $values as $key => $value ) { $this->wpdb->update( $this->get_table_submissions_values(), [ 'value' => $value ], [ 'submission_id' => $submission_id, 'key' => $key, ] ); } } $data['updated_at_gmt'] = current_time( 'mysql', true ); $data['updated_at'] = get_date_from_gmt( $data['updated_at_gmt'] ); return $this->wpdb->update( $this->get_table_submissions(), $data, [ 'id' => $submission_id ] ); } /** * Move single submission to trash * * @param $id * * @return bool|int number of affected rows or false if failed */ public function move_to_trash_submission( $id ) { return $this->update_submission( $id, [ 'status' => static::STATUS_TRASH ] ); } /** * Delete a single submission. * * @param $id * * @return bool|int number of affected rows or false if failed */ public function delete_submission( $id ) { $this->wpdb->delete( $this->get_table_submissions_values(), [ 'submission_id' => $id ] ); $this->wpdb->delete( $this->get_table_form_actions_log(), [ 'submission_id' => $id ] ); return $this->wpdb->delete( $this->get_table_submissions(), [ 'id' => $id ] ); } /** * Restore a single submission. * * @param $id * * @return bool|int number of affected rows or false if failed */ public function restore( $id ) { return $this->wpdb->update( $this->get_table_submissions(), [ 'status' => self::STATUS_NEW ], [ 'id' => $id ] ); } /** * @param $submission_id * @param Action_Base $action Should be class based on ActionBase (do not type hint to support third party plugins) * @param $status * @param null $log_message * * @return bool|int */ public function add_action_log( $submission_id, $action, $status, $log_message = null ) { $current_datetime_gmt = current_time( 'mysql', true ); $current_datetime = get_date_from_gmt( $current_datetime_gmt ); return $this->wpdb->insert( $this->get_table_form_actions_log(), [ 'submission_id' => $submission_id, 'action_name' => $action->get_name(), 'action_label' => $action->get_label(), 'status' => $status, 'log' => $log_message, 'created_at_gmt' => $current_datetime_gmt, 'updated_at_gmt' => $current_datetime_gmt, 'created_at' => $current_datetime, 'updated_at' => $current_datetime, ] ); } public function get_last_error() { $this->wpdb->last_error; } public function get_table_submissions() { return $this->table_submissions; } public function get_table_submissions_values() { return $this->table_submissions_values; } public function get_table_form_actions_log() { return $this->table_submissions_actions_log; } /** * Extract data from `$target` by selected `$keys`. * `$as` used to convert extracted data with different keys. * `$as` support converting to types. using ':' after the key name. * * Example: `$target = [ 'someId' => '5' ];` * `$keys = [ 'someId' ]` * `$as = [ 'id:int' ]` * Output will be `[ 'id' => 5 ];` * * @param array|\stdClass $target * @param array $keys * @param array $as * * @return array */ private function extract( $target, $keys, $as = [] ) { $result = []; $as_types = []; $as_count = 0; if ( is_object( $target ) ) { $target = (array) $target; } foreach ( $as as $key => $as_item ) { $exploded = explode( ':', $as_item ); if ( count( $exploded ) > 1 ) { $as_types [] = $exploded[1]; $as[ $key ] = $exploded[0]; } } foreach ( $keys as $key ) { if ( isset( $target[ $key ] ) ) { if ( isset( $as[ $as_count ] ) ) { $value = $target[ $key ]; if ( isset( $as_types[ $as_count ] ) ) { settype( $value, $as_types[ $as_count ] ); } $result[ $as[ $as_count ] ] = $value; } else { $result[ $key ] = $target[ $key ]; } } ++$as_count; } return $result; } private function handle_and_for_where_query( &$target_where_query ) { if ( $target_where_query ) { $target_where_query .= ' AND '; } } private function filter_after_and_before( $filters, &$target_where_query ) { // Filters 'after' & 'before' known in advance, and could handled systematically. if ( isset( $filters['after'] ) || isset( $filters['before'] ) ) { $this->handle_and_for_where_query( $target_where_query ); // TODO: This logic applies only for 'date' format not 'date-time' format. $after = '0000-01-01 00:00:00'; $before = '9999-12-31 23:59:59'; if ( isset( $filters['after'] ) ) { $after = $filters['after']['value'] . ' 00:00:00'; } if ( isset( $filters['before'] ) ) { $before = $filters['before']['value'] . ' 23:59:59'; } $after = get_gmt_from_date( $after ); $before = get_gmt_from_date( $before ); $target_where_query .= $this->wpdb->prepare( 'created_at_gmt BETWEEN %s and %s', [ $after, $before ] ); } } private function filter_status( $filters, &$target_where_query ) { if ( isset( $filters['status'] ) ) { $this->handle_and_for_where_query( $target_where_query ); switch ( $filters['status']['value'] ) { case 'all': $target_where_query .= 'status != \'' . self::STATUS_TRASH . '\''; break; case 'unread': $target_where_query .= 'status = \'' . self::STATUS_NEW . '\' AND is_read = 0'; break; case 'read': $target_where_query .= 'status = \'' . self::STATUS_NEW . '\' AND is_read > 0'; break; case 'trash': $target_where_query .= 'status = \'' . self::STATUS_TRASH . '\''; break; } } } private function filter_ids( $filters, &$target_where_query ) { if ( ! isset( $filters['ids'] ) || empty( $filters['ids'] ) ) { return; } $this->handle_and_for_where_query( $target_where_query ); $ids_collection = new Collection( $filters['ids']['value'] ); $placeholder = $ids_collection->map( function () { return '%d'; } )->implode( ', ' ); $target_where_query .= $this->wpdb->prepare( "`id` IN ({$placeholder})", $ids_collection->all() ); // phpcs:ignore } private function filter_search( $filters, &$target_where_query ) { if ( isset( $filters['search'] ) ) { $this->handle_and_for_where_query( $target_where_query ); $like = '%' . $this->wpdb->esc_like( $filters['search']['value'] ) . '%'; $meta_table_name = $this->get_table_submissions_values(); $search = $this->wpdb->prepare( 'LIKE %s OR t_submissions.id LIKE %s', [ $like, $like ] ); $target_where_query .= "( ( SELECT GROUP_CONCAT({$meta_table_name}.value) FROM `{$meta_table_name}` WHERE {$meta_table_name}.submission_id = t_submissions.id GROUP BY {$meta_table_name}.submission_id ) {$search} )"; } } /** * Filter bu element_id and post_id * * @param $filters * @param $target_where_query */ private function filter_by_form( $filters, &$target_where_query ) { if ( ! isset( $filters['form']['value'] ) ) { return; } $this->handle_and_for_where_query( $target_where_query ); list( $post_id, $form_id ) = explode( '_', $filters['form']['value'] ); $target_where_query .= $this->wpdb->prepare( 'post_id = %d AND element_id = %s', $post_id, $form_id ); } /** * @param $filters * @param $target_where_query */ private function filter_by_referer( $filters, &$target_where_query ) { if ( ! isset( $filters['referer']['value'] ) ) { return; } $this->handle_and_for_where_query( $target_where_query ); $target_where_query .= $this->wpdb->prepare( 'referer = %s', $filters['referer']['value'] ); } private function handle_order( $order, &$target_order_query ) { if ( ! empty( $order ) ) { $order['by'] = esc_sql( $order['by'] ); $order['order'] = strtoupper( $order['order'] ); if ( ! in_array( $order['order'], [ 'ASC', 'DESC' ], true ) ) { $order['order'] = 'ASC'; } $target_order_query = 'ORDER BY ' . $order['by'] . ' ' . $order['order']; } } /** * @param \stdClass $submission * @param bool $with_form_fields * * @return array */ private function get_submission_body( $submission, $with_form_fields = false ) { $id = (int) $submission->id; $result = [ 'id' => $id, ]; $result['post'] = $this->extract( $submission, [ 'post_id' ], [ 'id:int' ] ); $result['form'] = $this->extract( $submission, [ 'form_name', 'element_id' ], [ 'name' ] ); $edit_post_id = $submission->post_id; // TODO: Should be removed if there is an ability to edit "global widgets" $meta = json_decode( $submission->meta ?? '', true ); if ( isset( $meta['edit_post_id'] ) ) { $edit_post_id = $meta['edit_post_id']; } $document = Plugin::elementor()->documents->get( $edit_post_id ); if ( $document ) { $result['post']['edit_url'] = $document->get_edit_url(); } if ( $with_form_fields ) { $form = Form_Snapshot_Repository::instance() ->find( $submission->post_id, $submission->element_id ); if ( $form ) { $result['form']['fields'] = $form->fields; } } $user = get_user_by( 'id', $submission->user_id ); $result['actions_count'] = (int) $submission->actions_count; $result['actions_succeeded_count'] = (int) $submission->actions_succeeded_count; $result['referer'] = $submission->referer; $result['referer_title'] = $submission->referer_title; $result['element_id'] = $submission->element_id; $result['main_meta_id'] = $submission->main_meta_id; $result['user_id'] = $submission->user_id; $result['user_agent'] = $submission->user_agent; $result['user_ip'] = $submission->user_ip; $result['user_name'] = $user ? $user->display_name : null; $result['created_at_gmt'] = $submission->created_at_gmt; $result['updated_at_gmt'] = $submission->updated_at_gmt; // Return the the dates according to WP current selected timezone. $result['created_at'] = get_date_from_gmt( $submission->created_at_gmt ); $result['updated_at'] = get_date_from_gmt( $submission->updated_at_gmt ); $result['status'] = $submission->status; $result['is_read'] = (bool) $submission->is_read; return $result; } private function get_new_submission_initial_data( array $submission_data ) { $current_datetime_gmt = current_time( 'mysql', true ); $current_datetime = get_date_from_gmt( $current_datetime_gmt ); $submission_data['hash_id'] = wp_generate_uuid4(); $submission_data = array_merge( [ 'created_at_gmt' => $current_datetime_gmt, 'updated_at_gmt' => $current_datetime_gmt, 'created_at' => $current_datetime, 'updated_at' => $current_datetime, 'type' => self::TYPE_SUBMISSION, 'status' => self::STATUS_NEW, ], $submission_data ); return $submission_data; } private function apply_filter( array $filter ) { $where_sql = ''; $this->filter_after_and_before( $filter, $where_sql ); $this->filter_status( $filter, $where_sql ); $this->filter_search( $filter, $where_sql ); $this->filter_by_form( $filter, $where_sql ); $this->filter_ids( $filter, $where_sql ); $this->filter_by_referer( $filter, $where_sql ); if ( ! $where_sql ) { return ''; } return 'WHERE ' . $where_sql; } public function __construct() { global $wpdb; $this->wpdb = $wpdb; $this->table_submissions = $wpdb->prefix . self::E_SUBMISSIONS; $this->table_submissions_values = $wpdb->prefix . self::E_SUBMISSIONS_VALUES; $this->table_submissions_actions_log = $wpdb->prefix . self::E_SUBMISSIONS_ACTIONS_LOG; } } submissions/database/repositories/form-snapshot-repository.php 0000644 00000006756 14720522625 0021166 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\Database\Repositories; use Elementor\Core\Base\Base_Object; use Elementor\Core\Utils\Collection; use ElementorPro\Modules\Forms\Submissions\Database\Entities\Form_Snapshot; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Form_Snapshot_Repository extends Base_Object { // There are two underscore prefix to avoid duplicate the meta when the post will be published. const POST_META_KEY = '__elementor_forms_snapshot'; /** * @var static */ private static $instance = null; /** * @var Collection */ private $cache; /** * @return static */ public static function instance() { if ( null === static::$instance ) { static::$instance = new static(); } return static::$instance; } /** * Get specific form. * * @param $post_id * @param $form_id * @param bool $from_cache * * @return Form_Snapshot|null */ public function find( $post_id, $form_id, $from_cache = true ) { $key = Form_Snapshot::generate_key( $post_id, $form_id ); if ( $from_cache && $this->cache->get( $key, false ) ) { return $this->cache->get( $key, false ); } return $this->save_in_cache( $this->get_post_forms( $post_id ) )->get( $key ); } /** * Get all the forms. * * @return Collection */ public function all() { global $wpdb; $result = $wpdb->get_results( $wpdb->prepare( "SELECT pm.meta_value, pm.post_id FROM {$wpdb->postmeta} pm WHERE pm.meta_key = %s", static::POST_META_KEY ) ); if ( ! $result ) { return new Collection( [] ); } foreach ( $result as $post_forms ) { $this->save_in_cache( $this->parse_meta( $post_forms->meta_value, $post_forms->post_id ) ); } return $this->cache; } /** * @param $post_id * @param $form_id * @param $data * * @return Form_Snapshot */ public function create_or_update( $post_id, $form_id, $data ) { $forms = $this->get_post_forms( $post_id ) ->filter( function ( Form_Snapshot $form ) use ( $form_id ) { return $form->id !== $form_id; } ); $form = new Form_Snapshot( $post_id, $data + [ 'id' => $form_id ] ); $forms[] = $form; update_post_meta( $post_id, self::POST_META_KEY, // Use `wp_slash` in order to avoid the unslashing during the `update_post_meta` wp_slash( wp_json_encode( $forms->values() ) ) ); $this->save_in_cache( $forms ); return $form; } public function clear_cache() { $this->cache = new Collection( [] ); } /** * @param $post_id * * @return Collection */ private function get_post_forms( $post_id ) { $meta_value = get_post_meta( $post_id, self::POST_META_KEY, true ); if ( ! $meta_value ) { return new Collection( [] ); } return $this->parse_meta( $meta_value, $post_id ); } /** * Receive a meta value and transform it to an array of Form objects. * * @param $meta_value * @param $post_id * * @return Collection */ private function parse_meta( $meta_value, $post_id ) { return ( new Collection( json_decode( $meta_value, true ) ) ) ->map( function ( $item ) use ( $post_id ) { return new Form_Snapshot( $post_id, $item ); } ); } /** * @param $forms * * @return Collection */ private function save_in_cache( Collection $forms ) { /** @var Form_Snapshot $form */ foreach ( $forms as $form ) { $this->cache[ $form->get_key() ] = $form; } return $this->cache; } /** * Forms_Repository constructor. */ public function __construct() { $this->cache = new Collection( [] ); } } submissions/database/entities/form-snapshot.php 0000644 00000003474 14720522625 0016040 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\Database\Entities; use Elementor\Core\Base\Base_Object; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * The Form_Snapshot is a snapshot of the form as it saved in the document data, on each submission creation it updates the snapshot to the current state of the form, * As a consequence the queries are quicker (filters, export, etc.) and in case the form itself removed from the document, the Form_Snapshot * remains and allows the user export and filter submissions as before. */ class Form_Snapshot extends Base_Object implements \JsonSerializable { /** * @var string */ public $id; /** * @var int */ public $post_id; /** * @var string */ public $name; /** * @var array { * @type string $id * @type string $type * @type string $label * } */ public $fields = []; /** * @param $post_id * @param $form_id * * @return string */ public static function generate_key( $post_id, $form_id ) { return "{$post_id}_{$form_id}"; } /** * @return string */ public function get_key() { return static::generate_key( $this->post_id, $this->id ); } /** * @return string */ public function get_label() { return "{$this->name} ($this->id)"; } /** * Implement for the JsonSerializable method, will trigger when trying to json_encode this object. * * @return array */ #[\ReturnTypeWillChange] public function jsonSerialize() { return [ 'id' => $this->id, 'name' => $this->name, 'fields' => $this->fields, ]; } /** * Form constructor. * * @param $post_id * @param $data */ public function __construct( $post_id, $data ) { $this->post_id = (int) $post_id; $this->id = $data['id']; $this->name = $data['name']; $this->fields = $data['fields']; } } submissions/database/migrations/fix-indexes.php 0000644 00000007441 14720522625 0016011 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\Database\Migrations; use Elementor\Core\Utils\Collection; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Fix_Indexes extends Base_Migration { /** * In the previous migrations some databases had problems with the indexes. * this migration checks if user's tables are filled with required indexes, if not it creates them. */ public function run() { $indexes = $this->get_indexes(); foreach ( $indexes as $table => $table_indexes ) { $existing_indexes = $this->get_existed_indexes_of( $table ); // Protect from database errors (for example: table do not exists somehow). if ( null === $existing_indexes ) { continue; } $indexes_query = $table_indexes->except( $existing_indexes )->implode( ',' ); $this->wpdb->query( "ALTER TABLE `{$table}` {$indexes_query};" ); // phpcs:ignore } } /** * Get the user exited indexes * * @param $table_name * * @return array|null */ private function get_existed_indexes_of( $table_name ) { $result = $this->wpdb->get_results( "SHOW INDEX FROM {$table_name};", ARRAY_A ); // phpcs:ignore if ( false === $result ) { return null; } return ( new Collection( $result ) ) ->map( function ( $row ) { if ( ! isset( $row['Key_name'] ) ) { return null; } return $row['Key_name']; } ) ->filter() ->values(); } /** * Get all the database indexes. * * @return Collection[] */ private function get_indexes() { $max_index_length = static::MAX_INDEX_LENGTH; return [ $this->query->get_table_submissions() => new Collection( [ 'main_meta_id_index' => 'ADD INDEX `main_meta_id_index` (`main_meta_id`)', 'hash_id_unique_index' => "ADD UNIQUE INDEX `hash_id_unique_index` (`hash_id` ({$max_index_length}))", 'hash_id_index' => "ADD INDEX `hash_id_index` (`hash_id` ({$max_index_length}))", 'type_index' => "ADD INDEX `type_index` (`type` ({$max_index_length}))", 'post_id_index' => 'ADD INDEX `post_id_index` (`post_id`)', 'element_id_index' => "ADD INDEX `element_id_index` (`element_id` ({$max_index_length}))", 'campaign_id_index' => 'ADD INDEX `campaign_id_index` (`campaign_id`)', 'user_id_index' => 'ADD INDEX `user_id_index` (`user_id`)', 'user_ip_index' => 'ADD INDEX `user_ip_index` (`user_ip`)', 'status_index' => 'ADD INDEX `status_index` (`status`)', 'is_read_index' => 'ADD INDEX `is_read_index` (`is_read`)', 'created_at_gmt_index' => 'ADD INDEX `created_at_gmt_index` (`created_at_gmt`)', 'updated_at_gmt_index' => 'ADD INDEX `updated_at_gmt_index` (`updated_at_gmt`)', 'created_at_index' => 'ADD INDEX `created_at_index` (`created_at`)', 'updated_at_index' => 'ADD INDEX `updated_at_index` (`updated_at`)', 'referer_index' => "ADD INDEX `referer_index` (`referer` ({$max_index_length}))", 'referer_title_index' => "ADD INDEX `referer_title_index` (`referer_title` ({$max_index_length}))", ] ), $this->query->get_table_submissions_values() => new Collection( [ 'submission_id_index' => 'ADD INDEX `submission_id_index` (`submission_id`)', 'key_index' => "ADD INDEX `key_index` (`key` ({$max_index_length}))", ] ), $this->query->get_table_form_actions_log() => new Collection( [ 'submission_id_index' => 'ADD INDEX `submission_id_index` (`submission_id`)', 'action_name_index' => "ADD INDEX `action_name_index` (`action_name` ({$max_index_length}))", 'status_index' => 'ADD INDEX `status_index` (`status`)', 'created_at_gmt_index' => 'ADD INDEX `created_at_gmt_index` (`created_at_gmt`)', 'updated_at_gmt_index' => 'ADD INDEX `updated_at_gmt_index` (`updated_at_gmt`)', 'created_at_index' => 'ADD INDEX `created_at_index` (`created_at`)', 'updated_at_index' => 'ADD INDEX `updated_at_index` (`updated_at`)', ] ), ]; } } submissions/database/migrations/referer-extra.php 0000644 00000001146 14720522625 0016335 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\Database\Migrations; class Referer_Extra extends Base_Migration { public function run() { $max_index_length = static::MAX_INDEX_LENGTH; // phpcs:disable $this->wpdb->query(" ALTER TABLE `{$this->query->get_table_submissions()}` ADD COLUMN `referer_title` varchar(300) null AFTER `referer`; "); $this->wpdb->query(" ALTER TABLE `{$this->query->get_table_submissions()}` ADD INDEX `referer_index` (`referer`({$max_index_length})), ADD INDEX `referer_title_index` (`referer_title`({$max_index_length})); "); // phpcs:enable } } submissions/database/migrations/base-migration.php 0000644 00000002067 14720522625 0016466 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\Database\Migrations; use Elementor\Core\Base\Base_Object; use ElementorPro\Modules\Forms\Submissions\Database\Query; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Base_Migration extends Base_Object { /* * Ref: wp-admin/includes/schema.php::wp_get_db_schema * * Indexes have a maximum size of 767 bytes. Historically, we haven't need to be concerned about that. * As of 4.2, however, we moved to utf8mb4, which uses 4 bytes per character. This means that an index which * used to have room for floor(767/3) = 255 characters, now only has room for floor(767/4) = 191 characters. */ const MAX_INDEX_LENGTH = 191; /** * @var \wpdb */ protected $wpdb; /** * @var Query */ protected $query; /** * Base_Migration constructor. * * @param \wpdb $wpdb */ public function __construct( \wpdb $wpdb ) { $this->wpdb = $wpdb; $this->query = Query::get_instance(); } /** * Run migration. * * @return void */ abstract public function run(); } submissions/database/migrations/initial.php 0000644 00000007160 14720522625 0015215 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\Database\Migrations; class Initial extends Base_Migration { public function run() { $this->create_tables(); $this->add_indexes(); } private function create_tables() { $charset_collate = $this->wpdb->get_charset_collate(); $e_submission_table = "CREATE TABLE `{$this->query->get_table_submissions()}` ( id bigint(20) unsigned auto_increment primary key, type varchar(60) null, hash_id varchar(60) not null, main_meta_id bigint(20) unsigned not null comment 'Id of main field. to represent the main meta field', post_id bigint(20) unsigned not null, referer varchar(500) not null, element_id varchar(20) not null, form_name varchar(60) not null, campaign_id bigint(20) unsigned not null, user_id bigint(20) unsigned null, user_ip varchar(46) not null, user_agent text not null, actions_count INT DEFAULT 0, actions_succeeded_count INT DEFAULT 0, status varchar(20) not null, is_read tinyint(1) default 0 not null, meta text null, created_at_gmt datetime not null, updated_at_gmt datetime not null, created_at datetime not null, updated_at datetime not null ) {$charset_collate};"; $e_submission_values_table = "CREATE TABLE `{$this->query->get_table_submissions_values()}` ( id bigint(20) unsigned auto_increment primary key, submission_id bigint(20) unsigned not null default 0, `key` varchar(60) null, value longtext null ) {$charset_collate};"; $e_submission_actions_log_table = "CREATE TABLE `{$this->query->get_table_form_actions_log()}` ( id bigint(20) unsigned auto_increment primary key, submission_id bigint(20) unsigned not null, action_name varchar(60) not null, action_label varchar(60) null, status varchar(20) not null, log text null, created_at_gmt datetime not null, updated_at_gmt datetime not null, created_at datetime not null, updated_at datetime not null ) {$charset_collate};"; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta( $e_submission_table . $e_submission_values_table . $e_submission_actions_log_table ); } private function add_indexes() { // phpcs:disable $this->wpdb->query( "ALTER TABLE `{$this->query->get_table_submissions()}` ADD INDEX `main_meta_id_index` (`main_meta_id`), ADD UNIQUE INDEX `hash_id_unique_index` (`hash_id`), ADD INDEX `hash_id_index` (`hash_id`), ADD INDEX `type_index` (`type`), ADD INDEX `post_id_index` (`post_id`), ADD INDEX `element_id_index` (`element_id`), ADD INDEX `campaign_id_index` (`campaign_id`), ADD INDEX `user_id_index` (`user_id`), ADD INDEX `user_ip_index` (`user_ip`), ADD INDEX `status_index` (`status`), ADD INDEX `is_read_index` (`is_read`), ADD INDEX `created_at_gmt_index` (`created_at_gmt`), ADD INDEX `updated_at_gmt_index` (`updated_at_gmt`), ADD INDEX `created_at_index` (`created_at`), ADD INDEX `updated_at_index` (`updated_at`) " ); $this->wpdb->query( "ALTER TABLE `{$this->query->get_table_submissions_values()}` ADD INDEX `submission_id_index` (`submission_id`), ADD INDEX `key_index` (`key`) " ); $this->wpdb->query( "ALTER TABLE `{$this->query->get_table_form_actions_log()}` ADD INDEX `submission_id_index` (`submission_id`), ADD INDEX `action_name_index` (`action_name`), ADD INDEX `status_index` (`status`), ADD INDEX `created_at_gmt_index` (`created_at_gmt`), ADD INDEX `updated_at_gmt_index` (`updated_at_gmt`), ADD INDEX `created_at_index` (`created_at`), ADD INDEX `updated_at_index` (`updated_at`) " ); // phpcs:enable } } submissions/admin-menu-items/submissions-promotion-menu-item.php 0000644 00000004230 14720522625 0021310 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\AdminMenuItems; use ElementorPro\Modules\Tiers\AdminMenuItems\Base_Promotion_Template; use Elementor\Settings; use ElementorPro\License\API; use ElementorPro\Plugin; use ElementorPro\Modules\Forms\Submissions\Component as Form_Submissions_Component; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Submissions_Promotion_Menu_Item extends Base_Promotion_Template { public function get_name(): string { return 'submissions-promotion'; } public function get_label(): string { return $this->get_page_title(); } public function get_page_title(): string { return esc_html__( 'Submissions', 'elementor-pro' ); } protected function get_promotion_title(): string { return esc_html__( 'Seal the deal on your Form Submissions', 'elementor-pro' ) . '<br>'; } protected function get_content_lines():array { return [ esc_html__( 'Integrate your favorite marketing software', 'elementor-pro' ), esc_html__( 'Collect lead submissions directly within your WordPress Admin to manage, analyze and perform bulk actions on the submitted lead', 'elementor-pro' ), ]; } protected function get_video_url(): string { return 'https://www.youtube-nocookie.com/embed/LNfnwba9C-8?si=JLHk3UAexnvTfU1a'; } public function get_cta_text() { if ( ! API::active_licence_has_feature( Form_Submissions_Component::NAME ) ) { return esc_html__( 'Upgrade Now', 'elementor-pro' ); } return API::is_license_expired() ? esc_html__( 'Renew now', 'elementor-pro' ) : esc_html__( 'Connect & Activate', 'elementor-pro' ); } protected function get_cta_url(): string { if ( ! API::active_licence_has_feature( Form_Submissions_Component::NAME ) ) { $upgrade_url = 'https://go.elementor.com/go-pro-advanced-form-submissions/'; return $upgrade_url; } $connect_url = Plugin::instance()->license_admin->get_connect_url( [ 'utm_source' => 'wp-dash-submissions', 'utm_medium' => 'wp-dash', 'utm_campaign' => 'connect-and-activate-license', ] ); $renew_url = 'https://go.elementor.com/renew-submissions/'; return API::is_license_expired() ? $renew_url : $connect_url; } } submissions/admin-menu-items/submissions-menu-item.php 0000644 00000006134 14720522625 0017271 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\AdminMenuItems; use Elementor\Core\Admin\Admin_Notices; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page; use Elementor\Settings; use Elementor\User; use ElementorPro\Core\Utils\Abtest; use ElementorPro\core\utils\Hints; use ElementorPro\Modules\Forms\Submissions\Database\Query; use ElementorPro\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Submissions_Menu_Item implements Admin_Menu_Item_With_Page { public function get_capability() { return 'manage_options'; } public function get_label() { return esc_html__( 'Submissions', 'elementor-pro' ); } public function get_page_title() { return esc_html__( 'Submissions', 'elementor-pro' ); } public function get_parent_slug() { return Settings::PAGE_ID; } public function is_visible() { return true; } public function get_position() { return null; } public function render() { if ( $this->should_show_hint() ) { /** * @var Admin_Notices $admin_notices */ $admin_notices = Plugin::elementor()->admin->get_component( 'admin-notices' ); $notice_options = [ 'description' => esc_html__( 'Experiencing email deliverability issues? Get your emails delivered with Site Mailer.', 'elementor-pro' ), 'id' => 'site_mailer_forms_submissions_notice', 'type' => 'cta', 'button_secondary' => [ 'text' => Hints::is_plugin_installed( 'site-mailer' ) ? esc_html__( 'Activate Plugin', 'elementor-pro' ) : esc_html__( 'Install Plugin', 'elementor-pro' ), 'url' => Hints::get_plugin_action_url( 'site-mailer' ), 'type' => 'cta', ], ]; if ( 2 === Abtest::get_variation( 'plg_site_mailer_submission' ) ) { $notice_options['title'] = esc_html__( 'Get Your Emails Delivered With Site Mailer', 'elementor-pro' ); $notice_options['description'] = esc_html__( 'Make sure emails reach the inbox every time with improved deliverability, detailed email logs, and an easy setup with no need for an SMTP plugin.', 'elementor-pro' ); } $admin_notices->print_admin_notice( $notice_options ); } ?> <div class="wrap"> <h1 class="wp-heading-inline"><?php echo esc_html__( 'Submissions', 'elementor-pro' ); ?></h1> <hr class="wp-header-end"> <div id="e-form-submissions"></div> </div> <?php } public function has_submissions( $min_count = 1 ): bool { global $wpdb; $table = $wpdb->prefix . Query::E_SUBMISSIONS; // The placeholder ignores can be removed when %i is supported by WPCS. // See https://core.trac.wordpress.org/ticket/56091#comment:11 // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnsupportedPlaceholder, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber $submissions_count = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i LIMIT %d', $table, $min_count ) ); return $min_count <= $submissions_count; } public function should_show_hint(): bool { return ( Hints::should_show_hint( 'site_mailer_forms_submissions_notice' ) && $this->has_submissions() && ! User::is_user_notice_viewed( 'site_mailer_forms_submissions_notice' ) ); } } submissions/actions/save-to-database.php 0000644 00000014445 14720522625 0014430 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\Actions; use Elementor\Controls_Manager; use Elementor\Core\Utils\Collection; use ElementorPro\Plugin; use ElementorPro\Modules\Forms\Widgets\Form; use ElementorPro\Modules\Forms\Classes\Action_Base; use ElementorPro\Modules\Forms\Submissions\Component; use ElementorPro\Modules\Forms\Submissions\Database\Query; use ElementorPro\Modules\Forms\Submissions\Database\Repositories\Form_Snapshot_Repository; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Save_To_Database extends Action_Base { private $submission_id; private $actions_succeeded_count = 0; public function get_name() { return 'save-to-database'; } public function get_label() { return esc_html__( 'Collect Submissions', 'elementor-pro' ); } public function register_settings_section( $widget ) { $widget->start_controls_section( 'section_submissions', [ 'label' => esc_html__( 'Collect Submissions', 'elementor-pro' ), 'condition' => [ 'submit_actions' => $this->get_name(), ], ] ); $widget->add_control( 'submissions_action_message', [ 'type' => Controls_Manager::ALERT, 'alert_type' => 'info', 'content' => sprintf( /* translators: 1: Link open tag, 2: Link open tag, 3: Link close tag. */ esc_html__( 'Collected Submissions will be saved to Elementor > %1$s Submissions %2$s', 'elementor-pro' ), sprintf( '<a href="%s" target="_blank" rel="noreferrer">', self_admin_url( 'admin.php?page=' . Component::PAGE_ID ) ), '</a>', ), ] ); $widget->add_control( 'submissions_metadata', [ 'label' => esc_html__( 'Metadata', 'elementor-pro' ), 'type' => Controls_Manager::SELECT2, 'multiple' => true, 'options' => [ 'remote_ip' => esc_html__( 'User IP', 'elementor-pro' ), 'user_agent' => esc_html__( 'User Agent', 'elementor-pro' ), ], 'render_type' => 'none', 'label_block' => true, 'default' => [ 'remote_ip', 'user_agent' ], ] ); $widget->end_controls_section(); } public function on_export( $element ) { // This action does not have to do nothing on export. } /** * @param \ElementorPro\Modules\Forms\Classes\Form_Record $record * @param \ElementorPro\Modules\Forms\Classes\Ajax_Handler $ajax_handler */ public function run( $record, $ajax_handler ) { $meta_keys = array_merge( [ 'page_url', 'page_title' ], $record->get_form_settings( 'submissions_metadata' ) ); $meta = $record->get_form_meta( $meta_keys ); $actions_count = ( new Collection( $record->get_form_settings( 'submit_actions' ) ) ) ->filter(function ( $value ) { return $value !== $this->get_name(); }) ->count(); $post_id = $record->get_form_settings( 'form_post_id' ); $element_id = $ajax_handler->get_current_form()['id']; $form_name = $record->get_form_settings( 'form_name' ); $this->submission_id = Query::get_instance()->add_submission( [ 'main_meta_id' => 0, 'post_id' => $post_id, 'referer' => remove_query_arg( [ 'preview_id', 'preview_nonce', 'preview' ], $meta['page_url']['value'] ), 'referer_title' => $meta['page_title']['value'], 'element_id' => $element_id, 'form_name' => $form_name, 'campaign_id' => 0, 'user_id' => get_current_user_id(), 'user_ip' => ! empty( $meta['remote_ip'] ) ? $meta['remote_ip']['value'] : '', 'user_agent' => ! empty( $meta['user_agent'] ) ? $meta['user_agent']['value'] : '', 'actions_count' => $actions_count, 'actions_succeeded_count' => 0, 'meta' => wp_json_encode( [ // TODO: Should be removed if there is an ability to edit "global widgets" 'edit_post_id' => $record->get_form_settings( 'edit_post_id' ), ] ), ], $record->get_field( null ) ); /** @var Form $form_instance */ $form_instance = Plugin::elementor()->elements_manager->create_element_instance( $ajax_handler->get_current_form() ); $fields = $record->get_form_settings( 'form_fields' ); // When created new submission, it should also update or create // a form snapshot to save to new state of the form in case the form changed or will // be deleted later. Form_Snapshot_Repository::instance() ->create_or_update( $post_id, $element_id, [ 'name' => $form_name, 'fields' => array_map( function ( $field, $index ) use ( $form_instance ) { // Apply filters to demonstrate the same behavior as the render behavior. (adding select fields dynamically, etc.) // Ref: modules/forms/widgets/form.php:2116 $field = apply_filters( 'elementor_pro/forms/render/item', $field, $index, $form_instance ); $field = apply_filters( "elementor_pro/forms/render/item/{$field['field_type']}", $field, $index, $form_instance ); $mapped_field = [ 'id' => $field['custom_id'], 'type' => $field['field_type'], 'label' => $field['field_label'], ]; if ( isset( $field['field_options'] ) ) { $mapped_field['options'] = preg_split( '/(\r\n|\n|\r)/', $field['field_options'] ); } if ( isset( $field['allow_multiple'] ) ) { $mapped_field['is_multiple'] = 'true' === $field['allow_multiple']; } return $mapped_field; }, $fields, array_keys( $fields ) ), ] ); } /** * It listen for all the form actions and log the result into the database. * * @param Action_Base $action Should be class based on ActionBase (do not type hint to support third party plugins) * @param \Exception|null $exception */ private function save_action_log( $action, \Exception $exception = null ) { if ( ! $this->submission_id || $action->get_name() === $this->get_name() ) { return; } $query = Query::get_instance(); $error_message = null; if ( $exception ) { $error_message = $exception->getMessage(); $status = Query::ACTIONS_LOG_STATUS_FAILED; } else { $this->actions_succeeded_count += 1; $query->update_submission( $this->submission_id, [ 'actions_succeeded_count' => $this->actions_succeeded_count, ] ); $status = Query::ACTIONS_LOG_STATUS_SUCCESS; } $query->add_action_log( $this->submission_id, $action, $status, $error_message ); } /** * Save_To_Database constructor. */ public function __construct() { add_action( 'elementor_pro/forms/actions/after_run', function ( Action_Base $action, \Exception $exception = null ) { $this->save_action_log( $action, $exception ); }, 10, 2 ); } } submissions/export/csv-export.php 0000644 00000011273 14720522625 0013277 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\Export; use Elementor\Core\Base\Base_Object; use Elementor\Core\Utils\Collection; use ElementorPro\Modules\Forms\Submissions\Database\Entities\Form_Snapshot; use ElementorPro\Modules\Forms\Submissions\Database\Query; use ElementorPro\Modules\Forms\Submissions\Database\Repositories\Form_Snapshot_Repository; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class CSV_Export extends Base_Object { /** * @var Collection */ private $submissions; /** * @var integer */ private $post_id; /** * @var string */ private $element_id; /** * @var Form_Snapshot|null */ private $form; /** * @var Collection */ private $values_keys; /** * Csv_Export constructor. * * Csv_Export constructor. * * @param Collection $submissions */ public function __construct( Collection $submissions ) { $this->submissions = $submissions; $first_submission = $this->submissions->first(); $this->values_keys = new Collection( [] ); $this->post_id = $first_submission['post']['id']; $this->element_id = $first_submission['element_id']; $this->form = Form_Snapshot_Repository::instance()->find( $this->post_id, $this->element_id ); } /** * @return array */ public function prepare_for_json_response() { $this->values_keys = Query::get_instance()->get_submissions_value_keys( $this->post_id, $this->element_id ); $headers = $this->get_headers(); $rows = $this->get_rows(); return [ 'id' => $this->element_id, 'content' => array_merge( $headers, $rows ), 'mimetype' => 'text/csv;charset=UTF-8', 'extension' => 'csv', 'form_label' => $this->form ? $this->form->get_label() : "({$this->element_id})", ]; } /** * @return array */ private function get_headers() { $base_headers = [ '1_form_name' => esc_html__( 'Form Name (ID)', 'elementor-pro' ), '2_id' => esc_html__( 'Submission ID', 'elementor-pro' ), '3_created_at' => esc_html__( 'Created At', 'elementor-pro' ), '4_user_id' => esc_html__( 'User ID', 'elementor-pro' ), '5_user_agent' => esc_html__( 'User Agent', 'elementor-pro' ), '6_user_ip' => esc_html__( 'User IP', 'elementor-pro' ), '7_referrer' => esc_html__( 'Referrer', 'elementor-pro' ), ]; $labels_dictionary = $this->get_form_labels_dictionary(); $headers = $this->values_keys ->map_with_keys( function ( $key ) use ( $labels_dictionary ) { return [ // JSON_UNESCAPED_UNICODE - for supporting non english chars. $key => wp_json_encode( isset( $labels_dictionary[ $key ] ) ? $labels_dictionary[ $key ] : $key, JSON_UNESCAPED_UNICODE ), ]; } ) ->merge( $base_headers ) ->all(); return [ implode( ',', $headers ) ]; } /** * @return array */ private function get_rows() { return $this->submissions->map( function ( $submission ) { $base_values = [ '1_form_name' => wp_json_encode( $this->form ? $this->form->get_label() : "({$this->element_id})" ), '2_id' => wp_json_encode( $submission['id'] ), '3_created_at' => wp_json_encode( $submission['created_at'] ), '4_user_id' => wp_json_encode( $submission['user_id'] ), // JSON_UNESCAPED_SLASHES - Should not escape the user agent e.g: 'Mozilla/5.0 ...' '5_user_agent' => wp_json_encode( $submission['user_agent'], JSON_UNESCAPED_SLASHES ), '6_user_ip' => wp_json_encode( $submission['user_ip'] ), // JSON_UNESCAPED_SLASHES - should not escape the url slashes e.g: 'https://local.test/' '7_referrer' => wp_json_encode( $submission['referer'], JSON_UNESCAPED_SLASHES ), ]; $values_dictionary = $this->get_values_dictionary( $submission['values'] ); $row = $this->values_keys ->map_with_keys( function ( $key ) use ( $values_dictionary ) { return [ // JSON_UNESCAPED_UNICODE - for supporting non english chars. $key => wp_json_encode( isset( $values_dictionary[ $key ] ) ? $values_dictionary[ $key ] : '', JSON_UNESCAPED_UNICODE ), ]; } ) ->merge( $base_values ) ->all(); return implode( ',', $row ); } )->all(); } /** * Create a dictionary from the field id and label. * * @return array */ private function get_form_labels_dictionary() { if ( ! $this->form ) { return []; } $dictionary = []; foreach ( $this->form->fields as $field ) { $dictionary[ $field['id'] ] = $field['label']; } return $dictionary; } /** * Create a dictionary from the value record key and value. * * @param array $values * * @return array */ private function get_values_dictionary( $values ) { if ( ! $values ) { return []; } $dictionary = []; foreach ( $values as $value ) { $dictionary[ $value['key'] ] = $value['value']; } return $dictionary; } } fields/tel.php 0000644 00000002275 14720522625 0007322 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Fields; use ElementorPro\Modules\Forms\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Tel extends Field_Base { public function get_type() { return 'tel'; } public function get_name() { return esc_html__( 'Tel', 'elementor-pro' ); } public function render( $item, $item_index, $form ) { $form->add_render_attribute( 'input' . $item_index, 'class', 'elementor-field-textual' ); $form->add_render_attribute( 'input' . $item_index, 'pattern', '[0-9()#&+*-=.]+' ); $form->add_render_attribute( 'input' . $item_index, 'title', esc_html__( 'Only numbers and phone characters (#, -, *, etc) are accepted.', 'elementor-pro' ) ); ?> <input size="1" <?php $form->print_render_attribute_string( 'input' . $item_index ); ?>> <?php } public function validation( $field, Classes\Form_Record $record, Classes\Ajax_Handler $ajax_handler ) { if ( empty( $field['value'] ) ) { return; } if ( preg_match( '/^[0-9()#&+*-=.]+$/', $field['value'] ) !== 1 ) { $ajax_handler->add_error( $field['id'], esc_html__( 'The field accepts only numbers and phone characters (#, -, *, etc).', 'elementor-pro' ) ); } } } fields/number.php 0000644 00000005537 14720522625 0010032 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Fields; use Elementor\Widget_Base; use ElementorPro\Modules\Forms\Classes; use Elementor\Controls_Manager; use ElementorPro\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Number extends Field_Base { public function get_type() { return 'number'; } public function get_name() { return esc_html__( 'Number', 'elementor-pro' ); } public function render( $item, $item_index, $form ) { $form->add_render_attribute( 'input' . $item_index, 'class', 'elementor-field-textual' ); if ( isset( $item['field_min'] ) ) { $form->add_render_attribute( 'input' . $item_index, 'min', esc_attr( $item['field_min'] ) ); } if ( isset( $item['field_max'] ) ) { $form->add_render_attribute( 'input' . $item_index, 'max', esc_attr( $item['field_max'] ) ); } ?> <input <?php $form->print_render_attribute_string( 'input' . $item_index ); ?> > <?php } /** * @param Widget_Base $widget */ public function update_controls( $widget ) { $elementor = Plugin::elementor(); $control_data = $elementor->controls_manager->get_control_from_stack( $widget->get_unique_name(), 'form_fields' ); if ( is_wp_error( $control_data ) ) { return; } $field_controls = [ 'field_min' => [ 'name' => 'field_min', 'label' => esc_html__( 'Min. Value', 'elementor-pro' ), 'type' => Controls_Manager::NUMBER, 'condition' => [ 'field_type' => $this->get_type(), ], 'tab' => 'content', 'inner_tab' => 'form_fields_content_tab', 'tabs_wrapper' => 'form_fields_tabs', ], 'field_max' => [ 'name' => 'field_max', 'label' => esc_html__( 'Max. Value', 'elementor-pro' ), 'type' => Controls_Manager::NUMBER, 'condition' => [ 'field_type' => $this->get_type(), ], 'tab' => 'content', 'inner_tab' => 'form_fields_content_tab', 'tabs_wrapper' => 'form_fields_tabs', ], ]; $control_data['fields'] = $this->inject_field_controls( $control_data['fields'], $field_controls ); $widget->update_control( 'form_fields', $control_data ); } public function validation( $field, Classes\Form_Record $record, Classes\Ajax_Handler $ajax_handler ) { if ( ! empty( $field['field_max'] ) && $field['field_max'] < (int) $field['value'] ) { /* translators: %s: The value of max field. */ $ajax_handler->add_error( $field['id'], sprintf( esc_html__( 'The field value must be less than or equal to %s.', 'elementor-pro' ), $field['field_max'] ) ); } if ( ! empty( $field['field_min'] ) && $field['field_min'] > (int) $field['value'] ) { /* translators: %s: The value of min field. */ $ajax_handler->add_error( $field['id'], sprintf( esc_html__( 'The field value must be greater than or equal to %s.', 'elementor-pro' ), $field['field_min'] ) ); } } public function sanitize_field( $value, $field ) { return intval( $value ); } } fields/step.php 0000644 00000007222 14720522625 0007506 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Fields; use Elementor\Icons_Manager; use Elementor\Widget_Base; use Elementor\Controls_Manager; use ElementorPro\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Step extends Field_Base { public function get_type() { return 'step'; } public function get_name() { return esc_html__( 'Step', 'elementor-pro' ); } public function render( $item, $item_index, $form ) { $font_icon = ''; if ( Plugin::elementor()->experiments->is_feature_active( 'e_font_icon_svg' ) && $item['selected_icon']['value'] ) { if ( 'svg' === $item['selected_icon']['library'] ) { $font_icon = Icons_Manager::render_uploaded_svg_icon( $item['selected_icon']['value'] ); } else { $font_icon = Icons_Manager::render_font_icon( $item['selected_icon'] ); } } $form->add_render_attribute( 'step' . $item_index, [ 'class' => 'e-field-step elementor-hidden', 'data-label' => $item['field_label'], 'data-previousButton' => $item['previous_button'], 'data-nextButton' => $item['next_button'], 'data-iconUrl' => 'svg' === $item['selected_icon']['library'] && $item['selected_icon']['value'] ? $item['selected_icon']['value']['url'] : '', 'data-iconLibrary' => 'svg' !== $item['selected_icon']['library'] && $item['selected_icon']['value'] ? $item['selected_icon']['value'] : '', 'data-icon' => $font_icon, ] ); ?> <div <?php $form->print_render_attribute_string( 'step' . $item_index ); ?> ></div> <?php } /** * @param Widget_Base $widget */ public function update_controls( $widget ) { $elementor = Plugin::elementor(); $control_data = $elementor->controls_manager->get_control_from_stack( $widget->get_unique_name(), 'form_fields' ); if ( is_wp_error( $control_data ) ) { return; } $field_controls = [ 'previous_button' => [ 'name' => 'previous_button', 'label' => esc_html__( 'Previous Button', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], 'condition' => [ 'field_type' => $this->get_type(), ], 'tab' => 'content', 'inner_tab' => 'form_fields_content_tab', 'tabs_wrapper' => 'form_fields_tabs', ], 'next_button' => [ 'name' => 'next_button', 'label' => esc_html__( 'Next Button', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], 'condition' => [ 'field_type' => $this->get_type(), ], 'tab' => 'content', 'inner_tab' => 'form_fields_content_tab', 'tabs_wrapper' => 'form_fields_tabs', ], 'selected_icon' => [ 'name' => 'selected_icon', 'label' => esc_html__( 'Icon', 'elementor-pro' ), 'type' => Controls_Manager::ICONS, 'fa4compatibility' => 'icon', 'description' => esc_html__( 'Visible only if selected step type contains "Icon"', 'elementor-pro' ), 'default' => [ 'value' => 'fas fa-star', 'library' => 'fa-solid', ], 'recommended' => [ 'fa-solid' => [ 'chevron-down', 'angle-down', 'angle-double-down', 'caret-down', 'caret-square-down', ], 'fa-regular' => [ 'caret-square-down', ], ], 'skin' => 'inline', 'label_block' => false, 'condition' => [ 'field_type' => $this->get_type(), ], 'tab' => 'content', 'inner_tab' => 'form_fields_content_tab', 'tabs_wrapper' => 'form_fields_tabs', ], ]; $control_data['fields'] = $this->inject_field_controls( $control_data['fields'], $field_controls ); $widget->update_control( 'form_fields', $control_data ); } } fields/time.php 0000644 00000005055 14720522625 0007473 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Fields; use ElementorPro\Modules\Forms\Classes; use Elementor\Controls_Manager; use ElementorPro\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Time extends Field_Base { public $depended_scripts = [ 'flatpickr', ]; public $depended_styles = [ 'flatpickr', ]; public function get_type() { return 'time'; } public function get_name() { return esc_html__( 'Time', 'elementor-pro' ); } public function update_controls( $widget ) { $elementor = Plugin::elementor(); $control_data = $elementor->controls_manager->get_control_from_stack( $widget->get_unique_name(), 'form_fields' ); if ( is_wp_error( $control_data ) ) { return; } $field_controls = [ 'use_native_time' => [ 'name' => 'use_native_time', 'label' => esc_html__( 'Native HTML5', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'condition' => [ 'field_type' => $this->get_type(), ], 'tab' => 'content', 'inner_tab' => 'form_fields_content_tab', 'tabs_wrapper' => 'form_fields_tabs', ], ]; foreach ( $control_data['fields'] as $index => $field ) { if ( 'placeholder' !== $field['name'] ) { continue; } foreach ( $field['conditions']['terms'] as $condition_index => $terms ) { if ( ! isset( $terms['name'] ) || 'field_type' !== $terms['name'] || ! isset( $terms['operator'] ) || 'in' !== $terms['operator'] ) { continue; } $control_data['fields'][ $index ]['conditions']['terms'][ $condition_index ]['value'][] = $this->get_type(); break; } break; } $control_data['fields'] = $this->inject_field_controls( $control_data['fields'], $field_controls ); $widget->update_control( 'form_fields', $control_data ); } public function render( $item, $item_index, $form ) { $form->add_render_attribute( 'input' . $item_index, 'class', 'elementor-field-textual elementor-time-field' ); if ( isset( $item['use_native_time'] ) && 'yes' === $item['use_native_time'] ) { $form->add_render_attribute( 'input' . $item_index, 'class', 'elementor-use-native' ); } ?> <input <?php $form->print_render_attribute_string( 'input' . $item_index ); ?>> <?php } public function validation( $field, Classes\Form_Record $record, Classes\Ajax_Handler $ajax_handler ) { if ( empty( $field['value'] ) ) { return; } if ( preg_match( '/^(([0-1][0-9])|(2[0-3])):[0-5][0-9]$/', $field['value'] ) !== 1 ) { $ajax_handler->add_error( $field['id'], esc_html__( 'The field should be in HH:MM format.', 'elementor-pro' ) ); } } } fields/upload.php 0000644 00000037572 14720522625 0010032 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Fields; use Elementor\Widget_Base; use ElementorPro\Modules\Forms\Classes; use Elementor\Controls_Manager; use ElementorPro\Modules\Forms\Widgets\Form; use ElementorPro\Plugin; use ElementorPro\Core\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Upload extends Field_Base { public $fixed_files_indices = false; const MODE_LINK = 'link'; const MODE_ATTACH = 'attach'; const MODE_BOTH = 'both'; public function get_type() { return 'upload'; } public function get_name() { return esc_html__( 'File Upload', 'elementor-pro' ); } /** * @param Widget_Base $widget */ public function update_controls( $widget ) { $elementor = Plugin::elementor(); $control_data = $elementor->controls_manager->get_control_from_stack( $widget->get_unique_name(), 'form_fields' ); if ( is_wp_error( $control_data ) ) { return; } $field_controls = [ 'attachment_type' => [ 'name' => 'attachment_type', 'label' => esc_html__( 'Send files', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'condition' => [ 'field_type' => $this->get_type(), ], 'options' => [ self::MODE_LINK => esc_html__( 'Email with link', 'elementor-pro' ), self::MODE_ATTACH => esc_html__( 'Email with attachment', 'elementor-pro' ), self::MODE_BOTH => esc_html__( 'Email with both', 'elementor-pro' ), ], 'default' => self::MODE_LINK, 'description' => esc_html__( "Uploads you receive via link are stored on your server. However, uploads via attachment won't be saved on your server, and under Submissions", 'elementor-pro' ), 'tab' => 'content', 'inner_tab' => 'form_fields_content_tab', 'tabs_wrapper' => 'form_fields_tabs', ], 'file_sizes' => [ 'name' => 'file_sizes', 'label' => esc_html__( 'Max. File Size', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'condition' => [ 'field_type' => $this->get_type(), ], 'options' => $this->get_upload_file_size_options(), 'description' => esc_html__( 'If you need to increase max upload size please contact your hosting.', 'elementor-pro' ), 'tab' => 'content', 'inner_tab' => 'form_fields_content_tab', 'tabs_wrapper' => 'form_fields_tabs', ], 'file_types' => [ 'name' => 'file_types', 'label' => esc_html__( 'Allowed File Types', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'ai' => [ 'active' => false, ], 'condition' => [ 'field_type' => $this->get_type(), ], 'description' => esc_html__( 'Enter the allowed file types, separated by a comma (jpg, gif, pdf, etc).', 'elementor-pro' ), 'tab' => 'content', 'inner_tab' => 'form_fields_content_tab', 'tabs_wrapper' => 'form_fields_tabs', ], 'allow_multiple_upload' => [ 'name' => 'allow_multiple_upload', 'label' => esc_html__( 'Multiple Files', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'condition' => [ 'field_type' => $this->get_type(), ], 'tab' => 'content', 'inner_tab' => 'form_fields_content_tab', 'tabs_wrapper' => 'form_fields_tabs', ], 'max_files' => [ 'name' => 'max_files', 'label' => esc_html__( 'Max. Files', 'elementor-pro' ), 'type' => Controls_Manager::NUMBER, 'condition' => [ 'field_type' => $this->get_type(), 'allow_multiple_upload' => 'yes', ], 'tab' => 'content', 'inner_tab' => 'form_fields_content_tab', 'tabs_wrapper' => 'form_fields_tabs', ], ]; $control_data['fields'] = $this->inject_field_controls( $control_data['fields'], $field_controls ); $widget->update_control( 'form_fields', $control_data ); } /** * @param $item * @param $item_index * @param Form $form */ public function render( $item, $item_index, $form ) { $form->add_render_attribute( 'input' . $item_index, 'class', 'elementor-upload-field' ); $form->add_render_attribute( 'input' . $item_index, 'type', 'file', true ); if ( ! empty( $item['allow_multiple_upload'] ) ) { $form->add_render_attribute( 'input' . $item_index, 'multiple', 'multiple' ); $form->add_render_attribute( 'input' . $item_index, 'name', $form->get_attribute_name( $item ) . '[]', true ); } if ( ! empty( $item['file_sizes'] ) ) { $form->add_render_attribute( 'input' . $item_index, [ 'data-maxsize' => $item['file_sizes'], //MB 'data-maxsize-message' => esc_html__( 'This file exceeds the maximum allowed size.', 'elementor-pro' ), ] ); } ?> <input <?php $form->print_render_attribute_string( 'input' . $item_index ); ?>> <?php } /** * Fix multiple files upload indices in global $_FILES array */ private function fix_file_indices() { if ( $this->fixed_files_indices ) { return; } // a mapping of $_FILES indices for validity checking $names = [ 'name', 'type', 'tmp_name', 'error', 'size', ]; $files = $_FILES['form_fields']; // phpcs:ignore -- escaped when processing the file later on. // iterate over each uploaded file foreach ( $files as $key => $part ) { $key = (string) $key; if ( in_array( $key, $names, true ) && is_array( $part ) ) { foreach ( $part as $position => $value ) { if ( is_array( $value ) ) { foreach ( $value as $index => $inner_val ) { $files[ $position ][ $index ][ $key ] = $inner_val; } } else { $files[ $position ][0][ $key ] = $value; } } } // remove original key reference unset( $files[ $key ] ); } $_FILES['form_fields'] = $files; $this->fixed_files_indices = true; } /** * validate uploaded file size against allowed file size * * @param array $field * @param $file * * @return bool */ private function is_file_size_valid( $field, $file ) { $allowed_size = ( ! empty( $field['file_sizes'] ) ) ? $field['file_sizes'] : wp_max_upload_size() / pow( 1024, 2 ); // File size validation $file_size_meta = $allowed_size * pow( 1024, 2 ); $upload_file_size = $file['size']; return ( $upload_file_size < $file_size_meta ); } /** * validates uploaded file type against allowed file types * * @param array $field * @param $file * * @return bool */ private function is_file_type_valid( $field, $file ) { // File type validation if ( empty( $field['file_types'] ) ) { $field['file_types'] = 'jpg,jpeg,png,gif,pdf,doc,docx,ppt,pptx,odt,avi,ogg,m4a,mov,mp3,mp4,mpg,wav,wmv'; } $file_extension = pathinfo( $file['name'], PATHINFO_EXTENSION ); $file_types_meta = explode( ',', $field['file_types'] ); $file_types_meta = array_map( 'trim', $file_types_meta ); $file_types_meta = array_map( 'strtolower', $file_types_meta ); $file_extension = strtolower( $file_extension ); return ( in_array( $file_extension, $file_types_meta ) && ! in_array( $file_extension, $this->get_blacklist_file_ext() ) ); } /** * A blacklist of file extensions. * * @return array */ private function get_blacklist_file_ext() { static $blacklist = false; if ( ! $blacklist ) { $blacklist = [ 'php', 'php3', 'php4', 'php5', 'php6', 'phps', 'php7', 'phtml', 'shtml', 'pht', 'swf', 'html', 'asp', 'aspx', 'cmd', 'csh', 'bat', 'htm', 'hta', 'jar', 'exe', 'com', 'js', 'lnk', 'htaccess', 'htpasswd', 'phtml', 'ps1', 'ps2', 'py', 'rb', 'tmp', 'cgi', 'svg', 'php2', 'phtm', 'phar', 'hphp', 'phpt', 'svgz', ]; /** * Elementor forms blacklisted file extensions. * * Filters the list of file types that won't be uploaded using Elementor forms. * * By default Elementor forms doesn't upload some file types for security reasons. * This hook allows developers to alter this list, either add more file types to * strengthen the security or remove file types to increase flexibility. * * @since 1.0.0 * * @param array $blacklist A blacklist of file extensions. */ $blacklist = apply_filters( 'elementor_pro/forms/filetypes/blacklist', $blacklist ); } return $blacklist; } /** * validate uploaded file field * * @param array $field * @param Classes\Form_Record $record * @param Classes\Ajax_Handler $ajax_handler */ public function validation( $field, Classes\Form_Record $record, Classes\Ajax_Handler $ajax_handler ) { static $upload_errors = false; if ( ! $upload_errors ) { $upload_errors = [ UPLOAD_ERR_OK => esc_html__( 'There is no error, the file uploaded with success.', 'elementor-pro' ), /* translators: 1: upload_max_filesize, 2: php.ini */ UPLOAD_ERR_INI_SIZE => sprintf( esc_html__( 'The uploaded file exceeds the %1$s directive in %2$s.', 'elementor-pro' ), 'upload_max_filesize', 'php.ini' ), /* translators: %s: MAX_FILE_SIZE */ UPLOAD_ERR_FORM_SIZE => sprintf( esc_html__( 'The uploaded file exceeds the %s directive that was specified in the HTML form.', 'elementor-pro' ), 'MAX_FILE_SIZE' ), UPLOAD_ERR_PARTIAL => esc_html__( 'The uploaded file was only partially uploaded.', 'elementor-pro' ), UPLOAD_ERR_NO_FILE => esc_html__( 'No file was uploaded.', 'elementor-pro' ), UPLOAD_ERR_NO_TMP_DIR => esc_html__( 'Missing a temporary folder.', 'elementor-pro' ), UPLOAD_ERR_CANT_WRITE => esc_html__( 'Failed to write file to disk.', 'elementor-pro' ), /* translators: %s: phpinfo() */ UPLOAD_ERR_EXTENSION => sprintf( esc_html__( 'A PHP extension stopped the file upload. PHP does not provide a way to ascertain which extension caused the file upload to stop; examining the list of loaded extensions with %s may help.', 'elementor-pro' ), 'phpinfo()' ), ]; } $this->fix_file_indices(); $id = $field['id']; $files = Utils::_unstable_get_super_global_value( $_FILES, 'form_fields' ); if ( ! empty( $field['max_files'] ) ) { if ( count( $files[ $id ] ) > $field['max_files'] ) { $error_message = sprintf( /* translators: %d: The number of allowed files. */ _n( 'You can upload only %d file.', 'You can upload up to %d files.', intval( $field['max_files'] ), 'elementor-pro' ), intval( $field['max_files'] ) ); $ajax_handler->add_error( $id, $error_message ); return; } } foreach ( $files[ $id ] as $index => $file ) { // not uploaded if ( ! $field['required'] && UPLOAD_ERR_NO_FILE === $file['error'] ) { return; } // is the file required and missing? if ( $field['required'] && UPLOAD_ERR_NO_FILE === $file['error'] ) { $ajax_handler->add_error( $id, $upload_errors[ $file['error'] ] ); return; } // Has any error with upload the file? if ( $file['error'] > UPLOAD_ERR_OK ) { $ajax_handler->add_error( $id, $upload_errors[ $file['error'] ] ); return; } // valid file type? if ( ! $this->is_file_type_valid( $field, $file ) ) { $ajax_handler->add_error( $id, esc_html__( 'This file type is not allowed.', 'elementor-pro' ) ); } // allowed file size? if ( ! $this->is_file_size_valid( $field, $file ) ) { $ajax_handler->add_error( $id, esc_html__( 'This file exceeds the maximum allowed size.', 'elementor-pro' ) ); } } } /** * Gets the path to uploaded file. * * @return string */ private function get_upload_dir() { $wp_upload_dir = wp_upload_dir(); $path = $wp_upload_dir['basedir'] . '/elementor/forms'; /** * Elementor forms upload file path. * * Filters the path to a file uploaded using Elementor forms. * * By default Elementor forms defines a path to uploaded file. This * hook allows developers to alter this path. * * @since 1.0.0 * * @param string $path Path to uploaded files. */ $path = apply_filters( 'elementor_pro/forms/upload_path', $path ); return $path; } /** * Gets the URL to uploaded file. * * @param $file_name * * @return string */ private function get_file_url( $file_name ) { $wp_upload_dir = wp_upload_dir(); $url = $wp_upload_dir['baseurl'] . '/elementor/forms/' . $file_name; /** * Elementor forms upload file URL. * * Filters the URL to a file uploaded using Elementor forms. * * By default Elementor forms defines a URL to uploaded file. This * hook allows developers to alter this URL. * * @since 1.0.0 * * @param string $url Upload file URL. * @param string $file_name Upload file name. */ $url = apply_filters( 'elementor_pro/forms/upload_url', $url, $file_name ); return $url; } /** * This function returns the uploads folder after making sure * it is created and has protection files * @return string */ private function get_ensure_upload_dir() { $path = $this->get_upload_dir(); if ( file_exists( $path . '/index.php' ) ) { return $path; } wp_mkdir_p( $path ); $files = [ [ 'file' => 'index.php', 'content' => [ '<?php', '// Silence is golden.', ], ], [ 'file' => '.htaccess', 'content' => [ 'Options -Indexes', '<ifModule mod_headers.c>', ' <Files *.*>', ' Header set Content-Disposition attachment', ' </Files>', '</IfModule>', ], ], ]; foreach ( $files as $file ) { if ( ! file_exists( trailingslashit( $path ) . $file['file'] ) ) { $content = implode( PHP_EOL, $file['content'] ); @ file_put_contents( trailingslashit( $path ) . $file['file'], $content ); } } return $path; } /** * creates array of upload sizes based on server limits * to use in the file_sizes control * @return array */ private function get_upload_file_size_options() { $max_file_size = wp_max_upload_size() / pow( 1024, 2 ); //MB $sizes = []; for ( $file_size = 1; $file_size <= $max_file_size; $file_size++ ) { $sizes[ $file_size ] = $file_size . 'MB'; } return $sizes; } /** * process file and move it to uploads directory * * @param array $field * @param Classes\Form_Record $record * @param Classes\Ajax_Handler $ajax_handler */ public function process_field( $field, Classes\Form_Record $record, Classes\Ajax_Handler $ajax_handler ) { $id = $field['id']; $files = Utils::_unstable_get_super_global_value( $_FILES, 'form_fields' ); foreach ( $files[ $id ] as $index => $file ) { if ( UPLOAD_ERR_NO_FILE === $file['error'] ) { continue; } $uploads_dir = $this->get_ensure_upload_dir(); $file_extension = pathinfo( $file['name'], PATHINFO_EXTENSION ); $filename = uniqid() . '.' . $file_extension; $filename = wp_unique_filename( $uploads_dir, $filename ); $new_file = trailingslashit( $uploads_dir ) . $filename; if ( is_dir( $uploads_dir ) && is_writable( $uploads_dir ) ) { $move_new_file = Plugin::instance()->php_api->move_uploaded_file( $file['tmp_name'], $new_file ); if ( false !== $move_new_file ) { // Set correct file permissions. $perms = 0644; @ chmod( $new_file, $perms ); $record->add_file( $id, $index, [ 'path' => $new_file, 'url' => $this->get_file_url( $filename ), ] ); } else { $ajax_handler->add_error( $id, esc_html__( 'There was an error while trying to upload your file.', 'elementor-pro' ) ); } } else { $ajax_handler->add_admin_error_message( esc_html__( 'Upload directory is not writable or does not exist.', 'elementor-pro' ) ); } } } /** * Used to set the upload filed values with * value => file url * raw_value => file path * * @param Classes\Form_Record $record * @param Classes\Ajax_Handler $ajax_handler */ public function set_file_fields_values( Classes\Form_Record $record, Classes\Ajax_Handler $ajax_handler ) { $files = $record->get( 'files' ); if ( empty( $files ) ) { return; } foreach ( $files as $id => $files_array ) { $record->update_field( $id, 'value', implode( ' , ', $files_array['url'] ) ); $record->update_field( $id, 'raw_value', implode( ' , ', $files_array['path'] ) ); } } public function __construct() { parent::__construct(); add_action( 'elementor_pro/forms/process', [ $this, 'set_file_fields_values' ], 10, 2 ); } } fields/field-base.php 0000644 00000005400 14720522625 0010522 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Fields; use ElementorPro\Modules\Forms\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class Field_Base { public $depended_scripts = []; public $depended_styles = []; abstract public function get_type(); abstract public function get_name(); /** * Get the field ID. * * TODO: Make it an abstract function that will replace `get_type()`. * * @since 3.5.0 * * @return string */ public function get_id() { return $this->get_type(); } abstract public function render( $item, $item_index, $form ); public function validation( $field, Classes\Form_Record $record, Classes\Ajax_Handler $ajax_handler ) {} public function process_field( $field, Classes\Form_Record $record, Classes\Ajax_Handler $ajax_handler ) {} public function add_assets_depends( $form ) { foreach ( $this->depended_scripts as $script ) { wp_enqueue_script( $script ); } foreach ( $this->depended_styles as $style ) { wp_enqueue_style( $style ); } } public function add_preview_depends() { foreach ( $this->depended_scripts as $script ) { wp_enqueue_script( $script ); } foreach ( $this->depended_styles as $style ) { wp_enqueue_style( $style ); } } public function add_field_type( $field_types ) { if ( ! in_array( $this->get_type(), $field_types ) ) { $field_types[ $this->get_type() ] = $this->get_name(); } return $field_types; } public function field_render( $item, $item_index, $form ) { $this->add_assets_depends( $form ); $this->render( $item, $item_index, $form ); } public function sanitize_field( $value, $field ) { return sanitize_text_field( $value ); } public function inject_field_controls( $array, $controls_to_inject ) { $keys = array_keys( $array ); $key_index = array_search( 'required', $keys ) + 1; return array_merge( array_slice( $array, 0, $key_index, true ), $controls_to_inject, array_slice( $array, $key_index, null, true ) ); } public function __construct() { $field_type = $this->get_type(); add_action( "elementor_pro/forms/render_field/{$field_type}", [ $this, 'field_render' ], 10, 3 ); add_action( "elementor_pro/forms/validation/{$field_type}", [ $this, 'validation' ], 10, 3 ); add_action( "elementor_pro/forms/process/{$field_type}", [ $this, 'process_field' ], 10, 3 ); add_filter( 'elementor_pro/forms/field_types', [ $this, 'add_field_type' ] ); add_filter( "elementor_pro/forms/sanitize/{$field_type}", [ $this, 'sanitize_field' ], 10, 2 ); add_action( 'elementor/preview/enqueue_scripts', [ $this, 'add_preview_depends' ] ); if ( method_exists( $this, 'update_controls' ) ) { add_action( 'elementor/element/form/section_form_fields/before_section_end', [ $this, 'update_controls' ] ); } } } fields/date.php 0000644 00000006527 14720522625 0007457 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Fields; use Elementor\Controls_Manager; use ElementorPro\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Date extends Field_Base { public $depended_scripts = [ 'flatpickr', ]; public $depended_styles = [ 'flatpickr', ]; public function get_type() { return 'date'; } public function get_name() { return esc_html__( 'Date', 'elementor-pro' ); } public function render( $item, $item_index, $form ) { $form->add_render_attribute( 'input' . $item_index, 'class', 'elementor-field-textual elementor-date-field' ); $form->add_render_attribute( 'input' . $item_index, 'pattern', '[0-9]{4}-[0-9]{2}-[0-9]{2}' ); if ( isset( $item['use_native_date'] ) && 'yes' === $item['use_native_date'] ) { $form->add_render_attribute( 'input' . $item_index, 'class', 'elementor-use-native' ); } if ( ! empty( $item['min_date'] ) ) { $form->add_render_attribute( 'input' . $item_index, 'min', esc_attr( $item['min_date'] ) ); } if ( ! empty( $item['max_date'] ) ) { $form->add_render_attribute( 'input' . $item_index, 'max', esc_attr( $item['max_date'] ) ); } ?> <input <?php $form->print_render_attribute_string( 'input' . $item_index ); ?>> <?php } public function update_controls( $widget ) { $elementor = Plugin::elementor(); $control_data = $elementor->controls_manager->get_control_from_stack( $widget->get_unique_name(), 'form_fields' ); if ( is_wp_error( $control_data ) ) { return; } $field_controls = [ 'min_date' => [ 'name' => 'min_date', 'label' => esc_html__( 'Min. Date', 'elementor-pro' ), 'type' => Controls_Manager::DATE_TIME, 'condition' => [ 'field_type' => $this->get_type(), ], 'label_block' => false, 'picker_options' => [ 'enableTime' => false, ], 'tab' => 'content', 'inner_tab' => 'form_fields_content_tab', 'tabs_wrapper' => 'form_fields_tabs', ], 'max_date' => [ 'name' => 'max_date', 'label' => esc_html__( 'Max. Date', 'elementor-pro' ), 'type' => Controls_Manager::DATE_TIME, 'condition' => [ 'field_type' => $this->get_type(), ], 'label_block' => false, 'picker_options' => [ 'enableTime' => false, ], 'tab' => 'content', 'inner_tab' => 'form_fields_content_tab', 'tabs_wrapper' => 'form_fields_tabs', ], 'use_native_date' => [ 'name' => 'use_native_date', 'label' => esc_html__( 'Native HTML5', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'condition' => [ 'field_type' => $this->get_type(), ], 'tab' => 'content', 'inner_tab' => 'form_fields_content_tab', 'tabs_wrapper' => 'form_fields_tabs', ], ]; foreach ( $control_data['fields'] as $index => $field ) { if ( 'placeholder' !== $field['name'] ) { continue; } foreach ( $field['conditions']['terms'] as $condition_index => $terms ) { if ( ! isset( $terms['name'] ) || 'field_type' !== $terms['name'] || ! isset( $terms['operator'] ) || 'in' !== $terms['operator'] ) { continue; } $control_data['fields'][ $index ]['conditions']['terms'][ $condition_index ]['value'][] = $this->get_type(); break; } break; } $control_data['fields'] = $this->inject_field_controls( $control_data['fields'], $field_controls ); $widget->update_control( 'form_fields', $control_data ); } } fields/acceptance.php 0000644 00000004554 14720522625 0010626 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Fields; use Elementor\Controls_Manager; use ElementorPro\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Acceptance extends Field_Base { public function get_type() { return 'acceptance'; } public function get_name() { return esc_html__( 'Acceptance', 'elementor-pro' ); } public function update_controls( $widget ) { $elementor = Plugin::elementor(); $control_data = $elementor->controls_manager->get_control_from_stack( $widget->get_unique_name(), 'form_fields' ); if ( is_wp_error( $control_data ) ) { return; } $field_controls = [ 'acceptance_text' => [ 'name' => 'acceptance_text', 'label' => esc_html__( 'Acceptance Text', 'elementor-pro' ), 'type' => Controls_Manager::TEXTAREA, 'condition' => [ 'field_type' => $this->get_type(), ], 'tab' => 'content', 'inner_tab' => 'form_fields_content_tab', 'tabs_wrapper' => 'form_fields_tabs', ], 'checked_by_default' => [ 'name' => 'checked_by_default', 'label' => esc_html__( 'Checked by Default', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'condition' => [ 'field_type' => $this->get_type(), ], 'tab' => 'content', 'inner_tab' => 'form_fields_content_tab', 'tabs_wrapper' => 'form_fields_tabs', ], ]; $control_data['fields'] = $this->inject_field_controls( $control_data['fields'], $field_controls ); $widget->update_control( 'form_fields', $control_data ); } public function render( $item, $item_index, $form ) { $label = ''; $form->add_render_attribute( 'input' . $item_index, 'class', 'elementor-acceptance-field' ); $form->add_render_attribute( 'input' . $item_index, 'type', 'checkbox', true ); if ( ! empty( $item['acceptance_text'] ) ) { $label = '<label for="' . $form->get_attribute_id( $item ) . '">' . $item['acceptance_text'] . '</label>'; } if ( ! empty( $item['checked_by_default'] ) ) { $form->add_render_attribute( 'input' . $item_index, 'checked', 'checked' ); } ?> <div class="elementor-field-subgroup"> <span class="elementor-field-option"> <input <?php $form->print_render_attribute_string( 'input' . $item_index ); ?>> <?php // PHPCS - the variables $label is safe. echo $label; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> </span> </div> <?php } } module.php 0000644 00000022644 14720522625 0006557 0 ustar 00 <?php namespace ElementorPro\Modules\Forms; use Elementor\Controls_Manager; use Elementor\Settings; use Elementor\Core\Admin\Admin_Notices; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use ElementorPro\Core\Upgrade\Manager as Upgrade_Manager; use Elementor\User; use ElementorPro\Core\Utils; use ElementorPro\Modules\Forms\Data\Controller; use ElementorPro\Base\Module_Base; use ElementorPro\Modules\Forms\Controls\Fields_Map; use ElementorPro\Modules\Forms\Registrars\Form_Actions_Registrar; use ElementorPro\Modules\Forms\Registrars\Form_Fields_Registrar; use ElementorPro\Modules\Forms\Submissions\Component as Form_Submissions_Component; use ElementorPro\Modules\Forms\Controls\Fields_Repeater; use ElementorPro\Plugin; use ElementorPro\License\API; use ElementorPro\Modules\Forms\Submissions\AdminMenuItems\Submissions_Promotion_Menu_Item; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends Module_Base { /** * @var Form_Actions_Registrar */ public $actions_registrar; /** * @var Form_Fields_Registrar */ public $fields_registrar; const ACTIVITY_LOG_LICENSE_FEATURE_NAME = 'activity-log'; const CF7DB_LICENSE_FEATURE_NAME = 'cf7db'; const AKISMET_LICENSE_FEATURE_NAME = 'akismet'; const WIDGET_NAME_CLASS_NAME_MAP = [ 'form' => 'Form', 'login' => 'Login', ]; public function get_name() { return 'forms'; } public function get_widgets() { return API::filter_active_features( static::WIDGET_NAME_CLASS_NAME_MAP ); } /** * Get the base URL for assets. * * @return string */ public function get_assets_base_url(): string { return ELEMENTOR_PRO_URL; } /** * Register styles. * * At build time, Elementor compiles `/modules/forms/assets/scss/frontend.scss` * to `/assets/css/widget-forms.min.css`. * * @return void */ public function register_styles() { $widget_styles = $this->get_widgets_style_list(); foreach ( $widget_styles as $widget_style_name ) { wp_register_style( $widget_style_name, $this->get_css_assets_url( $widget_style_name, null, true, true ), [ 'elementor-frontend' ], ELEMENTOR_PRO_VERSION ); } } private function get_widgets_style_list(): array { return [ 'widget-form', 'widget-login', ]; } public static function find_element_recursive( $elements, $form_id ) { foreach ( $elements as $element ) { if ( $form_id === $element['id'] ) { return $element; } if ( ! empty( $element['elements'] ) ) { $element = self::find_element_recursive( $element['elements'], $form_id ); if ( $element ) { return $element; } } } return false; } public function register_controls( Controls_Manager $controls_manager ) { $controls_manager->register( new Fields_Repeater() ); $controls_manager->register( new Fields_Map() ); } /** * @param array $data * * @return array * @throws \Exception */ public function forms_panel_action_data( array $data ) { $document = Utils::_unstable_get_document_for_edit( $data['editor_post_id'] ); if ( empty( $data['service'] ) ) { throw new \Exception( 'Service required.' ); } /** @var \ElementorPro\Modules\Forms\Classes\Integration_Base $integration */ $integration = $this->actions_registrar->get( $data['service'] ); if ( ! $integration ) { throw new \Exception( 'Action not found.' ); } return $integration->handle_panel_request( $data ); } /** * @deprecated 3.5.0 Use `fields_registrar->register()` instead. */ public function add_form_field_type( $type, $instance ) { Plugin::elementor()->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'fields_registrar->register()' ); $this->fields_registrar->register( $instance, $type ); } /** * @deprecated 3.5.0 Use `actions_registrar->register()` instead. */ public function add_form_action( $id, $instance ) { Plugin::elementor()->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'actions_registrar->register()' ); $this->actions_registrar->register( $instance, $id ); } /** * @deprecated 3.5.0 Use `actions_registrar->get()` instead. */ public function get_form_actions( $id = null ) { Plugin::elementor()->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'actions_registrar->get()' ); return $this->actions_registrar->get( $id ); } public function register_ajax_actions( Ajax $ajax ) { $ajax->register_ajax_action( 'pro_forms_panel_action_data', [ $this, 'forms_panel_action_data' ] ); } /** * Register submissions */ private function register_submissions_component() { $name = Form_Submissions_Component::NAME; if ( is_admin() ) { add_action( 'elementor/admin/after_create_settings/' . Settings::PAGE_ID, [ $this, 'register_submissions_admin_fields' ] ); } if ( '1' === get_option( 'elementor_' . $name ) ) { return; } if ( current_user_can( 'manage_options' ) ) { add_action( 'admin_notices', [ $this, 'register_submissions_admin_notice' ] ); } $this->add_component( $name, new Form_Submissions_Component() ); } public function register_submissions_admin_fields( Settings $settings ) { $settings->add_field( Settings::TAB_ADVANCED, Settings::TAB_ADVANCED, Form_Submissions_Component::NAME, [ 'label' => esc_html__( 'Form Submissions', 'elementor-pro' ), 'field_args' => [ 'type' => 'select', 'std' => '', 'options' => [ '' => esc_html__( 'Enable', 'elementor-pro' ), '1' => esc_html__( 'Disable', 'elementor-pro' ), ], 'desc' => esc_html__( 'Never lose another submission! Using “Actions After Submit” you can now choose to save all submissions to an internal database.', 'elementor-pro' ), ], ], ); } public function register_submissions_admin_notice() { $notice_id = 'elementor-pro-forms-submissions'; if ( User::is_user_notice_viewed( $notice_id ) ) { return; } $is_new_site = Upgrade_Manager::install_compare( '3.25.0', '>=' ); if ( $is_new_site ) { return; } /** * @var Admin_Notices $admin_notices */ $admin_notices = Plugin::elementor()->admin->get_component( 'admin-notices' ); $dismiss_url = add_query_arg( [ 'action' => 'elementor_set_admin_notice_viewed', 'notice_id' => esc_attr( $notice_id ), ], admin_url( 'admin-post.php' ) ); $admin_notices->print_admin_notice( [ 'id' => $notice_id, 'title' => esc_html__( 'Form Submissions now activated by default on all websites', 'elementor-pro' ), 'description' => sprintf( esc_html__( 'The Form Submissions feature, previously located under the Features tab in Elementor, is now enabled by default on all websites. If you prefer to disable this feature, you can do so by navigating to %1$sSettings → Advanced%2$s.', 'elementor-pro' ), '<a href="' . esc_url( admin_url( 'admin.php?page=elementor-settings#tab-' . Settings::TAB_ADVANCED ) ) . '"><strong>', '</strong></a>' ), 'button' => [ 'text' => esc_html__( 'Got it! Keep it enabled', 'elementor-pro' ), 'classes' => [ 'e-notice-dismiss' ], 'url' => esc_url_raw( $dismiss_url ), 'type' => 'cta', ], ] ); } /** * Module constructor. */ public function __construct() { parent::__construct(); add_action( 'elementor/frontend/after_register_styles', [ $this, 'register_styles' ] ); add_action( 'elementor/controls/register', [ $this, 'register_controls' ] ); add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] ); $this->add_component( 'recaptcha', new Classes\Recaptcha_Handler() ); $this->add_component( 'recaptcha_v3', new Classes\Recaptcha_V3_Handler() ); $this->add_component( 'honeypot', new Classes\Honeypot_Handler() ); // Akismet if ( class_exists( '\Akismet' ) && API::is_licence_has_feature( static::AKISMET_LICENSE_FEATURE_NAME, API::BC_VALIDATION_CALLBACK ) ) { $this->add_component( 'akismet', new Classes\Akismet() ); } if ( API::is_licence_has_feature( Form_Submissions_Component::NAME, API::BC_VALIDATION_CALLBACK ) ) { $this->register_submissions_component(); } else { add_action( 'elementor/admin/menu/register', function( $admin_menu ) { $admin_menu->register( Form_Submissions_Component::PAGE_ID, new Submissions_Promotion_Menu_Item() ); }, 9 /* After "Settings" */ ); } // Initialize registrars. $this->actions_registrar = new Form_Actions_Registrar(); $this->fields_registrar = new Form_Fields_Registrar(); // Add Actions as components, that runs manually in the Ajax_Handler // Activity Log if ( function_exists( 'aal_insert_log' ) && API::is_licence_has_feature( static::ACTIVITY_LOG_LICENSE_FEATURE_NAME, API::BC_VALIDATION_CALLBACK ) ) { $this->add_component( 'activity_log', new Actions\Activity_Log() ); } // Contact Form to Database if ( function_exists( 'CF7DBPlugin_init' ) && API::is_licence_has_feature( static::CF7DB_LICENSE_FEATURE_NAME, API::BC_VALIDATION_CALLBACK ) ) { $this->add_component( 'cf7db', new Actions\CF7DB() ); } // Ajax Handler if ( Classes\Ajax_Handler::is_form_submitted() ) { $this->add_component( 'ajax_handler', new Classes\Ajax_Handler() ); /** * Elementor form submitted. * * Fires when the form is submitted. This hook allows developers * to add functionality after form submission. * * @since 2.0.0 * * @param Module $this An instance of the form module. */ do_action( 'elementor_pro/forms/form_submitted', $this ); } } } widgets/form.php 0000644 00000221374 14720522625 0007704 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Widgets; use Elementor\Controls_Manager; use Elementor\Core\Kits\Documents\Tabs\Global_Colors; use Elementor\Core\Kits\Documents\Tabs\Global_Typography; use Elementor\Group_Control_Border; use Elementor\Group_Control_Typography; use Elementor\Icons_Manager; use Elementor\Repeater; use ElementorPro\Core\Utils; use ElementorPro\Modules\Forms\Classes\Ajax_Handler; use ElementorPro\Modules\Forms\Classes\Form_Base; use ElementorPro\Modules\Forms\Controls\Fields_Repeater; use ElementorPro\Modules\Forms\Module; use ElementorPro\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Form extends Form_Base { public function get_name() { return 'form'; } public function get_title() { return esc_html__( 'Form', 'elementor-pro' ); } public function get_icon() { return 'eicon-form-horizontal'; } public function get_keywords() { return [ 'form', 'forms', 'field', 'button', 'mailchimp', 'drip', 'mailpoet', 'convertkit', 'getresponse', 'recaptcha', 'zapier', 'webhook', 'activecampaign', 'slack', 'discord', 'mailerlite' ]; } protected function is_dynamic_content(): bool { return false; } /** * Get style dependencies. * * Retrieve the list of style dependencies the widget requires. * * @since 3.24.0 * @access public * * @return array Widget style dependencies. */ public function get_style_depends(): array { return [ 'widget-form' ]; } protected function register_controls() { $repeater = new Repeater(); $field_types = [ 'text' => esc_html__( 'Text', 'elementor-pro' ), 'email' => esc_html__( 'Email', 'elementor-pro' ), 'textarea' => esc_html__( 'Textarea', 'elementor-pro' ), 'url' => esc_html__( 'URL', 'elementor-pro' ), 'tel' => esc_html__( 'Tel', 'elementor-pro' ), 'radio' => esc_html__( 'Radio', 'elementor-pro' ), 'select' => esc_html__( 'Select', 'elementor-pro' ), 'checkbox' => esc_html__( 'Checkbox', 'elementor-pro' ), 'acceptance' => esc_html__( 'Acceptance', 'elementor-pro' ), 'number' => esc_html__( 'Number', 'elementor-pro' ), 'date' => esc_html__( 'Date', 'elementor-pro' ), 'time' => esc_html__( 'Time', 'elementor-pro' ), 'upload' => esc_html__( 'File Upload', 'elementor-pro' ), 'password' => esc_html__( 'Password', 'elementor-pro' ), 'html' => esc_html__( 'HTML', 'elementor-pro' ), 'hidden' => esc_html__( 'Hidden', 'elementor-pro' ), ]; /** * Forms field types. * * Filters the list of field types displayed in the form `field_type` control. * * This hook allows developers to alter the list of displayed field types. For * example, removing the 'upload' field type from the list of fields types will * prevent uploading files using Elementor forms. * * @since 1.0.0 * * @param array $field_types Field types. */ $field_types = apply_filters( 'elementor_pro/forms/field_types', $field_types ); $repeater->start_controls_tabs( 'form_fields_tabs' ); $repeater->start_controls_tab( 'form_fields_content_tab', [ 'label' => esc_html__( 'Content', 'elementor-pro' ), ] ); $repeater->add_control( 'field_type', [ 'label' => esc_html__( 'Type', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'options' => $field_types, 'default' => 'text', ] ); $repeater->add_control( 'field_label', [ 'label' => esc_html__( 'Label', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'default' => '', 'dynamic' => [ 'active' => true, ], ] ); $repeater->add_control( 'placeholder', [ 'label' => esc_html__( 'Placeholder', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'default' => '', 'conditions' => [ 'terms' => [ [ 'name' => 'field_type', 'operator' => 'in', 'value' => [ 'tel', 'text', 'email', 'textarea', 'number', 'url', 'password', ], ], ], ], 'dynamic' => [ 'active' => true, ], ] ); $repeater->add_control( 'required', [ 'label' => esc_html__( 'Required', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'true', 'default' => '', 'conditions' => [ 'terms' => [ [ 'name' => 'field_type', 'operator' => '!in', 'value' => [ 'checkbox', 'recaptcha', 'recaptcha_v3', 'hidden', 'html', 'step', ], ], ], ], ] ); $repeater->add_control( 'field_options', [ 'label' => esc_html__( 'Options', 'elementor-pro' ), 'type' => Controls_Manager::TEXTAREA, 'default' => '', 'description' => esc_html__( 'Enter each option in a separate line. To differentiate between label and value, separate them with a pipe char ("|"). For example: First Name|f_name', 'elementor-pro' ), 'conditions' => [ 'terms' => [ [ 'name' => 'field_type', 'operator' => 'in', 'value' => [ 'select', 'checkbox', 'radio', ], ], ], ], ] ); $repeater->add_control( 'allow_multiple', [ 'label' => esc_html__( 'Multiple Selection', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'true', 'conditions' => [ 'terms' => [ [ 'name' => 'field_type', 'value' => 'select', ], ], ], ] ); $repeater->add_control( 'select_size', [ 'label' => esc_html__( 'Rows', 'elementor-pro' ), 'type' => Controls_Manager::NUMBER, 'min' => 2, 'step' => 1, 'conditions' => [ 'terms' => [ [ 'name' => 'field_type', 'value' => 'select', ], [ 'name' => 'allow_multiple', 'value' => 'true', ], ], ], ] ); $repeater->add_control( 'inline_list', [ 'label' => esc_html__( 'Inline List', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'elementor-subgroup-inline', 'default' => '', 'conditions' => [ 'terms' => [ [ 'name' => 'field_type', 'operator' => 'in', 'value' => [ 'checkbox', 'radio', ], ], ], ], ] ); $repeater->add_control( 'field_html', [ 'label' => esc_html__( 'HTML', 'elementor-pro' ), 'type' => Controls_Manager::TEXTAREA, 'dynamic' => [ 'active' => true, ], 'conditions' => [ 'terms' => [ [ 'name' => 'field_type', 'value' => 'html', ], ], ], ] ); $repeater->add_responsive_control( 'width', [ 'label' => esc_html__( 'Column Width', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'options' => [ '' => esc_html__( 'Default', 'elementor-pro' ), '100' => '100%', '80' => '80%', '75' => '75%', '70' => '70%', '66' => '66%', '60' => '60%', '50' => '50%', '40' => '40%', '33' => '33%', '30' => '30%', '25' => '25%', '20' => '20%', ], 'default' => '100', 'conditions' => [ 'terms' => [ [ 'name' => 'field_type', 'operator' => '!in', 'value' => [ 'hidden', 'recaptcha', 'recaptcha_v3', 'step', ], ], ], ], ] ); $repeater->add_control( 'rows', [ 'label' => esc_html__( 'Rows', 'elementor-pro' ), 'type' => Controls_Manager::NUMBER, 'default' => 4, 'conditions' => [ 'terms' => [ [ 'name' => 'field_type', 'value' => 'textarea', ], ], ], ] ); $repeater->add_control( 'recaptcha_size', [ 'label' => esc_html__( 'Size', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'default' => 'normal', 'options' => [ 'normal' => esc_html__( 'Normal', 'elementor-pro' ), 'compact' => esc_html__( 'Compact', 'elementor-pro' ), ], 'conditions' => [ 'terms' => [ [ 'name' => 'field_type', 'value' => 'recaptcha', ], ], ], ] ); $repeater->add_control( 'recaptcha_style', [ 'label' => esc_html__( 'Style', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'default' => 'light', 'options' => [ 'light' => esc_html__( 'Light', 'elementor-pro' ), 'dark' => esc_html__( 'Dark', 'elementor-pro' ), ], 'conditions' => [ 'terms' => [ [ 'name' => 'field_type', 'value' => 'recaptcha', ], ], ], ] ); $repeater->add_control( 'recaptcha_badge', [ 'label' => esc_html__( 'Badge', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'default' => 'bottomright', 'options' => [ 'bottomright' => esc_html__( 'Bottom Right', 'elementor-pro' ), 'bottomleft' => esc_html__( 'Bottom Left', 'elementor-pro' ), 'inline' => esc_html__( 'Inline', 'elementor-pro' ), ], 'description' => esc_html__( 'To view the validation badge, switch to preview mode', 'elementor-pro' ), 'conditions' => [ 'terms' => [ [ 'name' => 'field_type', 'value' => 'recaptcha_v3', ], ], ], ] ); $repeater->add_control( 'css_classes', [ 'label' => esc_html__( 'CSS Classes', 'elementor-pro' ), 'type' => Controls_Manager::HIDDEN, 'default' => '', 'title' => esc_html__( 'Add your custom class WITHOUT the dot. e.g: my-class', 'elementor-pro' ), ] ); $repeater->end_controls_tab(); $repeater->start_controls_tab( 'form_fields_advanced_tab', [ 'label' => esc_html__( 'Advanced', 'elementor-pro' ), 'condition' => [ 'field_type!' => 'html', ], ] ); $repeater->add_control( 'field_value', [ 'label' => esc_html__( 'Default Value', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'default' => '', 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], 'conditions' => [ 'terms' => [ [ 'name' => 'field_type', 'operator' => 'in', 'value' => [ 'text', 'email', 'textarea', 'url', 'tel', 'radio', 'select', 'number', 'date', 'time', 'hidden', ], ], ], ], ] ); $repeater->add_control( 'custom_id', [ 'label' => esc_html__( 'ID', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'description' => sprintf( esc_html__( 'Please make sure the ID is unique and not used elsewhere on the page. This field allows %1$sA-z 0-9%2$s & underscore chars without spaces.', 'elementor-pro' ), '<code>', '</code>' ), 'render_type' => 'none', 'required' => true, 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], ] ); $shortcode_template = '{{ view.container.settings.get( \'custom_id\' ) }}'; $repeater->add_control( 'shortcode', [ 'label' => esc_html__( 'Shortcode', 'elementor-pro' ), 'type' => Controls_Manager::RAW_HTML, 'classes' => 'forms-field-shortcode', 'raw' => '<input class="elementor-form-field-shortcode" value=\'[field id="' . $shortcode_template . '"]\' readonly />', ] ); $repeater->end_controls_tab(); $repeater->end_controls_tabs(); $this->start_controls_section( 'section_form_fields', [ 'label' => esc_html__( 'Form Fields', 'elementor-pro' ), ] ); $this->add_control( 'form_name', [ 'label' => esc_html__( 'Form Name', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'default' => esc_html__( 'New Form', 'elementor-pro' ), 'placeholder' => esc_html__( 'Form Name', 'elementor-pro' ), ] ); $this->add_control( 'form_fields', [ 'type' => Fields_Repeater::CONTROL_TYPE, 'fields' => $repeater->get_controls(), 'default' => [ [ 'custom_id' => 'name', 'field_type' => 'text', 'field_label' => esc_html__( 'Name', 'elementor-pro' ), 'placeholder' => esc_html__( 'Name', 'elementor-pro' ), 'width' => '100', 'dynamic' => [ 'active' => true, ], ], [ 'custom_id' => 'email', 'field_type' => 'email', 'required' => 'true', 'field_label' => esc_html__( 'Email', 'elementor-pro' ), 'placeholder' => esc_html__( 'Email', 'elementor-pro' ), 'width' => '100', ], [ 'custom_id' => 'message', 'field_type' => 'textarea', 'field_label' => esc_html__( 'Message', 'elementor-pro' ), 'placeholder' => esc_html__( 'Message', 'elementor-pro' ), 'width' => '100', ], ], 'title_field' => '{{{ field_label }}}', ] ); $this->add_control( 'input_size', [ 'label' => esc_html__( 'Input Size', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'options' => [ 'xs' => esc_html__( 'Extra Small', 'elementor-pro' ), 'sm' => esc_html__( 'Small', 'elementor-pro' ), 'md' => esc_html__( 'Medium', 'elementor-pro' ), 'lg' => esc_html__( 'Large', 'elementor-pro' ), 'xl' => esc_html__( 'Extra Large', 'elementor-pro' ), ], 'default' => 'sm', 'separator' => 'before', ] ); $this->add_control( 'show_labels', [ 'label' => esc_html__( 'Label', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'Show', 'elementor-pro' ), 'label_off' => esc_html__( 'Hide', 'elementor-pro' ), 'return_value' => 'true', 'default' => 'true', 'separator' => 'before', ] ); $this->add_control( 'mark_required', [ 'label' => esc_html__( 'Required Mark', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'Show', 'elementor-pro' ), 'label_off' => esc_html__( 'Hide', 'elementor-pro' ), 'default' => '', 'condition' => [ 'show_labels!' => '', ], ] ); $this->add_control( 'label_position', [ 'label' => esc_html__( 'Label Position', 'elementor-pro' ), 'type' => Controls_Manager::HIDDEN, 'options' => [ 'above' => esc_html__( 'Above', 'elementor-pro' ), 'inline' => esc_html__( 'Inline', 'elementor-pro' ), ], 'default' => 'above', 'condition' => [ 'show_labels!' => '', ], ] ); $this->end_controls_section(); $this->start_controls_section( 'section_buttons', [ 'label' => esc_html__( 'Buttons', 'elementor-pro' ), ] ); $this->add_control( 'button_size', [ 'label' => esc_html__( 'Size', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'default' => 'sm', 'options' => self::get_button_sizes(), ] ); $this->add_responsive_control( 'button_width', [ 'label' => esc_html__( 'Column Width', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'options' => [ '' => esc_html__( 'Default', 'elementor-pro' ), '100' => '100%', '80' => '80%', '75' => '75%', '70' => '70%', '66' => '66%', '60' => '60%', '50' => '50%', '40' => '40%', '33' => '33%', '30' => '30%', '25' => '25%', '20' => '20%', ], 'default' => '100', 'frontend_available' => true, ] ); $this->add_control( 'heading_steps_buttons', [ 'label' => esc_html__( 'Step Buttons', 'elementor-pro' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_control( 'step_next_label', [ 'label' => esc_html__( 'Next', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], 'frontend_available' => true, 'render_type' => 'none', 'default' => esc_html__( 'Next', 'elementor-pro' ), 'placeholder' => esc_html__( 'Next', 'elementor-pro' ), ] ); $this->add_control( 'step_previous_label', [ 'label' => esc_html__( 'Previous', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], 'frontend_available' => true, 'render_type' => 'none', 'default' => esc_html__( 'Previous', 'elementor-pro' ), 'placeholder' => esc_html__( 'Previous', 'elementor-pro' ), ] ); $this->add_control( 'heading_submit_button', [ 'label' => esc_html__( 'Submit Button', 'elementor-pro' ), 'type' => Controls_Manager::HEADING, ] ); $this->add_control( 'button_text', [ 'label' => esc_html__( 'Submit', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'default' => esc_html__( 'Send', 'elementor-pro' ), 'placeholder' => esc_html__( 'Send', 'elementor-pro' ), 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], ] ); $this->add_control( 'selected_button_icon', [ 'label' => esc_html__( 'Icon', 'elementor-pro' ), 'type' => Controls_Manager::ICONS, 'skin' => 'inline', 'label_block' => false, ] ); $start = is_rtl() ? 'right' : 'left'; $end = is_rtl() ? 'left' : 'right'; $this->add_control( 'button_icon_align', [ 'label' => esc_html__( 'Icon Position', 'elementor-pro' ), 'type' => Controls_Manager::CHOOSE, 'default' => is_rtl() ? 'row-reverse' : 'row', 'options' => [ 'row' => [ 'title' => esc_html__( 'Start', 'elementor-pro' ), 'icon' => "eicon-h-align-{$start}", ], 'row-reverse' => [ 'title' => esc_html__( 'End', 'elementor-pro' ), 'icon' => "eicon-h-align-{$end}", ], ], 'selectors_dictionary' => [ 'left' => is_rtl() ? 'row-reverse' : 'row', 'right' => is_rtl() ? 'row' : 'row-reverse', ], 'selectors' => [ '{{WRAPPER}} .elementor-button-content-wrapper' => 'flex-direction: {{VALUE}};', ], 'condition' => [ 'button_text!' => '', 'selected_button_icon[value]!' => '', ], ] ); $this->add_control( 'button_icon_indent', [ 'label' => esc_html__( 'Icon Spacing', 'elementor-pro' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'custom' ], 'range' => [ 'px' => [ 'max' => 100, ], 'em' => [ 'max' => 10, ], 'rem' => [ 'max' => 10, ], ], 'condition' => [ 'button_text!' => '', 'selected_button_icon[value]!' => '', ], 'selectors' => [ '{{WRAPPER}} .elementor-button span' => 'gap: {{SIZE}}{{UNIT}};', ], ] ); $this->add_control( 'button_css_id', [ 'label' => esc_html__( 'Button ID', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'default' => '', 'ai' => [ 'active' => false, ], 'title' => esc_html__( 'Add your custom id WITHOUT the Pound key. e.g: my-id', 'elementor-pro' ), 'description' => sprintf( esc_html__( 'Please make sure the ID is unique and not used elsewhere on the page. This field allows %1$sA-z 0-9%2$s & underscore chars without spaces.', 'elementor-pro' ), '<code>', '</code>' ), 'separator' => 'before', 'dynamic' => [ 'active' => true, ], ] ); $this->end_controls_section(); $this->start_controls_section( 'section_integration', [ 'label' => esc_html__( 'Actions After Submit', 'elementor-pro' ), ] ); $actions = Module::instance()->actions_registrar->get(); $actions_options = []; foreach ( $actions as $action ) { $actions_options[ $action->get_name() ] = $action->get_label(); } $default_submit_actions = [ 'email' ]; /** * Default submit actions. * * Filters the list of submit actions pre deffined by Elementor forms. * * By default, only one submit action is set by Elementor forms, an 'email' * action. This hook allows developers to alter those submit action. * * @param array $default_submit_actions A list of default submit actions. */ $default_submit_actions = apply_filters( 'elementor_pro/forms/default_submit_actions', $default_submit_actions ); $this->add_control( 'submit_actions', [ 'label' => esc_html__( 'Add Action', 'elementor-pro' ), 'type' => Controls_Manager::SELECT2, 'multiple' => true, 'options' => $actions_options, 'render_type' => 'none', 'label_block' => true, 'default' => $default_submit_actions, 'description' => esc_html__( 'Add actions that will be performed after a visitor submits the form (e.g. send an email notification). Choosing an action will add its setting below.', 'elementor-pro' ), ] ); $this->end_controls_section(); foreach ( $actions as $action ) { $action->register_settings_section( $this ); } // Steps settings. $this->start_controls_section( 'section_steps_settings', [ 'label' => esc_html__( 'Steps Settings', 'elementor-pro' ), 'tab' => Controls_Manager::TAB_CONTENT, ] ); $this->add_control( 'step_type', [ 'label' => esc_html__( 'Type', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'frontend_available' => true, 'render_type' => 'none', 'options' => [ 'none' => 'None', 'text' => 'Text', 'icon' => 'Icon', 'number' => 'Number', 'progress_bar' => 'Progress Bar', 'number_text' => 'Number & Text', 'icon_text' => 'Icon & Text', ], 'default' => 'number_text', ] ); $this->add_control( 'step_icon_shape', [ 'label' => esc_html__( 'Shape', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'frontend_available' => true, 'render_type' => 'none', 'options' => [ 'circle' => 'Circle', 'square' => 'Square', 'rounded' => 'Rounded', 'none' => 'None', ], 'default' => 'circle', 'conditions' => [ 'terms' => [ [ 'name' => 'step_type', 'operator' => '!in', 'value' => [ 'progress_bar', 'text', ], ], ], ], ] ); $repeater->add_control( 'display_percentage', [ 'label' => esc_html__( 'Display Percentage', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'frontend_available' => true, 'render_type' => 'none', 'return_value' => 'true', 'default' => '', 'condition' => [ 'step_type' => 'progress_bar', ], ] ); // End of steps settings. $this->end_controls_section(); $this->start_controls_section( 'section_form_options', [ 'label' => esc_html__( 'Additional Options', 'elementor-pro' ), 'tab' => Controls_Manager::TAB_CONTENT, ] ); $this->add_control( 'form_id', [ 'label' => esc_html__( 'Form ID', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'ai' => [ 'active' => false, ], 'placeholder' => 'new_form_id', 'description' => sprintf( esc_html__( 'Please make sure the ID is unique and not used elsewhere on the page. This field allows %1$sA-z 0-9%2$s & underscore chars without spaces.', 'elementor-pro' ), '<code>', '</code>' ), 'separator' => 'after', 'dynamic' => [ 'active' => true, ], ] ); $this->add_control( 'form_validation', [ 'label' => esc_html__( 'Form Validation', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'options' => [ '' => esc_html__( 'Browser Default', 'elementor-pro' ), 'custom' => esc_html__( 'Custom', 'elementor-pro' ), ], 'default' => '', ] ); $this->add_control( 'custom_messages', [ 'label' => esc_html__( 'Custom Messages', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'default' => '', 'separator' => 'before', 'render_type' => 'none', ] ); $default_messages = Ajax_Handler::get_default_messages(); $this->add_control( 'success_message', [ 'label' => esc_html__( 'Success Message', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'default' => $default_messages[ Ajax_Handler::SUCCESS ], 'placeholder' => $default_messages[ Ajax_Handler::SUCCESS ], 'label_block' => true, 'condition' => [ 'custom_messages!' => '', ], 'render_type' => 'none', 'dynamic' => [ 'active' => true, ], ] ); $this->add_control( 'error_message', [ 'label' => esc_html__( 'Form Error', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'default' => $default_messages[ Ajax_Handler::ERROR ], 'placeholder' => $default_messages[ Ajax_Handler::ERROR ], 'label_block' => true, 'condition' => [ 'custom_messages!' => '', ], 'render_type' => 'none', 'dynamic' => [ 'active' => true, ], ] ); $this->add_control( 'server_message', [ 'label' => esc_html__( 'Server Error', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'default' => $default_messages[ Ajax_Handler::SERVER_ERROR ], 'placeholder' => $default_messages[ Ajax_Handler::SERVER_ERROR ], 'label_block' => true, 'condition' => [ 'custom_messages!' => '', ], 'render_type' => 'none', 'dynamic' => [ 'active' => true, ], ] ); $this->add_control( 'invalid_message', [ 'label' => esc_html__( 'Invalid Form', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'default' => $default_messages[ Ajax_Handler::INVALID_FORM ], 'placeholder' => $default_messages[ Ajax_Handler::INVALID_FORM ], 'label_block' => true, 'condition' => [ 'custom_messages!' => '', ], 'render_type' => 'none', 'dynamic' => [ 'active' => true, ], ] ); $this->add_control( 'required_field_message', [ 'label' => esc_html__( 'Required Field', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'default' => $default_messages[ Ajax_Handler::FIELD_REQUIRED ], 'placeholder' => $default_messages[ Ajax_Handler::FIELD_REQUIRED ], 'label_block' => true, 'condition' => [ 'custom_messages!' => '', 'form_validation' => 'custom', ], 'render_type' => 'none', 'dynamic' => [ 'active' => true, ], ] ); $this->end_controls_section(); $this->start_controls_section( 'section_form_style', [ 'label' => esc_html__( 'Form', 'elementor-pro' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_control( 'column_gap', [ 'label' => esc_html__( 'Columns Gap', 'elementor-pro' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'custom' ], 'default' => [ 'size' => 10, ], 'range' => [ 'px' => [ 'max' => 60, ], 'em' => [ 'max' => 6, ], 'rem' => [ 'max' => 6, ], ], 'selectors' => [ '{{WRAPPER}} .elementor-field-group' => 'padding-right: calc( {{SIZE}}{{UNIT}}/2 ); padding-left: calc( {{SIZE}}{{UNIT}}/2 );', '{{WRAPPER}} .elementor-form-fields-wrapper' => 'margin-left: calc( -{{SIZE}}{{UNIT}}/2 ); margin-right: calc( -{{SIZE}}{{UNIT}}/2 );', ], ] ); $this->add_control( 'row_gap', [ 'label' => esc_html__( 'Rows Gap', 'elementor-pro' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'custom' ], 'default' => [ 'size' => 10, ], 'range' => [ 'px' => [ 'max' => 60, ], 'em' => [ 'max' => 6, ], 'rem' => [ 'max' => 6, ], ], 'selectors' => [ '{{WRAPPER}} .elementor-field-group' => 'margin-bottom: {{SIZE}}{{UNIT}};', '{{WRAPPER}} .elementor-field-group.recaptcha_v3-bottomleft, {{WRAPPER}} .elementor-field-group.recaptcha_v3-bottomright' => 'margin-bottom: 0;', '{{WRAPPER}} .elementor-form-fields-wrapper' => 'margin-bottom: -{{SIZE}}{{UNIT}};', ], ] ); $this->add_control( 'heading_label', [ 'label' => esc_html__( 'Label', 'elementor-pro' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_control( 'label_spacing', [ 'label' => esc_html__( 'Spacing', 'elementor-pro' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'custom' ], 'default' => [ 'size' => 0, ], 'range' => [ 'px' => [ 'max' => 60, ], 'em' => [ 'max' => 6, ], 'rem' => [ 'max' => 6, ], ], 'selectors' => [ 'body.rtl {{WRAPPER}} .elementor-labels-inline .elementor-field-group > label' => 'padding-left: {{SIZE}}{{UNIT}};', // for the label position = inline option 'body:not(.rtl) {{WRAPPER}} .elementor-labels-inline .elementor-field-group > label' => 'padding-right: {{SIZE}}{{UNIT}};', // for the label position = inline option 'body {{WRAPPER}} .elementor-labels-above .elementor-field-group > label' => 'padding-bottom: {{SIZE}}{{UNIT}};', // for the label position = above option ], ] ); $this->add_control( 'label_color', [ 'label' => esc_html__( 'Text Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .elementor-field-group > label, {{WRAPPER}} .elementor-field-subgroup label' => 'color: {{VALUE}};', ], 'global' => [ 'default' => Global_Colors::COLOR_TEXT, ], ] ); $this->add_control( 'mark_required_color', [ 'label' => esc_html__( 'Mark Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => [ '{{WRAPPER}} .elementor-mark-required .elementor-field-label:after' => 'color: {{COLOR}};', ], 'condition' => [ 'mark_required' => 'yes', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'label_typography', 'selector' => '{{WRAPPER}} .elementor-field-group > label', 'global' => [ 'default' => Global_Typography::TYPOGRAPHY_TEXT, ], ] ); $this->add_control( 'heading_html', [ 'label' => esc_html__( 'HTML Field', 'elementor-pro' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_control( 'html_spacing', [ 'label' => esc_html__( 'Spacing', 'elementor-pro' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'custom' ], 'default' => [ 'size' => 0, ], 'range' => [ 'px' => [ 'max' => 60, ], 'em' => [ 'max' => 6, ], 'rem' => [ 'max' => 6, ], ], 'selectors' => [ '{{WRAPPER}} .elementor-field-type-html' => 'padding-bottom: {{SIZE}}{{UNIT}};', ], ] ); $this->add_control( 'html_color', [ 'label' => esc_html__( 'Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .elementor-field-type-html' => 'color: {{VALUE}};', ], 'global' => [ 'default' => Global_Colors::COLOR_TEXT, ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'html_typography', 'selector' => '{{WRAPPER}} .elementor-field-type-html', 'global' => [ 'default' => Global_Typography::TYPOGRAPHY_TEXT, ], ] ); $this->end_controls_section(); $this->start_controls_section( 'section_field_style', [ 'label' => esc_html__( 'Field', 'elementor-pro' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_control( 'field_text_color', [ 'label' => esc_html__( 'Text Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .elementor-field-group .elementor-field' => 'color: {{VALUE}};', ], 'global' => [ 'default' => Global_Colors::COLOR_TEXT, ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'field_typography', 'selector' => '{{WRAPPER}} .elementor-field-group .elementor-field, {{WRAPPER}} .elementor-field-subgroup label', 'global' => [ 'default' => Global_Typography::TYPOGRAPHY_TEXT, ], ] ); $this->add_control( 'field_background_color', [ 'label' => esc_html__( 'Background Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '#ffffff', 'selectors' => [ '{{WRAPPER}} .elementor-field-group:not(.elementor-field-type-upload) .elementor-field:not(.elementor-select-wrapper)' => 'background-color: {{VALUE}};', '{{WRAPPER}} .elementor-field-group .elementor-select-wrapper select' => 'background-color: {{VALUE}};', ], 'separator' => 'before', ] ); $this->add_control( 'field_border_color', [ 'label' => esc_html__( 'Border Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .elementor-field-group:not(.elementor-field-type-upload) .elementor-field:not(.elementor-select-wrapper)' => 'border-color: {{VALUE}};', '{{WRAPPER}} .elementor-field-group .elementor-select-wrapper select' => 'border-color: {{VALUE}};', '{{WRAPPER}} .elementor-field-group .elementor-select-wrapper::before' => 'color: {{VALUE}};', ], 'separator' => 'before', ] ); $this->add_control( 'field_border_width', [ 'label' => esc_html__( 'Border Width', 'elementor-pro' ), 'type' => Controls_Manager::DIMENSIONS, 'placeholder' => '1', 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}} .elementor-field-group:not(.elementor-field-type-upload) .elementor-field:not(.elementor-select-wrapper)' => 'border-width: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', '{{WRAPPER}} .elementor-field-group .elementor-select-wrapper select' => 'border-width: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); $this->add_control( 'field_border_radius', [ 'label' => esc_html__( 'Border Radius', 'elementor-pro' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ], 'selectors' => [ '{{WRAPPER}} .elementor-field-group:not(.elementor-field-type-upload) .elementor-field:not(.elementor-select-wrapper)' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', '{{WRAPPER}} .elementor-field-group .elementor-select-wrapper select' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); $this->end_controls_section(); $this->start_controls_section( 'section_button_style', [ 'label' => esc_html__( 'Buttons', 'elementor-pro' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_responsive_control( 'button_align', [ 'label' => esc_html__( 'Position', 'elementor-pro' ), 'type' => Controls_Manager::CHOOSE, 'options' => [ 'start' => [ 'title' => esc_html__( 'Left', 'elementor-pro' ), 'icon' => 'eicon-h-align-left', ], 'center' => [ 'title' => esc_html__( 'Center', 'elementor-pro' ), 'icon' => 'eicon-h-align-center', ], 'end' => [ 'title' => esc_html__( 'Right', 'elementor-pro' ), 'icon' => 'eicon-h-align-right', ], 'stretch' => [ 'title' => esc_html__( 'Stretch', 'elementor-pro' ), 'icon' => 'eicon-h-align-stretch', ], ], 'default' => 'stretch', 'prefix_class' => 'elementor%s-button-align-', ] ); $this->add_responsive_control( 'button_content_align', [ 'label' => esc_html__( 'Alignment', 'elementor-pro' ), 'type' => Controls_Manager::CHOOSE, 'options' => [ 'start' => [ 'title' => esc_html__( 'Start', 'elementor-pro' ), 'icon' => "eicon-text-align-{$start}", ], 'center' => [ 'title' => esc_html__( 'Center', 'elementor-pro' ), 'icon' => 'eicon-text-align-center', ], 'end' => [ 'title' => esc_html__( 'End', 'elementor-pro' ), 'icon' => "eicon-text-align-{$end}", ], 'space-between' => [ 'title' => esc_html__( 'Space between', 'elementor-pro' ), 'icon' => 'eicon-text-align-justify', ], ], 'default' => '', 'selectors' => [ '{{WRAPPER}} .elementor-button span' => 'justify-content: {{VALUE}};', ], 'condition' => [ 'button_align' => 'stretch' ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'button_typography', 'global' => [ 'default' => Global_Typography::TYPOGRAPHY_ACCENT, ], 'selector' => '{{WRAPPER}} .elementor-button', ] ); $this->add_group_control( Group_Control_Border::get_type(), [ 'name' => 'button_border', 'selector' => '{{WRAPPER}} .elementor-button', 'exclude' => [ 'color', ], ] ); $this->start_controls_tabs( 'tabs_button_style' ); $this->start_controls_tab( 'tab_button_normal', [ 'label' => esc_html__( 'Normal', 'elementor-pro' ), ] ); $this->add_control( 'heading_next_submit_button', [ 'label' => esc_html__( 'Next & Submit Button', 'elementor-pro' ), 'type' => Controls_Manager::HEADING, ] ); $this->add_control( 'button_background_color', [ 'label' => esc_html__( 'Background Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'global' => [ 'default' => Global_Colors::COLOR_ACCENT, ], 'selectors' => [ '{{WRAPPER}} .e-form__buttons__wrapper__button-next' => 'background-color: {{VALUE}};', '{{WRAPPER}} .elementor-button[type="submit"]' => 'background-color: {{VALUE}};', ], ] ); $this->add_control( 'button_text_color', [ 'label' => esc_html__( 'Text Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '#ffffff', 'selectors' => [ '{{WRAPPER}} .e-form__buttons__wrapper__button-next' => 'color: {{VALUE}};', '{{WRAPPER}} .elementor-button[type="submit"]' => 'color: {{VALUE}};', '{{WRAPPER}} .elementor-button[type="submit"] svg *' => 'fill: {{VALUE}};', ], ] ); $this->add_control( 'button_border_color', [ 'label' => esc_html__( 'Border Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => [ '{{WRAPPER}} .e-form__buttons__wrapper__button-next' => 'border-color: {{VALUE}};', '{{WRAPPER}} .elementor-button[type="submit"]' => 'border-color: {{VALUE}};', ], 'condition' => [ 'button_border_border!' => '', ], ] ); $this->add_control( 'heading_previous_button', [ 'label' => esc_html__( 'Previous Button', 'elementor-pro' ), 'type' => Controls_Manager::HEADING, ] ); $this->add_control( 'previous_button_background_color', [ 'label' => esc_html__( 'Background Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'global' => [ 'default' => Global_Colors::COLOR_ACCENT, ], 'selectors' => [ '{{WRAPPER}} .e-form__buttons__wrapper__button-previous' => 'background-color: {{VALUE}};', ], ] ); $this->add_control( 'previous_button_text_color', [ 'label' => esc_html__( 'Text Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '#ffffff', 'selectors' => [ '{{WRAPPER}} .e-form__buttons__wrapper__button-previous' => 'color: {{VALUE}};', ], ] ); $this->add_control( 'previous_button_border_color', [ 'label' => esc_html__( 'Border Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => [ '{{WRAPPER}} .e-form__buttons__wrapper__button-previous' => 'border-color: {{VALUE}};', ], 'condition' => [ 'button_border_border!' => '', ], ] ); $this->end_controls_tab(); $this->start_controls_tab( 'tab_button_hover', [ 'label' => esc_html__( 'Hover', 'elementor-pro' ), ] ); $this->add_control( 'heading_next_submit_button_hover', [ 'label' => esc_html__( 'Next & Submit Button', 'elementor-pro' ), 'type' => Controls_Manager::HEADING, ] ); $this->add_control( 'button_background_hover_color', [ 'label' => esc_html__( 'Background Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => [ '{{WRAPPER}} .e-form__buttons__wrapper__button-next:hover' => 'background-color: {{VALUE}};', '{{WRAPPER}} .elementor-button[type="submit"]:hover' => 'background-color: {{VALUE}};', ], ] ); $this->add_control( 'button_hover_color', [ 'label' => esc_html__( 'Text Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '#ffffff', 'selectors' => [ '{{WRAPPER}} .e-form__buttons__wrapper__button-next:hover' => 'color: {{VALUE}};', '{{WRAPPER}} .elementor-button[type="submit"]:hover' => 'color: {{VALUE}};', '{{WRAPPER}} .elementor-button[type="submit"]:hover svg *' => 'fill: {{VALUE}};', ], ] ); $this->add_control( 'button_hover_border_color', [ 'label' => esc_html__( 'Border Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => [ '{{WRAPPER}} .e-form__buttons__wrapper__button-next:hover' => 'border-color: {{VALUE}};', '{{WRAPPER}} .elementor-button[type="submit"]:hover' => 'border-color: {{VALUE}};', ], 'condition' => [ 'button_border_border!' => '', ], ] ); $this->add_control( 'heading_previous_button_hover', [ 'label' => esc_html__( 'Previous Button', 'elementor-pro' ), 'type' => Controls_Manager::HEADING, ] ); $this->add_control( 'previous_button_background_color_hover', [ 'label' => esc_html__( 'Background Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => [ '{{WRAPPER}} .e-form__buttons__wrapper__button-previous:hover' => 'background-color: {{VALUE}};', ], ] ); $this->add_control( 'previous_button_text_color_hover', [ 'label' => esc_html__( 'Text Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '#ffffff', 'selectors' => [ '{{WRAPPER}} .e-form__buttons__wrapper__button-previous:hover' => 'color: {{VALUE}};', ], ] ); $this->add_control( 'previous_button_border_color_hover', [ 'label' => esc_html__( 'Border Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => [ '{{WRAPPER}} .e-form__buttons__wrapper__button-previous:hover' => 'border-color: {{VALUE}};', ], 'condition' => [ 'button_border_border!' => '', ], ] ); $this->add_control( 'hover_transition_duration', [ 'label' => esc_html__( 'Transition Duration', 'elementor-pro' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 's', 'ms', 'custom' ], 'default' => [ 'unit' => 'ms', ], 'selectors' => [ '{{WRAPPER}} .e-form__buttons__wrapper__button-previous' => 'transition-duration: {{SIZE}}{{UNIT}};', '{{WRAPPER}} .e-form__buttons__wrapper__button-next' => 'transition-duration: {{SIZE}}{{UNIT}};', '{{WRAPPER}} .elementor-button[type="submit"] svg *' => 'transition-duration: {{SIZE}}{{UNIT}};', '{{WRAPPER}} .elementor-button[type="submit"]' => 'transition-duration: {{SIZE}}{{UNIT}};', ], ] ); $this->add_control( 'button_hover_animation', [ 'label' => esc_html__( 'Animation', 'elementor-pro' ), 'type' => Controls_Manager::HOVER_ANIMATION, ] ); $this->end_controls_tab(); $this->end_controls_tabs(); $this->add_control( 'button_border_radius', [ 'label' => esc_html__( 'Border Radius', 'elementor-pro' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ], 'selectors' => [ '{{WRAPPER}} .elementor-button' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], 'separator' => 'before', ] ); $this->add_control( 'button_text_padding', [ 'label' => esc_html__( 'Text Padding', 'elementor-pro' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ], 'selectors' => [ '{{WRAPPER}} .elementor-button' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); $this->end_controls_section(); $this->start_controls_section( 'section_messages_style', [ 'label' => esc_html__( 'Messages', 'elementor-pro' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'message_typography', 'global' => [ 'default' => Global_Typography::TYPOGRAPHY_TEXT, ], 'selector' => '{{WRAPPER}} .elementor-message', ] ); $this->add_control( 'success_message_color', [ 'label' => esc_html__( 'Success Message Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .elementor-message.elementor-message-success' => 'color: {{COLOR}};', ], ] ); $this->add_control( 'error_message_color', [ 'label' => esc_html__( 'Error Message Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .elementor-message.elementor-message-danger' => 'color: {{COLOR}};', ], ] ); $this->add_control( 'inline_message_color', [ 'label' => esc_html__( 'Inline Message Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .elementor-message.elementor-help-inline' => 'color: {{COLOR}};', ], ] ); $this->end_controls_section(); $this->start_controls_section( 'section_steps_style', [ 'label' => esc_html__( 'Steps', 'elementor-pro' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'steps_typography', 'global' => [ 'default' => Global_Typography::TYPOGRAPHY_ACCENT, ], 'selector' => '{{WRAPPER}} .e-form__indicators__indicator, {{WRAPPER}} .e-form__indicators__indicator__label', 'conditions' => [ 'terms' => [ [ 'name' => 'step_type', 'operator' => '!in', 'value' => [ 'icon', 'progress_bar', ], ], ], ], ] ); $this->add_responsive_control( 'steps_gap', [ 'label' => esc_html__( 'Spacing', 'elementor-pro' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'custom' ], 'default' => [ 'size' => 20, ], 'range' => [ 'px' => [ 'max' => 100, ], 'em' => [ 'max' => 10, ], 'rem' => [ 'max' => 10, ], ], 'selectors' => [ '{{WRAPPER}}' => '--e-form-steps-indicators-spacing: {{SIZE}}{{UNIT}}', ], ] ); $this->add_responsive_control( 'steps_icon_size', [ 'label' => esc_html__( 'Icon Size', 'elementor-pro' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'custom' ], 'default' => [ 'size' => 15, ], 'range' => [ 'px' => [ 'max' => 100, ], 'em' => [ 'max' => 10, ], 'rem' => [ 'max' => 10, ], ], 'conditions' => [ 'terms' => [ [ 'name' => 'step_type', 'operator' => 'in', 'value' => [ 'icon', 'icon_text', ], ], ], ], 'selectors' => [ '{{WRAPPER}}' => '--e-form-steps-indicator-icon-size: {{SIZE}}{{UNIT}}', ], ] ); $this->add_responsive_control( 'steps_padding', [ 'label' => esc_html__( 'Padding', 'elementor-pro' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'default' => [ 'size' => 30, ], 'range' => [ 'px' => [ 'max' => 100, ], 'em' => [ 'max' => 10, ], 'rem' => [ 'max' => 10, ], ], 'selectors' => [ '{{WRAPPER}}' => '--e-form-steps-indicator-padding: {{SIZE}}{{UNIT}}', ], 'conditions' => [ 'terms' => [ [ 'name' => 'step_type', 'operator' => '!in', 'value' => [ 'text', 'progress_bar', ], ], ], ], ] ); $this->start_controls_tabs( 'steps_state', [ 'condition' => [ 'step_type!' => 'progress_bar', ], ] ); $this->start_controls_tab( 'tab_steps_state_inactive', [ 'label' => esc_html__( 'Inactive', 'elementor-pro' ), ] ); $this->add_control( 'step_inactive_primary_color', [ 'label' => esc_html__( 'Primary Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'global' => [ 'default' => Global_Colors::COLOR_TEXT, ], 'selectors' => [ '{{WRAPPER}}' => '--e-form-steps-indicator-inactive-primary-color: {{VALUE}};', ], ] ); $this->add_control( 'step_inactive_secondary_color', [ 'label' => esc_html__( 'Secondary Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '#ffffff', 'selectors' => [ '{{WRAPPER}}' => '--e-form-steps-indicator-inactive-secondary-color: {{VALUE}};', ], ] ); $this->end_controls_tab(); $this->start_controls_tab( 'tab_steps_state_active', [ 'label' => esc_html__( 'Active', 'elementor-pro' ), ] ); $this->add_control( 'step_active_primary_color', [ 'label' => esc_html__( 'Primary Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'global' => [ 'default' => Global_Colors::COLOR_ACCENT, ], 'selectors' => [ '{{WRAPPER}}' => '--e-form-steps-indicator-active-primary-color: {{VALUE}};', ], ] ); $this->add_control( 'step_active_secondary_color', [ 'label' => esc_html__( 'Secondary Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '#ffffff', 'selectors' => [ '{{WRAPPER}}' => '--e-form-steps-indicator-active-secondary-color: {{VALUE}};', ], ] ); $this->end_controls_tab(); $this->start_controls_tab( 'tab_steps_state_completed', [ 'label' => esc_html__( 'Completed', 'elementor-pro' ), ] ); $this->add_control( 'step_completed_primary_color', [ 'label' => esc_html__( 'Primary Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'global' => [ 'default' => Global_Colors::COLOR_ACCENT, ], 'selectors' => [ '{{WRAPPER}}' => '--e-form-steps-indicator-completed-primary-color: {{VALUE}};', ], ] ); $this->add_control( 'step_completed_secondary_color', [ 'label' => esc_html__( 'Secondary Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '#ffffff', 'condition' => [ 'step_icon_shape!' => 'none', ], 'selectors' => [ '{{WRAPPER}}' => '--e-form-steps-indicator-completed-secondary-color: {{VALUE}};', ], ] ); $this->end_controls_tab(); $this->end_controls_tabs(); $this->add_responsive_control( 'step_divider_width', [ 'label' => esc_html__( 'Divider Width', 'elementor-pro' ), 'separator' => 'before', 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'default' => [ 'size' => 1, ], 'range' => [ 'px' => [ 'max' => 100, ], 'em' => [ 'max' => 10, ], 'rem' => [ 'max' => 10, ], ], 'condition' => [ 'step_type!' => 'progress_bar', ], 'selectors' => [ '{{WRAPPER}}' => '--e-form-steps-divider-width: {{SIZE}}{{UNIT}}', ], ] ); $this->add_responsive_control( 'step_divider_gap', [ 'label' => esc_html__( 'Divider Gap', 'elementor-pro' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ], 'default' => [ 'size' => 10, ], 'range' => [ 'px' => [ 'max' => 100, ], 'em' => [ 'max' => 10, ], 'rem' => [ 'max' => 10, ], ], 'condition' => [ 'step_type!' => 'progress_bar', ], 'selectors' => [ '{{WRAPPER}}' => '--e-form-steps-divider-gap: {{SIZE}}{{UNIT}}', ], ] ); $this->add_control( 'step_progress_bar_color', [ 'label' => esc_html__( 'Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'global' => [ 'default' => Global_Colors::COLOR_ACCENT, ], 'condition' => [ 'step_type' => 'progress_bar', ], 'selectors' => [ '{{WRAPPER}}' => '--e-form-steps-indicator-progress-color: {{VALUE}};', ], ] ); $this->add_control( 'step_progress_bar_background_color', [ 'label' => esc_html__( 'Background Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'global' => [ 'default' => Global_Colors::COLOR_TEXT, ], 'condition' => [ 'step_type' => 'progress_bar', ], 'selectors' => [ '{{WRAPPER}}' => '--e-form-steps-indicator-progress-background-color: {{VALUE}};', ], ] ); $this->add_responsive_control( 'step_progress_bar_height', [ 'label' => esc_html__( 'Height', 'elementor-pro' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'vh', 'custom' ], 'default' => [ 'size' => 20, ], 'range' => [ 'px' => [ 'max' => 100, ], 'em' => [ 'max' => 10, ], 'rem' => [ 'max' => 10, ], ], 'condition' => [ 'step_type' => 'progress_bar', ], 'selectors' => [ '{{WRAPPER}}' => '--e-form-steps-indicator-progress-height: {{SIZE}}{{UNIT}}', ], ] ); $this->add_control( 'step_progress_bar_border_radius', [ 'label' => esc_html__( 'Border Radius', 'elementor-pro' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ], 'default' => [ 'size' => 0, ], 'range' => [ 'px' => [ 'max' => 100, ], 'em' => [ 'max' => 10, ], 'rem' => [ 'max' => 10, ], ], 'condition' => [ 'step_type' => 'progress_bar', ], 'selectors' => [ '{{WRAPPER}}' => '--e-form-steps-indicator-progress-border-radius: {{SIZE}}{{UNIT}}', ], ] ); $this->add_control( 'step_progress_bar_percentage_heading', [ 'label' => esc_html__( 'Percentage', 'elementor-pro' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', 'condition' => [ 'step_type' => 'progress_bar', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'step_progress_bar_percentage__typography', 'global' => [ 'default' => Global_Typography::TYPOGRAPHY_ACCENT, ], 'selector' => '{{WRAPPER}} .e-form__indicators__indicator__progress__meter', 'condition' => [ 'step_type' => 'progress_bar', ], ] ); $this->add_control( 'step_progress_bar_percentage_color', [ 'label' => esc_html__( 'Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'global' => [ 'default' => Global_Colors::COLOR_TEXT, ], 'condition' => [ 'step_type' => 'progress_bar', ], 'selectors' => [ '{{WRAPPER}}' => '--e-form-steps-indicator-progress-meter-color: {{VALUE}};', ], ] ); // End of steps style. $this->end_controls_section(); } private function render_icon_with_fallback( $settings ) { $migrated = isset( $settings['__fa4_migrated']['selected_button_icon'] ); $is_new = empty( $settings['button_icon'] ) && Icons_Manager::is_migration_allowed(); if ( $is_new || $migrated ) { Icons_Manager::render_icon( $settings['selected_button_icon'], [ 'aria-hidden' => 'true' ] ); } else { ?><i class="<?php echo esc_attr( $settings['button_icon'] ); ?>" aria-hidden="true"></i><?php } } protected function render() { $instance = $this->get_settings_for_display(); if ( ! Plugin::elementor()->editor->is_edit_mode() ) { /** * Elementor form pre render. * * Fires before the from is rendered in the frontend. This hook allows * developers to add functionality before the from is rendered. * * @since 2.4.0 * * @param array $instance Current form settings. * @param Form $this An instance of the form. */ do_action( 'elementor-pro/forms/pre_render', $instance, $this ); } $this->add_render_attribute( [ 'wrapper' => [ 'class' => [ 'elementor-form-fields-wrapper', 'elementor-labels-' . $instance['label_position'], ], ], 'submit-group' => [ 'class' => [ 'elementor-field-group', 'elementor-column', 'elementor-field-type-submit', ], ], 'button' => [ 'class' => 'elementor-button', 'type' => 'submit', ], 'button-content-wrapper' => [ 'class' => 'elementor-button-content-wrapper', ], 'button-icon' => [ 'class' => 'elementor-button-icon', ], 'button-text' => [ 'class' => 'elementor-button-text', ], ] ); if ( empty( $instance['button_width'] ) ) { $instance['button_width'] = '100'; } $this->add_render_attribute( 'submit-group', 'class', 'elementor-col-' . $instance['button_width'] . ' e-form__buttons' ); if ( ! empty( $instance['button_width_tablet'] ) ) { $this->add_render_attribute( 'submit-group', 'class', 'elementor-md-' . $instance['button_width_tablet'] ); } if ( ! empty( $instance['button_width_mobile'] ) ) { $this->add_render_attribute( 'submit-group', 'class', 'elementor-sm-' . $instance['button_width_mobile'] ); } if ( ! empty( $instance['button_size'] ) ) { $this->add_render_attribute( 'button', 'class', 'elementor-size-' . $instance['button_size'] ); } if ( ! empty( $instance['button_type'] ) ) { $this->add_render_attribute( 'button', 'class', 'elementor-button-' . $instance['button_type'] ); } if ( $instance['button_hover_animation'] ) { $this->add_render_attribute( 'button', 'class', 'elementor-animation-' . $instance['button_hover_animation'] ); } if ( ! empty( $instance['form_id'] ) ) { $this->add_render_attribute( 'form', 'id', $instance['form_id'] ); } if ( ! empty( $instance['form_name'] ) ) { $this->add_render_attribute( 'form', 'name', $instance['form_name'] ); } if ( 'custom' === $instance['form_validation'] ) { $this->add_render_attribute( 'form', 'novalidate' ); } if ( ! empty( $instance['button_css_id'] ) ) { $this->add_render_attribute( 'button', 'id', $instance['button_css_id'] ); } $referer_title = trim( wp_title( '', false ) ); if ( ! $referer_title && is_home() ) { $referer_title = get_option( 'blogname' ); } ?> <form class="elementor-form" method="post" <?php $this->print_render_attribute_string( 'form' ); ?>> <input type="hidden" name="post_id" value="<?php // PHPCS - the method Utils::get_current_post_id is safe. echo Utils::get_current_post_id(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>"/> <input type="hidden" name="form_id" value="<?php echo esc_attr( $this->get_id() ); ?>"/> <input type="hidden" name="referer_title" value="<?php echo esc_attr( $referer_title ); ?>" /> <?php if ( is_singular() ) { // `queried_id` may be different from `post_id` on Single theme builder templates. ?> <input type="hidden" name="queried_id" value="<?php echo get_the_ID(); ?>"/> <?php } ?> <div <?php $this->print_render_attribute_string( 'wrapper' ); ?>> <?php foreach ( $instance['form_fields'] as $item_index => $item ) : $item['input_size'] = $instance['input_size']; $this->form_fields_render_attributes( $item_index, $instance, $item ); $field_type = $item['field_type']; /** * Render form field. * * Filters the field rendered by Elementor forms. * * @since 1.0.0 * * @param array $item The field value. * @param int $item_index The field index. * @param Form $this An instance of the form. */ $item = apply_filters( 'elementor_pro/forms/render/item', $item, $item_index, $this ); /** * Render form field. * * Filters the field rendered by Elementor forms. * * The dynamic portion of the hook name, `$field_type`, refers to the field type. * * @since 1.0.0 * * @param array $item The field value. * @param int $item_index The field index. * @param Form $this An instance of the form. */ $item = apply_filters( "elementor_pro/forms/render/item/{$field_type}", $item, $item_index, $this ); $print_label = ! in_array( $item['field_type'], [ 'hidden', 'html', 'step' ], true ); ?> <div <?php $this->print_render_attribute_string( 'field-group' . $item_index ); ?>> <?php if ( $print_label && $item['field_label'] ) { ?> <label <?php $this->print_render_attribute_string( 'label' . $item_index ); ?>> <?php // PHPCS - the variable $item['field_label'] is safe. echo $item['field_label']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> </label> <?php } switch ( $item['field_type'] ) : case 'html': echo do_shortcode( $item['field_html'] ); break; case 'textarea': // PHPCS - the method make_textarea_field is safe. echo $this->make_textarea_field( $item, $item_index ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped break; case 'select': // PHPCS - the method make_select_field is safe. echo $this->make_select_field( $item, $item_index ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped break; case 'radio': case 'checkbox': // PHPCS - the method make_radio_checkbox_field is safe. echo $this->make_radio_checkbox_field( $item, $item_index, $item['field_type'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped break; case 'text': case 'email': case 'url': case 'password': case 'hidden': case 'search': $this->add_render_attribute( 'input' . $item_index, 'class', 'elementor-field-textual' ); ?> <input size="1" <?php $this->print_render_attribute_string( 'input' . $item_index ); ?>> <?php break; default: $field_type = $item['field_type']; /** * Elementor form field render. * * Fires when a field is rendered in the frontend. This hook allows developers to * add functionality when from fields are rendered. * * The dynamic portion of the hook name, `$field_type`, refers to the field type. * * @since 1.0.0 * * @param array $item The field value. * @param int $item_index The field index. * @param Form $this An instance of the form. */ do_action( "elementor_pro/forms/render_field/{$field_type}", $item, $item_index, $this ); endswitch; ?> </div> <?php endforeach; ?> <div <?php $this->print_render_attribute_string( 'submit-group' ); ?>> <button <?php $this->print_render_attribute_string( 'button' ); ?>> <span <?php $this->print_render_attribute_string( 'button-content-wrapper' ); ?>> <?php if ( ! empty( $instance['button_icon'] ) || ! empty( $instance['selected_button_icon']['value'] ) ) : ?> <span <?php $this->print_render_attribute_string( 'button-icon' ); ?>> <?php $this->render_icon_with_fallback( $instance ); ?> <?php if ( empty( $instance['button_text'] ) ) : ?> <span class="elementor-screen-only"><?php echo esc_html__( 'Submit', 'elementor-pro' ); ?></span> <?php endif; ?> </span> <?php endif; ?> <?php if ( ! empty( $instance['button_text'] ) ) : ?> <span <?php $this->print_render_attribute_string( 'button-text' ); ?>><?php $this->print_unescaped_setting( 'button_text' ); ?></span> <?php endif; ?> </span> </button> </div> </div> </form> <?php } /** * Render Form widget output in the editor. * * Written as a Backbone JavaScript template and used to generate the live preview. * * @since 2.9.0 * @access protected */ protected function content_template() { ?> <# view.addRenderAttribute( 'form', 'class', 'elementor-form' ); if ( '' !== settings.form_id ) { view.addRenderAttribute( 'form', 'id', settings.form_id ); } if ( '' !== settings.form_name ) { view.addRenderAttribute( 'form', 'name', settings.form_name ); } if ( 'custom' === settings.form_validation ) { view.addRenderAttribute( 'form', 'novalidate' ); } #> <form {{{ view.getRenderAttributeString( 'form' ) }}}> <div class="elementor-form-fields-wrapper elementor-labels-{{settings.label_position}}"> <# for ( var i in settings.form_fields ) { var item = settings.form_fields[ i ]; item = elementor.hooks.applyFilters( 'elementor_pro/forms/content_template/item', item, i, settings ); item.field_type = _.escape( item.field_type ); item.field_value = _.escape( item.field_value ); var options = item.field_options ? item.field_options.split( '\n' ) : [], itemClasses = _.escape( item.css_classes ), labelVisibility = '', placeholder = '', required = '', inputField = '', multiple = '', fieldGroupClasses = 'elementor-field-group elementor-column elementor-field-type-' + item.field_type, printLabel = settings.show_labels && ! [ 'hidden', 'html', 'step' ].includes( item.field_type ); fieldGroupClasses += ' elementor-col-' + ( ( '' !== item.width ) ? item.width : '100' ); if ( item.width_tablet ) { fieldGroupClasses += ' elementor-md-' + item.width_tablet; } if ( item.width_mobile ) { fieldGroupClasses += ' elementor-sm-' + item.width_mobile; } if ( item.required ) { required = 'required'; fieldGroupClasses += ' elementor-field-required'; if ( settings.mark_required ) { fieldGroupClasses += ' elementor-mark-required'; } } if ( item.placeholder ) { placeholder = 'placeholder="' + _.escape( item.placeholder ) + '"'; } if ( item.allow_multiple ) { multiple = ' multiple'; fieldGroupClasses += ' elementor-field-type-' + item.field_type + '-multiple'; } switch ( item.field_type ) { case 'step': inputField = `<div class="e-field-step elementor-hidden" data-label="${ item.field_label }" data-previousButton="${ item.previous_button || '' }" data-nextButton="${ item.next_button || '' }" data-iconUrl="${ 'svg' === item.selected_icon.library && item.selected_icon.value ? item.selected_icon.value.url : '' }" data-iconLibrary="${ 'svg' !== item.selected_icon.library && item.selected_icon.value ? item.selected_icon.value : '' }"></div>`; break; case 'html': inputField = item.field_html; break; case 'textarea': inputField = '<textarea class="elementor-field elementor-field-textual elementor-size-' + settings.input_size + ' ' + itemClasses + '" name="form_field_' + i + '" id="form_field_' + i + '" rows="' + item.rows + '" ' + required + ' ' + placeholder + '>' + item.field_value + '</textarea>'; break; case 'select': if ( options ) { var size = ''; if ( item.allow_multiple && item.select_size ) { size = ' size="' + item.select_size + '"'; } inputField = '<div class="elementor-field elementor-select-wrapper ' + itemClasses + '">'; inputField += '<select class="elementor-field-textual elementor-size-' + settings.input_size + '" name="form_field_' + i + '" id="form_field_' + i + '" ' + required + multiple + size + ' >'; for ( var x in options ) { var option_value = options[ x ]; var option_label = options[ x ]; var option_id = 'form_field_option' + i + x; if ( options[ x ].indexOf( '|' ) > -1 ) { var label_value = options[ x ].split( '|' ); option_label = label_value[0]; option_value = label_value[1]; } view.addRenderAttribute( option_id, 'value', option_value ); if ( item.field_value.split( ',' ) .indexOf( option_value ) ) { view.addRenderAttribute( option_id, 'selected', 'selected' ); } inputField += '<option ' + view.getRenderAttributeString( option_id ) + '>' + option_label + '</option>'; } inputField += '</select></div>'; } break; case 'radio': case 'checkbox': if ( options ) { var multiple = ''; if ( 'checkbox' === item.field_type && options.length > 1 ) { multiple = '[]'; } inputField = '<div class="elementor-field-subgroup ' + itemClasses + ' ' + _.escape( item.inline_list ) + '">'; for ( var x in options ) { var option_value = options[ x ]; var option_label = options[ x ]; var option_id = 'form_field_' + item.field_type + i + x; if ( options[x].indexOf( '|' ) > -1 ) { var label_value = options[x].split( '|' ); option_label = label_value[0]; option_value = label_value[1]; } view.addRenderAttribute( option_id, { value: option_value, type: item.field_type, id: 'form_field_' + i + '-' + x, name: 'form_field_' + i + multiple } ); if ( option_value === item.field_value ) { view.addRenderAttribute( option_id, 'checked', 'checked' ); } inputField += '<span class="elementor-field-option"><input ' + view.getRenderAttributeString( option_id ) + ' ' + required + '> '; inputField += '<label for="form_field_' + i + '-' + x + '">' + option_label + '</label></span>'; } inputField += '</div>'; } break; case 'text': case 'email': case 'url': case 'password': case 'number': case 'search': itemClasses = 'elementor-field-textual ' + itemClasses; inputField = '<input size="1" type="' + item.field_type + '" value="' + item.field_value + '" class="elementor-field elementor-size-' + settings.input_size + ' ' + itemClasses + '" name="form_field_' + i + '" id="form_field_' + i + '" ' + required + ' ' + placeholder + ' >'; break; default: item.placeholder = _.escape( item.placeholder ); inputField = elementor.hooks.applyFilters( 'elementor_pro/forms/content_template/field/' + item.field_type, '', item, i, settings ); } if ( inputField ) { #> <div class="{{ fieldGroupClasses }}"> <# if ( printLabel && item.field_label ) { #> <label class="elementor-field-label" for="form_field_{{ i }}" {{{ labelVisibility }}}>{{{ item.field_label }}}</label> <# } #> {{{ inputField }}} </div> <# } } view.addRenderAttribute( 'submit-group', { 'class': [ 'elementor-field-group', 'elementor-column', 'elementor-field-type-submit', 'e-form__buttons', 'elementor-col-' + ( ( '' !== settings.button_width ) ? settings.button_width : '100' ) ] } ); if ( settings.button_width_tablet ) { view.addRenderAttribute( 'submit-group', 'class', 'elementor-md-' + settings.button_width_tablet ); } if ( settings.button_width_mobile ) { view.addRenderAttribute( 'submit-group', 'class', 'elementor-sm-' + settings.button_width_mobile ); } view.addRenderAttribute( 'button', 'type', 'submit' ); view.addRenderAttribute( 'button', 'class', 'elementor-button' ); if ( '' !== settings.button_css_id ) { view.addRenderAttribute( 'button', 'id', settings.button_css_id ); } if ( '' !== settings.button_size ) { view.addRenderAttribute( 'button', 'class', 'elementor-size-' + settings.button_size ); } if ( '' !== settings.button_type ) { view.addRenderAttribute( 'button', 'class', 'elementor-button-' + settings.button_type ); } if ( '' !== settings.button_hover_animation ) { view.addRenderAttribute( 'button', 'class', 'elementor-animation-' + settings.button_hover_animation ); } view.addRenderAttribute( 'button-content-wrapper', 'class', 'elementor-button-content-wrapper' ); view.addRenderAttribute( 'button-icon', 'class', 'elementor-button-icon' ); view.addRenderAttribute( 'button-text', 'class', 'elementor-button-text' ); const iconHTML = elementor.helpers.renderIcon( view, settings.selected_button_icon, { 'aria-hidden': true }, 'i' , 'object' ); const migrated = elementor.helpers.isIconMigrated( settings, 'selected_button_icon' ); #> <div {{{ view.getRenderAttributeString( 'submit-group' ) }}}> <button {{{ view.getRenderAttributeString( 'button' ) }}}> <span {{{ view.getRenderAttributeString( 'button-content-wrapper' ) }}}> <# if ( settings.button_icon || settings.selected_button_icon ) { #> <span {{{ view.getRenderAttributeString( 'button-icon' ) }}}> <# if ( iconHTML && iconHTML.rendered && ( ! settings.button_icon || migrated ) ) { #> {{{ iconHTML.value }}} <# } else { #> <i class="{{ settings.button_icon }}" aria-hidden="true"></i> <# } #> <span class="elementor-screen-only"><?php echo esc_html__( 'Submit', 'elementor-pro' ); ?></span> </span> <# } #> <# if ( settings.button_text ) { #> <span {{{ view.getRenderAttributeString( 'button-text' ) }}}>{{{ settings.button_text }}}</span> <# } #> </span> </button> </div> </div> </form> <?php } public function get_group_name() { return 'forms'; } } widgets/login.php 0000644 00000074623 14720522625 0010054 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Widgets; use Elementor\Controls_Manager; use Elementor\Core\Kits\Documents\Tabs\Global_Colors; use Elementor\Core\Kits\Documents\Tabs\Global_Typography; use Elementor\Group_Control_Background; use Elementor\Group_Control_Border; use Elementor\Group_Control_Typography; use ElementorPro\Base\Base_Widget; use ElementorPro\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Login extends Base_Widget { public function get_name() { return 'login'; } public function get_title() { return esc_html__( 'Login', 'elementor-pro' ); } public function get_icon() { return 'eicon-lock-user'; } public function get_keywords() { return [ 'login', 'user', 'form' ]; } /** * Get style dependencies. * * Retrieve the list of style dependencies the widget requires. * * @since 3.24.0 * @access public * * @return array Widget style dependencies. */ public function get_style_depends(): array { return [ 'widget-login', 'widget-form' ]; } protected function register_controls() { $this->start_controls_section( 'section_fields_content', [ 'label' => esc_html__( 'Form Fields', 'elementor-pro' ), ] ); $this->add_control( 'show_labels', [ 'label' => esc_html__( 'Label', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes', 'label_off' => esc_html__( 'Hide', 'elementor-pro' ), 'label_on' => esc_html__( 'Show', 'elementor-pro' ), ] ); $this->add_control( 'input_size', [ 'label' => esc_html__( 'Input Size', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'options' => [ 'xs' => esc_html__( 'Extra Small', 'elementor-pro' ), 'sm' => esc_html__( 'Small', 'elementor-pro' ), 'md' => esc_html__( 'Medium', 'elementor-pro' ), 'lg' => esc_html__( 'Large', 'elementor-pro' ), 'xl' => esc_html__( 'Extra Large', 'elementor-pro' ), ], 'default' => 'sm', ] ); $this->end_controls_section(); $this->start_controls_section( 'section_button_content', [ 'label' => esc_html__( 'Button', 'elementor-pro' ), ] ); $this->add_control( 'button_text', [ 'label' => esc_html__( 'Text', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], 'default' => esc_html__( 'Log In', 'elementor-pro' ), ] ); $this->add_control( 'button_size', [ 'label' => esc_html__( 'Size', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'options' => [ 'xs' => esc_html__( 'Extra Small', 'elementor-pro' ), 'sm' => esc_html__( 'Small', 'elementor-pro' ), 'md' => esc_html__( 'Medium', 'elementor-pro' ), 'lg' => esc_html__( 'Large', 'elementor-pro' ), 'xl' => esc_html__( 'Extra Large', 'elementor-pro' ), ], 'default' => 'sm', ] ); $this->add_responsive_control( 'align', [ 'label' => esc_html__( 'Alignment', 'elementor-pro' ), 'type' => Controls_Manager::CHOOSE, 'options' => [ 'start' => [ 'title' => esc_html__( 'Left', 'elementor-pro' ), 'icon' => 'eicon-text-align-left', ], 'center' => [ 'title' => esc_html__( 'Center', 'elementor-pro' ), 'icon' => 'eicon-text-align-center', ], 'end' => [ 'title' => esc_html__( 'Right', 'elementor-pro' ), 'icon' => 'eicon-text-align-right', ], 'stretch' => [ 'title' => esc_html__( 'Justified', 'elementor-pro' ), 'icon' => 'eicon-text-align-justify', ], ], 'prefix_class' => 'elementor%s-button-align-', 'default' => '', ] ); $this->end_controls_section(); $this->start_controls_section( 'section_login_content', [ 'label' => esc_html__( 'Additional Options', 'elementor-pro' ), ] ); $this->add_control( 'redirect_after_login', [ 'label' => esc_html__( 'Redirect After Login', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'default' => '', 'label_off' => esc_html__( 'Off', 'elementor-pro' ), 'label_on' => esc_html__( 'On', 'elementor-pro' ), ] ); $this->add_control( 'redirect_url', [ 'type' => Controls_Manager::URL, 'show_label' => false, 'options' => false, 'description' => esc_html__( 'Note: Because of security reasons, you can ONLY use your current domain here.', 'elementor-pro' ), 'dynamic' => [ 'active' => true, ], 'condition' => [ 'redirect_after_login' => 'yes', ], ] ); $this->add_control( 'redirect_after_logout', [ 'label' => esc_html__( 'Redirect After Logout', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'default' => '', 'label_off' => esc_html__( 'Off', 'elementor-pro' ), 'label_on' => esc_html__( 'On', 'elementor-pro' ), ] ); $this->add_control( 'redirect_logout_url', [ 'type' => Controls_Manager::URL, 'show_label' => false, 'options' => false, 'description' => esc_html__( 'Note: Because of security reasons, you can ONLY use your current domain here.', 'elementor-pro' ), 'dynamic' => [ 'active' => true, ], 'condition' => [ 'redirect_after_logout' => 'yes', ], ] ); $this->add_control( 'show_lost_password', [ 'label' => esc_html__( 'Lost your password?', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes', 'label_off' => esc_html__( 'Hide', 'elementor-pro' ), 'label_on' => esc_html__( 'Show', 'elementor-pro' ), ] ); if ( get_option( 'users_can_register' ) ) { $this->add_control( 'show_register', [ 'label' => esc_html__( 'Register', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes', 'label_off' => esc_html__( 'Hide', 'elementor-pro' ), 'label_on' => esc_html__( 'Show', 'elementor-pro' ), ] ); } $this->add_control( 'show_remember_me', [ 'label' => esc_html__( 'Remember Me', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes', 'label_off' => esc_html__( 'Hide', 'elementor-pro' ), 'label_on' => esc_html__( 'Show', 'elementor-pro' ), ] ); $this->add_control( 'show_logged_in_message', [ 'label' => esc_html__( 'Logged in Message', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes', 'label_off' => esc_html__( 'Hide', 'elementor-pro' ), 'label_on' => esc_html__( 'Show', 'elementor-pro' ), ] ); $this->add_control( 'custom_labels', [ 'label' => esc_html__( 'Custom Label', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, ] ); $this->add_control( 'user_label', [ 'label' => esc_html__( 'Username Label', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], 'default' => esc_html__( 'Username or Email Address', 'elementor-pro' ), 'conditions' => [ 'relation' => 'or', 'terms' => [ [ 'name' => 'show_labels', 'operator' => '===', 'value' => 'yes', ], [ 'name' => 'custom_labels', 'operator' => '===', 'value' => 'yes', ], ], ], ] ); $this->add_control( 'user_placeholder', [ 'label' => esc_html__( 'Username Placeholder', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'default' => esc_html__( 'Username or Email Address', 'elementor-pro' ), 'condition' => [ 'custom_labels' => 'yes', ], 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], ] ); $this->add_control( 'password_label', [ 'label' => esc_html__( 'Password Label', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], 'default' => esc_html__( 'Password', 'elementor-pro' ), 'conditions' => [ 'relation' => 'or', 'terms' => [ [ 'name' => 'show_labels', 'operator' => '===', 'value' => 'yes', ], [ 'name' => 'custom_labels', 'operator' => '===', 'value' => 'yes', ], ], ], ] ); $this->add_control( 'password_placeholder', [ 'label' => esc_html__( 'Password Placeholder', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'default' => esc_html__( 'Password', 'elementor-pro' ), 'condition' => [ 'custom_labels' => 'yes', ], 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], ] ); $this->end_controls_section(); $this->start_controls_section( 'section_style', [ 'label' => esc_html__( 'Form', 'elementor-pro' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_control( 'row_gap', [ 'label' => esc_html__( 'Rows Gap', 'elementor-pro' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'custom' ], 'default' => [ 'size' => 10, ], 'range' => [ 'px' => [ 'max' => 60, ], 'em' => [ 'max' => 6, ], 'rem' => [ 'max' => 6, ], ], 'selectors' => [ '{{WRAPPER}} .elementor-field-group' => 'margin-bottom: {{SIZE}}{{UNIT}};', '{{WRAPPER}} .elementor-form-fields-wrapper' => 'margin-bottom: -{{SIZE}}{{UNIT}};', ], ] ); $this->add_control( 'links_color', [ 'label' => esc_html__( 'Links Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .elementor-field-group > a' => 'color: {{VALUE}};', ], 'global' => [ 'default' => Global_Colors::COLOR_TEXT, ], ] ); $this->add_control( 'links_hover_color', [ 'label' => esc_html__( 'Links Hover Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .elementor-field-group > a:hover' => 'color: {{VALUE}};', ], 'global' => [ 'default' => Global_Colors::COLOR_ACCENT, ], ] ); $this->end_controls_section(); $this->start_controls_section( 'section_style_labels', [ 'label' => esc_html__( 'Label', 'elementor-pro' ), 'tab' => Controls_Manager::TAB_STYLE, 'condition' => [ 'show_labels!' => '', ], ] ); $this->add_control( 'label_spacing', [ 'label' => esc_html__( 'Spacing', 'elementor-pro' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'custom' ], 'default' => [ 'size' => 0, ], 'range' => [ 'px' => [ 'max' => 60, ], 'em' => [ 'max' => 6, ], 'rem' => [ 'max' => 6, ], ], 'selectors' => [ 'body {{WRAPPER}} .elementor-field-group > label' => 'padding-bottom: {{SIZE}}{{UNIT}};', // for the label position = above option ], ] ); $this->add_control( 'label_color', [ 'label' => esc_html__( 'Text Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .elementor-form-fields-wrapper label' => 'color: {{VALUE}};', ], 'global' => [ 'default' => Global_Colors::COLOR_TEXT, ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'label_typography', 'selector' => '{{WRAPPER}} .elementor-form-fields-wrapper label', 'global' => [ 'default' => Global_Typography::TYPOGRAPHY_TEXT, ], ] ); $this->end_controls_section(); $this->start_controls_section( 'section_field_style', [ 'label' => esc_html__( 'Fields', 'elementor-pro' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_control( 'field_text_color', [ 'label' => esc_html__( 'Text Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .elementor-field-group .elementor-field' => 'color: {{VALUE}};', ], 'global' => [ 'default' => Global_Colors::COLOR_TEXT, ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'field_typography', 'selector' => '{{WRAPPER}} .elementor-field-group .elementor-field, {{WRAPPER}} .elementor-field-subgroup label', 'global' => [ 'default' => Global_Typography::TYPOGRAPHY_TEXT, ], ] ); $this->add_control( 'field_background_color', [ 'label' => esc_html__( 'Background Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '#ffffff', 'selectors' => [ '{{WRAPPER}} .elementor-field-group .elementor-field:not(.elementor-select-wrapper)' => 'background-color: {{VALUE}};', '{{WRAPPER}} .elementor-field-group .elementor-select-wrapper select' => 'background-color: {{VALUE}};', ], 'separator' => 'before', ] ); $this->add_control( 'field_border_color', [ 'label' => esc_html__( 'Border Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .elementor-field-group .elementor-field:not(.elementor-select-wrapper)' => 'border-color: {{VALUE}};', '{{WRAPPER}} .elementor-field-group .elementor-select-wrapper select' => 'border-color: {{VALUE}};', '{{WRAPPER}} .elementor-field-group .elementor-select-wrapper::before' => 'color: {{VALUE}};', ], 'separator' => 'before', ] ); $this->add_control( 'field_border_width', [ 'label' => esc_html__( 'Border Width', 'elementor-pro' ), 'type' => Controls_Manager::DIMENSIONS, 'placeholder' => '1', 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}} .elementor-field-group .elementor-field:not(.elementor-select-wrapper)' => 'border-width: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', '{{WRAPPER}} .elementor-field-group .elementor-select-wrapper select' => 'border-width: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); $this->add_control( 'field_border_radius', [ 'label' => esc_html__( 'Border Radius', 'elementor-pro' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ], 'selectors' => [ '{{WRAPPER}} .elementor-field-group .elementor-field:not(.elementor-select-wrapper)' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', '{{WRAPPER}} .elementor-field-group .elementor-select-wrapper select' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); $this->end_controls_section(); $this->start_controls_section( 'section_button_style', [ 'label' => esc_html__( 'Button', 'elementor-pro' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->start_controls_tabs( 'tabs_button_style' ); $this->start_controls_tab( 'tab_button_normal', [ 'label' => esc_html__( 'Normal', 'elementor-pro' ), ] ); $this->add_control( 'button_text_color', [ 'label' => esc_html__( 'Text Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => [ '{{WRAPPER}} .elementor-button' => 'color: {{VALUE}};', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'button_typography', 'global' => [ 'default' => Global_Typography::TYPOGRAPHY_ACCENT, ], 'selector' => '{{WRAPPER}} .elementor-button', ] ); $this->add_group_control( Group_Control_Background::get_type(), [ 'name' => 'button_background', 'types' => [ 'classic', 'gradient' ], 'exclude' => [ 'image' ], 'selector' => '{{WRAPPER}} .elementor-button', 'fields_options' => [ 'background' => [ 'default' => 'classic', ], 'color' => [ 'global' => [ 'default' => Global_Colors::COLOR_ACCENT, ], ], ], ] ); $this->add_group_control( Group_Control_Border::get_type(), [ 'name' => 'button_border', 'selector' => '{{WRAPPER}} .elementor-button', 'separator' => 'before', ] ); $this->add_control( 'button_border_radius', [ 'label' => esc_html__( 'Border Radius', 'elementor-pro' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ], 'selectors' => [ '{{WRAPPER}} .elementor-button' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); $this->add_control( 'button_text_padding', [ 'label' => esc_html__( 'Text Padding', 'elementor-pro' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ], 'selectors' => [ '{{WRAPPER}} .elementor-button' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); $this->end_controls_tab(); $this->start_controls_tab( 'tab_button_hover', [ 'label' => esc_html__( 'Hover', 'elementor-pro' ), ] ); $this->add_control( 'button_hover_color', [ 'label' => esc_html__( 'Text Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .elementor-button:hover' => 'color: {{VALUE}};', ], ] ); $this->add_group_control( Group_Control_Background::get_type(), [ 'name' => 'button_background_hover', 'types' => [ 'classic', 'gradient' ], 'exclude' => [ 'image' ], 'selector' => '{{WRAPPER}} .elementor-button:hover', 'fields_options' => [ 'background' => [ 'default' => 'classic', ], ], ] ); $this->add_control( 'button_hover_border_color', [ 'label' => esc_html__( 'Border Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .elementor-button:hover' => 'border-color: {{VALUE}};', ], 'condition' => [ 'button_border_border!' => '', ], ] ); $this->add_control( 'button_hover_animation', [ 'label' => esc_html__( 'Animation', 'elementor-pro' ), 'type' => Controls_Manager::HOVER_ANIMATION, ] ); $this->add_control( 'button_hover_transition_duration', [ 'label' => esc_html__( 'Transition Duration', 'elementor-pro' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 's', 'ms', 'custom' ], 'default' => [ 'unit' => 'ms', ], 'selectors' => [ '{{WRAPPER}} .elementor-button' => 'transition-duration: {{SIZE}}{{UNIT}}', ], ] ); $this->end_controls_tab(); $this->end_controls_tabs(); $this->end_controls_section(); $this->start_controls_section( 'section_style_message', [ 'label' => esc_html__( 'Logged in Message', 'elementor-pro' ), 'tab' => Controls_Manager::TAB_STYLE, 'condition' => [ 'show_logged_in_message' => 'yes', ], ] ); $this->add_control( 'message_color', [ 'label' => esc_html__( 'Text Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .elementor-widget-container .elementor-login__logged-in-message' => 'color: {{VALUE}};', ], 'global' => [ 'default' => Global_Colors::COLOR_TEXT, ], 'condition' => [ 'show_logged_in_message' => 'yes', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'message_typography', 'selector' => '{{WRAPPER}} .elementor-widget-container .elementor-login__logged-in-message', 'global' => [ 'default' => Global_Typography::TYPOGRAPHY_TEXT, ], 'condition' => [ 'show_logged_in_message' => 'yes', ], ] ); $this->end_controls_section(); } private function form_fields_render_attributes() { $settings = $this->get_settings_for_display(); if ( ! empty( $settings['button_size'] ) ) { $this->add_render_attribute( 'button', 'class', 'elementor-size-' . $settings['button_size'] ); } if ( $settings['button_hover_animation'] ) { $this->add_render_attribute( 'button', 'class', 'elementor-animation-' . $settings['button_hover_animation'] ); } $this->add_render_attribute( [ 'wrapper' => [ 'class' => [ 'elementor-form-fields-wrapper', ], ], 'field-group' => [ 'class' => [ 'elementor-field-type-text', 'elementor-field-group', 'elementor-column', 'elementor-col-100', ], ], 'submit-group' => [ 'class' => [ 'elementor-field-group', 'elementor-column', 'elementor-field-type-submit', 'elementor-col-100', ], ], 'button' => [ 'class' => [ 'elementor-button', ], 'name' => 'wp-submit', ], 'user_label' => [ 'for' => 'user-' . $this->get_id(), 'class' => 'elementor-field-label', ], 'user_input' => [ 'size' => '1', 'type' => 'text', 'name' => 'log', 'id' => 'user-' . $this->get_id(), 'placeholder' => $settings['user_placeholder'], 'class' => [ 'elementor-field', 'elementor-field-textual', 'elementor-size-' . $settings['input_size'], ], ], 'password_label' => [ 'for' => 'password-' . $this->get_id(), 'class' => 'elementor-field-label', ], 'password_input' => [ 'size' => '1', 'type' => 'password', 'name' => 'pwd', 'id' => 'password-' . $this->get_id(), 'placeholder' => $settings['password_placeholder'], 'class' => [ 'elementor-field', 'elementor-field-textual', 'elementor-size-' . $settings['input_size'], ], ], ] ); if ( ! $settings['show_labels'] ) { $this->add_render_attribute( 'user_label', 'class', 'elementor-screen-only' ); $this->add_render_attribute( 'password_label', 'class', 'elementor-screen-only' ); } $this->add_render_attribute( 'field-group', 'class', 'elementor-field-required' ) ->add_render_attribute( 'input', 'required', true ) ->add_render_attribute( 'input', 'aria-required', 'true' ); } protected function render() { $settings = $this->get_settings_for_display(); $current_url = remove_query_arg( 'fake_arg' ); $logout_redirect = $current_url; if ( 'yes' === $settings['redirect_after_login'] && ! empty( $settings['redirect_url']['url'] ) ) { $redirect_url = $settings['redirect_url']['url']; } else { $redirect_url = $current_url; } if ( 'yes' === $settings['redirect_after_logout'] && ! empty( $settings['redirect_logout_url']['url'] ) ) { $logout_redirect = $settings['redirect_logout_url']['url']; } if ( is_user_logged_in() && ! Plugin::elementor()->editor->is_edit_mode() ) { if ( 'yes' === $settings['show_logged_in_message'] ) { $current_user = wp_get_current_user(); // PHPCS - `sprintf` is safe. echo '<div class="elementor-login elementor-login__logged-in-message">' . sprintf( /* translators: 1: User display name, 2: Link opening tag, 3: Link closing tag. */ esc_html__( 'You are Logged in as %1$s (%2$sLogout%3$s)', 'elementor-pro' ), wp_kses_post( $current_user->display_name ), sprintf( '<a href="%s" target="_blank">', esc_url( wp_logout_url( $logout_redirect ) ) ), '</a>' ) . '</div>'; } return; } $this->form_fields_render_attributes(); ?> <form class="elementor-login elementor-form" method="post" action="<?php echo esc_url( site_url( 'wp-login.php', 'login_post' ) ); ?>"> <input type="hidden" name="redirect_to" value="<?php echo esc_attr( $redirect_url ); ?>"> <div <?php $this->print_render_attribute_string( 'wrapper' ); ?>> <div <?php $this->print_render_attribute_string( 'field-group' ); ?>> <label <?php $this->print_render_attribute_string( 'user_label' ); ?>><?php $this->print_unescaped_setting( 'user_label' ); ?></label> <input <?php $this->print_render_attribute_string( 'user_input' ); ?>> </div> <div <?php $this->print_render_attribute_string( 'field-group' ); ?>> <label <?php $this->print_render_attribute_string( 'password_label' ); ?>><?php $this->print_unescaped_setting( 'password_label' ); ?></label> <input <?php $this->print_render_attribute_string( 'password_input' ); ?>> </div> <?php if ( 'yes' === $settings['show_remember_me'] ) : ?> <div class="elementor-field-type-checkbox elementor-field-group elementor-column elementor-col-100 elementor-remember-me"> <label for="elementor-login-remember-me"> <input type="checkbox" id="elementor-login-remember-me" name="rememberme" value="forever"> <?php echo esc_html__( 'Remember Me', 'elementor-pro' ); ?> </label> </div> <?php endif; ?> <div <?php $this->print_render_attribute_string( 'submit-group' ); ?>> <button type="submit" <?php $this->print_render_attribute_string( 'button' ); ?>> <?php if ( ! empty( $settings['button_text'] ) ) : ?> <span class="elementor-button-text"><?php $this->print_unescaped_setting( 'button_text' ); ?></span> <?php endif; ?> </button> </div> <?php $show_lost_password = 'yes' === $settings['show_lost_password']; $show_register = get_option( 'users_can_register' ) && 'yes' === $settings['show_register']; if ( $show_lost_password || $show_register ) : ?> <div class="elementor-field-group elementor-column elementor-col-100"> <?php if ( $show_lost_password ) : ?> <?php // PHPCS - `wp_lostpassword_url` is safe. ?> <a class="elementor-lost-password" href="<?php echo wp_lostpassword_url( $redirect_url ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>"> <?php echo esc_html__( 'Lost your password?', 'elementor-pro' ); ?> </a> <?php endif; ?> <?php if ( $show_register ) : ?> <?php if ( $show_lost_password ) : ?> <span class="elementor-login-separator"> | </span> <?php endif; ?> <?php // PHPCS - `wp_registration_url` is safe. ?> <a class="elementor-register" href="<?php echo wp_registration_url(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>"> <?php echo esc_html__( 'Register', 'elementor-pro' ); ?> </a> <?php endif; ?> </div> <?php endif; ?> </div> </form> <?php } /** * Render Login Form output in the editor. * * Written as a Backbone JavaScript template and used to generate the live preview. * * @since 2.9.0 * @access protected */ protected function content_template() { ?> <div class="elementor-login elementor-form"> <div class="elementor-form-fields-wrapper"> <# view.addRenderAttribute( 'field-group', 'class', 'elementor-field-group elementor-column elementor-col-100 elementor-field-type-text' ); view.addRenderAttribute( 'user-label', { for: 'user-<?php echo $this->get_id(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>', class: 'elementor-field-label' } ); view.addRenderAttribute( 'password-label', { for: 'password-<?php echo $this->get_id(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>', class: 'elementor-field-label' } ); view.addRenderAttribute( 'user-input', { size: '1', type: 'text', name: 'log', id: 'user-<?php echo $this->get_id(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>', placeholder: settings.user_placeholder, class: [ 'elementor-field', 'elementor-field-textual', 'elementor-size-' + settings.input_size, ], } ); view.addRenderAttribute( 'password-input', { size: '1', type: 'password', name: 'pwd', id: 'password-<?php echo $this->get_id(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>', placeholder: settings.password_placeholder, class: [ 'elementor-field', 'elementor-field-textual', 'elementor-size-' + settings.input_size, ], } ); if ( ! settings.show_labels ) { view.addRenderAttribute( 'user-label', 'class', 'elementor-screen-only' ); view.addRenderAttribute( 'password-label', 'class', 'elementor-screen-only' ); } #> <div {{{ view.getRenderAttributeString( 'field-group' ) }}}> <label {{{ view.getRenderAttributeString( 'user-label' ) }}}>{{{ settings.user_label }}}</label> <input {{{ view.getRenderAttributeString( 'user-input' ) }}}> </div> <div {{{ view.getRenderAttributeString( 'field-group' ) }}}> <label {{{ view.getRenderAttributeString( 'password-label' ) }}}>{{{ settings.password_label }}}</label> <input {{{ view.getRenderAttributeString( 'password-input' ) }}}> </div> <# if ( settings.show_remember_me ) { #> <div class="elementor-field-type-checkbox elementor-field-group elementor-column elementor-col-100 elementor-remember-me"> <label for="elementor-login-remember-me"> <input type="checkbox" id="elementor-login-remember-me" name="rememberme" value="forever"> <?php // PHPCS - `esc_html__` is safe. ?> <?php echo esc_html__( 'Remember Me', 'elementor-pro' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> </label> </div> <# } #> <div class="elementor-field-group elementor-column elementor-field-type-submit elementor-col-100"> <button type="submit" class="elementor-button elementor-size-{{ settings.button_size }}"> <# if ( settings.button_text ) { #> <span class="elementor-button-text">{{ settings.button_text }}</span> <# } #> </button> </div> <# if ( settings.show_lost_password || settings.show_register ) { #> <div class="elementor-field-group elementor-column elementor-col-100"> <# if ( settings.show_lost_password ) { #> <?php // PHPCS - `wp_lostpassword_url` is safe. ?> <a class="elementor-lost-password" href="<?php echo wp_lostpassword_url(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>"> <?php echo esc_html__( 'Lost your password?', 'elementor-pro' ); ?> </a> <# } #> <?php if ( get_option( 'users_can_register' ) ) { ?> <# if ( settings.show_register ) { #> <# if ( settings.show_lost_password ) { #> <span class="elementor-login-separator"> | </span> <# } #> <?php // PHPCS - `wp_registration_url` is safe. ?> <a class="elementor-register" href="<?php echo wp_registration_url(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>"> <?php echo esc_html__( 'Register', 'elementor-pro' ); ?> </a> <# } #> <?php } ?> </div> <# } #> </div> </div> <?php } public function render_plain_content() {} public function get_group_name() { return 'forms'; } } controls/fields-map.php 0000644 00000001550 14720522625 0011147 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Controls; use Elementor\Control_Repeater; use Elementor\Controls_Manager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Class Fields_Map * @package ElementorPro\Modules\Forms\Controls * * each item needs the following properties: * remote_id, * remote_label * remote_type * remote_required * local_id */ class Fields_Map extends Control_Repeater { const CONTROL_TYPE = 'fields_map'; public function get_type() { return self::CONTROL_TYPE; } protected function get_default_settings() { return array_merge( parent::get_default_settings(), [ 'render_type' => 'none', 'fields' => [ [ 'name' => 'remote_id', 'type' => Controls_Manager::HIDDEN, ], [ 'name' => 'local_id', 'type' => Controls_Manager::SELECT, ], ], ] ); } } controls/fields-repeater.php 0000644 00000000500 14720522625 0012173 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Controls; use Elementor\Control_Repeater; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Fields_Repeater extends Control_Repeater { const CONTROL_TYPE = 'form-fields-repeater'; public function get_type() { return self::CONTROL_TYPE; } } actions/redirect.php 0000644 00000003451 14720522625 0010526 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Actions; use Elementor\Controls_Manager; use Elementor\Modules\DynamicTags\Module as TagsModule; use ElementorPro\Modules\Forms\Classes\Action_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Redirect extends Action_Base { public function get_name() { return 'redirect'; } public function get_label() { return esc_html__( 'Redirect', 'elementor-pro' ); } public function register_settings_section( $widget ) { $widget->start_controls_section( 'section_redirect', [ 'label' => esc_html__( 'Redirect', 'elementor-pro' ), 'condition' => [ 'submit_actions' => $this->get_name(), ], ] ); $widget->add_control( 'redirect_to', [ 'label' => esc_html__( 'Redirect To', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'placeholder' => esc_html__( 'https://your-link.com', 'elementor-pro' ), 'ai' => [ 'active' => false, ], 'dynamic' => [ 'active' => true, 'categories' => [ TagsModule::POST_META_CATEGORY, TagsModule::TEXT_CATEGORY, TagsModule::URL_CATEGORY, ], ], 'label_block' => true, 'render_type' => 'none', 'classes' => 'elementor-control-direction-ltr', ] ); $widget->end_controls_section(); } public function on_export( $element ) { unset( $element['settings']['redirect_to'] ); return $element; } public function run( $record, $ajax_handler ) { $redirect_to = $record->get_form_settings( 'redirect_to' ); $redirect_to = $record->replace_setting_shortcodes( $redirect_to, true ); $redirect_to = esc_url_raw( $redirect_to ); if ( ! empty( $redirect_to ) && filter_var( $redirect_to, FILTER_VALIDATE_URL ) ) { $ajax_handler->add_response_data( 'redirect_url', $redirect_to ); } } } actions/mailpoet.php 0000644 00000005646 14720522625 0010547 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Actions; use Elementor\Controls_Manager; use ElementorPro\Modules\Forms\Classes\Form_Record; use ElementorPro\Modules\Forms\Classes\Integration_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Mailpoet extends Integration_Base { public function get_name() { return 'mailpoet'; } public function get_label() { return 'MailPoet'; } public function register_settings_section( $widget ) { $widget->start_controls_section( 'section_mailpoet', [ 'label' => esc_html__( 'MailPoet', 'elementor-pro' ), 'condition' => [ 'submit_actions' => $this->get_name(), ], ] ); /** @var \WYSIJA_model_list $model_list */ $model_list = \WYSIJA::get( 'list', 'model' ); $mailpoet_lists = $model_list->get( [ 'name', 'list_id' ], [ 'is_enabled' => 1 ] ); $options = []; foreach ( $mailpoet_lists as $list ) { $options[ $list['list_id'] ] = $list['name']; } $widget->add_control( 'mailpoet_lists', [ 'label' => esc_html__( 'List', 'elementor-pro' ), 'type' => Controls_Manager::SELECT2, 'label_block' => true, 'options' => $options, 'render_type' => 'none', ] ); $this->register_fields_map_control( $widget ); $widget->end_controls_section(); } public function on_export( $element ) { unset( $element['mailpoet_lists'] ); return $element; } public function run( $record, $ajax_handler ) { $subscriber = $this->map_fields( $record ); /** @var \WYSIJA_help_user $helper_user */ $helper_user = \WYSIJA::get( 'user', 'helper' ); $helper_user->addSubscriber( $subscriber ); } /** * @param Form_Record $record * * @return array */ private function map_fields( $record ) { $settings = $record->get( 'form_settings' ); $fields = $record->get( 'fields' ); $subscriber = [ 'user' => [ 'email' => '', ], 'user_list' => [ 'list_ids' => (array) $settings['mailpoet_lists'] ], ]; foreach ( $settings['mailpoet_fields_map'] as $map_item ) { if ( empty( $fields[ $map_item['local_id'] ]['value'] ) ) { continue; } $value = $fields[ $map_item['local_id'] ]['value']; if ( 'email' === $map_item['remote_id'] ) { $subscriber['user']['email'] = $value; } else { $subscriber['user'][ $map_item['remote_id'] ] = $value; } } return $subscriber; } protected function get_fields_map_control_options() { return [ 'default' => [ [ 'remote_id' => 'firstname', 'remote_label' => esc_html__( 'First Name', 'elementor-pro' ), 'remote_type' => 'text', ], [ 'remote_id' => 'lastname', 'remote_label' => esc_html__( 'Last Name', 'elementor-pro' ), 'remote_type' => 'text', ], [ 'remote_id' => 'email', 'remote_label' => esc_html__( 'Email', 'elementor-pro' ), 'remote_type' => 'email', 'remote_required' => true, ], ], 'condition' => [ 'mailpoet_lists!' => '', ], ]; } } actions/activity-log.php 0000644 00000001714 14720522625 0011340 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Actions; use ElementorPro\Modules\Forms\Classes\Action_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Integration with Activity Log */ class Activity_Log extends Action_Base { public function get_name() { return 'activity-log'; } public function get_label() { return 'Activity Log'; } public function register_settings_section( $widget ) {} public function on_export( $element ) {} public function aal_init_roles( $roles ) { $roles['manage_options'][] = 'Elementor Forms'; return $roles; } public function run( $record, $ajax_handler ) { aal_insert_log( [ 'action' => 'New Record', 'object_type' => 'Elementor Forms', 'object_id' => $record->get_form_settings( 'id' ), 'object_name' => $record->get_form_settings( 'form_name' ), ] ); } public function __construct() { add_filter( 'aal_init_roles', [ $this, 'aal_init_roles' ] ); } } actions/mailerlite.php 0000644 00000017701 14720522625 0011057 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Actions; use Elementor\Controls_Manager; use Elementor\Settings; use ElementorPro\Core\Utils; use ElementorPro\Modules\Forms\Classes\Form_Record; use ElementorPro\Modules\Forms\Classes\Mailerlite_Handler; use ElementorPro\Modules\Forms\Classes\Integration_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Mailerlite extends Integration_Base { const OPTION_NAME_API_KEY = 'pro_mailerlite_api_key'; private function get_global_api_key() { return get_option( 'elementor_' . self::OPTION_NAME_API_KEY ); } public function get_name() { return 'mailerlite'; } public function get_label() { return esc_html__( 'MailerLite', 'elementor-pro' ); } public function register_settings_section( $widget ) { $widget->start_controls_section( 'section_mailerlite', [ 'label' => esc_html__( 'MailerLite', 'elementor-pro' ), 'condition' => [ 'submit_actions' => $this->get_name(), ], ] ); self::global_api_control( $widget, $this->get_global_api_key(), 'MailerLite API Key', [ 'mailerlite_api_key_source' => 'default', ], $this->get_name() ); $widget->add_control( 'mailerlite_api_key_source', [ 'label' => esc_html__( 'API Key', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'label_block' => false, 'options' => [ 'default' => esc_html__( 'Default', 'elementor-pro' ), 'custom' => esc_html__( 'Custom', 'elementor-pro' ), ], 'default' => 'default', ] ); $widget->add_control( 'mailerlite_custom_api_key', [ 'label' => esc_html__( 'Custom API Key', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'condition' => [ 'mailerlite_api_key_source' => 'custom', ], 'description' => esc_html__( 'Use this field to set a custom API Key for the current form', 'elementor-pro' ), 'ai' => [ 'active' => false, ], ] ); $widget->add_control( 'mailerlite_group', [ 'label' => esc_html__( 'Group', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'options' => [], 'render_type' => 'none', 'conditions' => [ 'relation' => 'or', 'terms' => [ [ 'name' => 'mailerlite_custom_api_key', 'operator' => '!==', 'value' => '', ], [ 'name' => 'mailerlite_api_key_source', 'operator' => '=', 'value' => 'default', ], ], ], ] ); $this->register_fields_map_control( $widget ); $widget->add_control( 'allow_resubscribe', [ 'label' => esc_html__( 'Allow Resubscribe', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'condition' => [ 'mailerlite_group!' => '', ], ] ); $widget->end_controls_section(); } public function on_export( $element ) { unset( $element['settings']['mailerlite_api_key_source'], $element['settings']['mailerlite_custom_api_key'], $element['settings']['mailerlite_group'], $element['settings']['mailerlite_fields_map'] ); return $element; } public function run( $record, $ajax_handler ) { $form_settings = $record->get( 'form_settings' ); $subscriber = $this->create_subscriber_object( $record ); if ( ! $subscriber ) { new \Exception( esc_html__( 'Integration requires an email field', 'elementor-pro' ) ); } if ( 'default' === $form_settings['mailerlite_api_key_source'] ) { $api_key = $this->get_global_api_key(); } else { $api_key = $form_settings['mailerlite_custom_api_key']; } $handler = new Mailerlite_Handler( $api_key ); $handler->create_subscriber( $form_settings['mailerlite_group'], $subscriber ); } /** * Create subscriber array from submitted data and form settings * returns a subscriber array or false on error * * @param Form_Record $record * * @return array|bool */ private function create_subscriber_object( Form_Record $record ) { $email = $this->get_mapped_field( $record, 'email' ); if ( ! $email ) { return false; } $subscriber = [ 'email' => $email, 'name' => $this->get_mapped_field( $record, 'name' ), ]; $subscriber['fields'] = $this->get_mailerlite_custom_fields( $record ); // Allow re-subscribe $allow_resubscribe = $record->get_form_settings( 'allow_resubscribe' ); if ( ! empty( $allow_resubscribe ) && 'yes' === $allow_resubscribe ) { $subscriber['resubscribe'] = true; } return $subscriber; } /** * @param Form_Record $record * * @return array */ private function get_mailerlite_custom_fields( Form_Record $record ) { $custom_fields = []; $form_fields = $record->get( 'fields' ); $field_mapping = $record->get_form_settings( 'mailerlite_fields_map' ); foreach ( $field_mapping as $map_item ) { if ( in_array( $map_item['remote_id'], [ 'email', 'name' ] ) ) { continue; } if ( empty( $map_item['local_id'] ) ) { continue; } foreach ( $form_fields as $id => $field ) { if ( $id !== $map_item['local_id'] ) { continue; } $custom_fields[ $map_item['remote_id'] ] = $field['value']; } } return $custom_fields; } private function get_mapped_field( Form_Record $record, $field_id ) { $fields = $record->get( 'fields' ); foreach ( $record->get_form_settings( 'mailerlite_fields_map' ) as $map_item ) { if ( empty( $fields[ $map_item['local_id'] ]['value'] ) ) { continue; } if ( $field_id === $map_item['remote_id'] ) { return $fields[ $map_item['local_id'] ]['value']; } } return ''; } public function handle_panel_request( array $data ) { if ( ! empty( $data['api_key'] ) && 'default' === $data['api_key'] ) { $api_key = $this->get_global_api_key(); } elseif ( ! empty( $data['custom_api_key'] ) ) { $api_key = $data['custom_api_key']; } if ( empty( $api_key ) ) { throw new \Exception( '`api_key` is required.', 400 ); } $handler = new Mailerlite_Handler( $api_key ); if ( 'groups' === $data['mailerlite_action'] ) { return $handler->get_groups(); } } public function register_admin_fields( Settings $settings ) { $settings->add_section( Settings::TAB_INTEGRATIONS, 'mailerlite', [ 'callback' => function() { echo '<hr><h2>' . esc_html__( 'MailerLite', 'elementor-pro' ) . '</h2>'; }, 'fields' => [ self::OPTION_NAME_API_KEY => [ 'label' => esc_html__( 'API Key', 'elementor-pro' ), 'field_args' => [ 'type' => 'text', 'desc' => sprintf( /* translators: 1: Link opening tag, 2: Link closing tag. */ esc_html__( 'To integrate with our forms you need an %1$sAPI Key%2$s.', 'elementor-pro' ), '<a href="https://help.mailerlite.com/article/show/35040-where-can-i-find-the-api-key" target="_blank">', '</a>' ), ], ], 'validate_api_data' => [ 'field_args' => [ 'type' => 'raw_html', 'html' => sprintf( '<button data-action="%s" data-nonce="%s" class="button elementor-button-spinner" id="elementor_pro_mailerlite_api_key_button">%s</button>', self::OPTION_NAME_API_KEY . '_validate', wp_create_nonce( self::OPTION_NAME_API_KEY ), esc_html__( 'Validate API Key', 'elementor-pro' ) ), ], ], ], ] ); } public function ajax_validate_api_key() { check_ajax_referer( self::OPTION_NAME_API_KEY, '_nonce' ); if ( ! isset( $_POST['api_key'] ) ) { wp_send_json_error(); } if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( 'Permission denied' ); } try { new Mailerlite_Handler( $_POST['api_key'] ); // phpcs:ignore -- No need to sanitize to support special characters. } catch ( \Exception $exception ) { wp_send_json_error(); } wp_send_json_success(); } public function __construct() { if ( is_admin() ) { add_action( 'elementor/admin/after_create_settings/' . Settings::PAGE_ID, [ $this, 'register_admin_fields' ], 15 ); } add_action( 'wp_ajax_' . self::OPTION_NAME_API_KEY . '_validate', [ $this, 'ajax_validate_api_key' ] ); } protected function get_fields_map_control_options() { return [ 'condition' => [ 'mailerlite_group!' => '', ], ]; } } actions/email2.php 0000644 00000002033 14720522625 0010071 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Actions; use Elementor\Controls_Manager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Email2 extends Email { public function get_name() { return 'email2'; } public function get_label() { return esc_html__( 'Email 2', 'elementor-pro' ); } protected function get_control_id( $control_id ) { return $control_id . '_2'; } protected function get_reply_to( $record, $fields ) { return isset( $fields['email_reply_to'] ) ? $fields['email_reply_to'] : ''; } public function register_settings_section( $widget ) { parent::register_settings_section( $widget ); $admin_email = get_option( 'admin_email' ); $widget->update_control( $this->get_control_id( 'email_reply_to' ), [ 'type' => Controls_Manager::TEXT, 'default' => $admin_email, 'placeholder' => $admin_email, 'ai' => [ 'active' => false, ], ] ); $widget->update_control( $this->get_control_id( 'form_metadata' ), [ 'default' => [], ] ); } } actions/drip.php 0000644 00000021466 14720522625 0007671 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Actions; use Elementor\Controls_Manager; use Elementor\Settings; use ElementorPro\Modules\Forms\Classes\Form_Record; use ElementorPro\Modules\Forms\Classes\Integration_Base; use ElementorPro\Modules\Forms\Classes\Drip_Handler; use ElementorPro\Core\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Drip extends Integration_Base { const OPTION_NAME_API_KEY = 'pro_drip_api_token'; private function get_global_api_key() { return get_option( 'elementor_' . self::OPTION_NAME_API_KEY ); } public function get_name() { return 'drip'; } public function get_label() { return esc_html__( 'Drip', 'elementor-pro' ); } public function register_settings_section( $widget ) { $widget->start_controls_section( 'section_drip', [ 'label' => esc_html__( 'Drip', 'elementor-pro' ), 'condition' => [ 'submit_actions' => $this->get_name(), ], ] ); self::global_api_control( $widget, $this->get_global_api_key(), 'Drip API Token', [ 'drip_api_token_source' => 'default', ], $this->get_name() ); $widget->add_control( 'drip_api_token_source', [ 'label' => esc_html__( 'API Key', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'label_block' => false, 'options' => [ 'default' => esc_html__( 'Default', 'elementor-pro' ), 'custom' => esc_html__( 'Custom', 'elementor-pro' ), ], 'default' => 'default', ] ); $widget->add_control( 'drip_custom_api_token', [ 'label' => esc_html__( 'Custom API Key', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'condition' => [ 'drip_api_token_source' => 'custom', ], 'description' => esc_html__( 'Use this field to set a custom API Key for the current form', 'elementor-pro' ), 'ai' => [ 'active' => false, ], ] ); $widget->add_control( 'drip_account', [ 'label' => esc_html__( 'Account', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'options' => [], 'render_type' => 'none', 'conditions' => [ 'relation' => 'or', 'terms' => [ [ 'name' => 'drip_custom_api_token', 'operator' => '!==', 'value' => '', ], [ 'name' => 'drip_api_token_source', 'operator' => '=', 'value' => 'default', ], ], ], ] ); $this->register_fields_map_control( $widget ); $widget->add_control( 'drip_custom_field_heading', [ 'label' => esc_html__( 'Send Additional Data to Drip', 'elementor-pro' ), 'type' => Controls_Manager::HEADING, 'condition' => [ 'drip_account!' => '', ], ] ); $widget->add_control( 'drip_custom_fields', [ 'label' => esc_html__( 'Form Fields', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'no', 'description' => esc_html__( 'Send all form fields to drip as custom fields', 'elementor-pro' ), 'condition' => [ 'drip_account!' => '', ], ] ); $widget->add_control( 'tags', [ 'label' => esc_html__( 'Tags', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'description' => esc_html__( 'Add as many tags as you want, comma separated.', 'elementor-pro' ), 'condition' => [ 'drip_account!' => '', ], 'ai' => [ 'active' => false, ], ] ); $widget->end_controls_section(); } public function on_export( $element ) { unset( $element['settings']['drip_api_token_source'], $element['settings']['drip_custom_api_token'], $element['settings']['drip_account'], $element['settings']['drip_fields_map'], $element['settings']['tags'], $element['settings']['drip_custom_fields'] ); return $element; } public function run( $record, $ajax_handler ) { $form_settings = $record->get( 'form_settings' ); $subscriber = $this->create_subscriber_object( $record ); if ( ! $subscriber ) { throw new \Exception( 'Integration requires an email field.' ); } if ( 'default' === $form_settings['drip_api_token_source'] ) { $api_key = $this->get_global_api_key(); } else { $api_key = $form_settings['drip_custom_api_token']; } $handler = new Drip_Handler( $api_key ); $handler->create_subscriber( $form_settings['drip_account'], $subscriber ); } /** * Create subscriber array from submitted data and form settings * returns a subscriber array or false on error * * @param Form_Record $record * * @return array|bool */ private function create_subscriber_object( Form_Record $record ) { $form_settings = $record->get( 'form_settings' ); $email = $this->map_email_field( $record ); if ( ! $email ) { return false; } $subscriber = [ 'ip_address' => Utils::get_client_ip(), 'email' => $email, ]; if ( isset( $form_settings['tags'] ) && ! empty( $form_settings['tags'] ) ) { $tags = $record->replace_setting_shortcodes( $form_settings['tags'] ); $subscriber['tags'] = explode( ',', $tags ); } $custom_fields = []; if ( isset( $form_settings['drip_custom_fields'] ) && 'yes' === $form_settings['drip_custom_fields'] ) { $custom_fields = $this->get_drip_custom_fields( $record ); } $subscriber['custom_fields'] = $custom_fields; return $subscriber; } /** * @param Form_Record $record * * @return array */ private function get_drip_custom_fields( Form_Record $record ) { $local_email_id = ''; foreach ( $record->get_form_settings( 'drip_fields_map' ) as $map_item ) { if ( 'email' === $map_item['remote_id'] ) { $local_email_id = $map_item['local_id']; } } $custom_fields = []; foreach ( $record->get( 'fields' ) as $id => $field ) { if ( $local_email_id === $id ) { continue; } $custom_fields[ $id ] = $field['value']; } return $custom_fields; } /** * extracts Email field from form based on mapping * returns email address or false if missing * * @param Form_Record $record * * @return bool */ private function map_email_field( Form_Record $record ) { $fields = $record->get( 'fields' ); foreach ( $record->get_form_settings( 'drip_fields_map' ) as $map_item ) { if ( empty( $fields[ $map_item['local_id'] ]['value'] ) ) { continue; } $value = $fields[ $map_item['local_id'] ]['value']; if ( 'email' === $map_item['remote_id'] ) { return $value; } } return false; } /** * @param array $data * * @return array * @throws \Exception */ public function handle_panel_request( array $data ) { if ( ! empty( $data['api_token'] ) && 'default' === $data['api_token'] ) { $api_key = $this->get_global_api_key(); } elseif ( ! empty( $data['custom_api_token'] ) ) { $api_key = $data['custom_api_token']; } if ( empty( $api_key ) ) { throw new \Exception( '`api_token` is required.', 400 ); } $handler = new Drip_Handler( $api_key ); return $handler->get_accounts(); } public function register_admin_fields( Settings $settings ) { $settings->add_section( Settings::TAB_INTEGRATIONS, 'drip', [ 'callback' => function() { echo '<hr><h2>' . esc_html__( 'Drip', 'elementor-pro' ) . '</h2>'; }, 'fields' => [ self::OPTION_NAME_API_KEY => [ 'label' => esc_html__( 'API Key', 'elementor-pro' ), 'field_args' => [ 'type' => 'text', 'desc' => sprintf( /* translators: 1: Link opening tag, 2: Link closing tag. */ esc_html__( 'To integrate with our forms you need an %1$sAPI Key%2$s.', 'elementor-pro' ), '<a href="http://kb.getdrip.com/general/where-can-i-find-my-api-token/" target="_blank">', '</a>' ), ], ], 'validate_api_data' => [ 'field_args' => [ 'type' => 'raw_html', 'html' => sprintf( '<button data-action="%s" data-nonce="%s" class="button elementor-button-spinner" id="elementor_pro_drip_api_token_button">%s</button>', self::OPTION_NAME_API_KEY . '_validate', wp_create_nonce( self::OPTION_NAME_API_KEY ), esc_html__( 'Validate API Key', 'elementor-pro' ) ), ], ], ], ] ); } /** * */ public function ajax_validate_api_token() { check_ajax_referer( self::OPTION_NAME_API_KEY, '_nonce' ); if ( ! isset( $_POST['api_key'] ) ) { wp_send_json_error(); } if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( 'Permission denied' ); } try { new Drip_Handler( $_POST['api_key'] ); // phpcs:ignore -- No need to sanitize to support special characters. } catch ( \Exception $exception ) { wp_send_json_error(); } wp_send_json_success(); } public function __construct() { if ( is_admin() ) { add_action( 'elementor/admin/after_create_settings/' . Settings::PAGE_ID, [ $this, 'register_admin_fields' ], 15 ); } add_action( 'wp_ajax_' . self::OPTION_NAME_API_KEY . '_validate', [ $this, 'ajax_validate_api_token' ] ); } protected function get_fields_map_control_options() { return [ 'condition' => [ 'drip_account!' => '', ], ]; } } actions/email.php 0000644 00000033150 14720522625 0010013 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Actions; use Elementor\Controls_Manager; use ElementorPro\Core\Utils\Hints; use ElementorPro\Core\Utils; use ElementorPro\Core\Utils\Collection; use ElementorPro\Modules\Forms\Classes\Ajax_Handler; use ElementorPro\Modules\Forms\Classes\Action_Base; use ElementorPro\Modules\Forms\Classes\Form_Record; use ElementorPro\Modules\Forms\Fields\Upload; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Email extends Action_Base { public function get_name() { return 'email'; } public function get_label() { return esc_html__( 'Email', 'elementor-pro' ); } public function register_settings_section( $widget ) { $widget->start_controls_section( $this->get_control_id( 'section_email' ), [ 'label' => $this->get_label(), 'tab' => Controls_Manager::TAB_CONTENT, 'condition' => [ 'submit_actions' => $this->get_name(), ], ] ); $widget->add_control( $this->get_control_id( 'email_to' ), [ 'label' => esc_html__( 'To', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'default' => get_option( 'admin_email' ), 'ai' => [ 'active' => false, ], 'placeholder' => get_option( 'admin_email' ), 'label_block' => true, 'title' => esc_html__( 'Separate emails with commas', 'elementor-pro' ), 'render_type' => 'none', 'dynamic' => [ 'active' => true, ], ] ); /* translators: %s: Site title. */ $default_message = sprintf( esc_html__( 'New message from "%s"', 'elementor-pro' ), get_option( 'blogname' ) ); $widget->add_control( $this->get_control_id( 'email_subject' ), [ 'label' => esc_html__( 'Subject', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'default' => $default_message, 'ai' => [ 'active' => false, ], 'placeholder' => $default_message, 'label_block' => true, 'render_type' => 'none', 'dynamic' => [ 'active' => true, ], ] ); $widget->add_control( $this->get_control_id( 'email_content' ), [ 'label' => esc_html__( 'Message', 'elementor-pro' ), 'type' => Controls_Manager::TEXTAREA, 'default' => '[all-fields]', 'ai' => [ 'active' => false, ], 'placeholder' => '[all-fields]', 'description' => sprintf( /* translators: %s: The [all-fields] shortcode. */ esc_html__( 'By default, all form fields are sent via %s shortcode. To customize sent fields, copy the shortcode that appears inside each field and paste it above.', 'elementor-pro' ), '<code>[all-fields]</code>' ), 'render_type' => 'none', 'dynamic' => [ 'active' => true, ], ] ); $site_domain = Utils::get_site_domain(); $widget->add_control( $this->get_control_id( 'email_from' ), [ 'label' => esc_html__( 'From Email', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'default' => 'email@' . $site_domain, 'ai' => [ 'active' => false, ], 'render_type' => 'none', 'dynamic' => [ 'active' => true, ], ] ); $widget->add_control( $this->get_control_id( 'email_from_name' ), [ 'label' => esc_html__( 'From Name', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'default' => get_bloginfo( 'name' ), 'ai' => [ 'active' => false, ], 'render_type' => 'none', 'dynamic' => [ 'active' => true, ], ] ); $widget->add_control( $this->get_control_id( 'email_reply_to' ), [ 'label' => esc_html__( 'Reply-To', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'options' => [ '' => '', ], 'render_type' => 'none', ] ); $widget->add_control( $this->get_control_id( 'email_to_cc' ), [ 'label' => esc_html__( 'Cc', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'default' => '', 'ai' => [ 'active' => false, ], 'title' => esc_html__( 'Separate emails with commas', 'elementor-pro' ), 'render_type' => 'none', 'dynamic' => [ 'active' => true, ], ] ); $widget->add_control( $this->get_control_id( 'email_to_bcc' ), [ 'label' => esc_html__( 'Bcc', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'default' => '', 'ai' => [ 'active' => false, ], 'title' => esc_html__( 'Separate emails with commas', 'elementor-pro' ), 'render_type' => 'none', 'dynamic' => [ 'active' => true, ], ] ); $widget->add_control( $this->get_control_id( 'form_metadata' ), [ 'label' => esc_html__( 'Meta Data', 'elementor-pro' ), 'type' => Controls_Manager::SELECT2, 'multiple' => true, 'label_block' => true, 'separator' => 'before', 'default' => [ 'date', 'time', 'page_url', 'user_agent', 'remote_ip', 'credit', ], 'options' => [ 'date' => esc_html__( 'Date', 'elementor-pro' ), 'time' => esc_html__( 'Time', 'elementor-pro' ), 'page_url' => esc_html__( 'Page URL', 'elementor-pro' ), 'user_agent' => esc_html__( 'User Agent', 'elementor-pro' ), 'remote_ip' => esc_html__( 'Remote IP', 'elementor-pro' ), 'credit' => esc_html__( 'Credit', 'elementor-pro' ), ], 'render_type' => 'none', ] ); $widget->add_control( $this->get_control_id( 'email_content_type' ), [ 'label' => esc_html__( 'Send As', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'default' => 'html', 'render_type' => 'none', 'options' => [ 'html' => esc_html__( 'HTML', 'elementor-pro' ), 'plain' => esc_html__( 'Plain', 'elementor-pro' ), ], ] ); $notice_id = 'site_mailer_forms_email_notice'; if ( Hints::should_show_hint( $notice_id ) ) { $notice_content = esc_html__( 'Experiencing email deliverability issues? Get your emails delivered with Site Mailer.', 'elementor-pro' ); if ( 2 === Utils\Abtest::get_variation( 'plg_site_mailer_submission' ) ) { $notice_content = esc_html__( 'Make sure your emails reach the inbox every time with Site Mailer', 'elementor-pro' ); } $widget->add_control( $this->get_control_id( 'site_mailer_promo' ), [ 'type' => Controls_Manager::RAW_HTML, 'raw' => Hints::get_notice_template( [ 'display' => ! Hints::is_dismissed( $notice_id ), 'type' => 'info', 'content' => $notice_content, 'icon' => true, 'dismissible' => $notice_id, 'button_text' => Hints::is_plugin_installed( 'site-mailer' ) ? __( 'Activate Plugin', 'elementor-pro' ) : __( 'Install Plugin', 'elementor-pro' ), 'button_event' => $notice_id, 'button_data' => [ 'action_url' => Hints::get_plugin_action_url( 'site-mailer' ), ], ], true ), ] ); } $widget->end_controls_section(); } public function on_export( $element ) { $controls_to_unset = [ 'email_to', 'email_from', 'email_from_name', 'email_subject', 'email_reply_to', 'email_to_cc', 'email_to_bcc', ]; foreach ( $controls_to_unset as $base_id ) { $control_id = $this->get_control_id( $base_id ); unset( $element['settings'][ $control_id ] ); } return $element; } /** * @param \ElementorPro\Modules\Forms\Classes\Form_Record $record * @param \ElementorPro\Modules\Forms\Classes\Ajax_Handler $ajax_handler */ public function run( $record, $ajax_handler ) { $settings = $record->get( 'form_settings' ); $send_html = 'plain' !== $settings[ $this->get_control_id( 'email_content_type' ) ]; $line_break = $send_html ? '<br>' : "\n"; $fields = [ 'email_to' => get_option( 'admin_email' ), /* translators: %s: Site title. */ 'email_subject' => sprintf( esc_html__( 'New message from "%s"', 'elementor-pro' ), get_bloginfo( 'name' ) ), 'email_content' => '[all-fields]', 'email_from_name' => get_bloginfo( 'name' ), 'email_from' => get_bloginfo( 'admin_email' ), 'email_reply_to' => 'noreply@' . Utils::get_site_domain(), 'email_to_cc' => '', 'email_to_bcc' => '', ]; foreach ( $fields as $key => $default ) { $setting = trim( $settings[ $this->get_control_id( $key ) ] ); $setting = $record->replace_setting_shortcodes( $setting ); if ( ! empty( $setting ) ) { $fields[ $key ] = $setting; } } $email_reply_to = $this->get_reply_to( $record, $fields ); $fields['email_content'] = $this->replace_content_shortcodes( $fields['email_content'], $record, $line_break ); $email_meta = ''; $form_metadata_settings = $settings[ $this->get_control_id( 'form_metadata' ) ]; foreach ( $record->get( 'meta' ) as $id => $field ) { if ( in_array( $id, $form_metadata_settings ) ) { $email_meta .= $this->field_formatted( $field ) . $line_break; } } if ( ! empty( $email_meta ) ) { $fields['email_content'] .= $line_break . '---' . $line_break . $line_break . $email_meta; } $headers = sprintf( 'From: %s <%s>' . "\r\n", $fields['email_from_name'], $fields['email_from'] ); $headers .= sprintf( 'Reply-To: %s' . "\r\n", $email_reply_to ); if ( $send_html ) { $headers .= 'Content-Type: text/html; charset=UTF-8' . "\r\n"; } $cc_header = ''; if ( ! empty( $fields['email_to_cc'] ) ) { $cc_header = 'Cc: ' . $fields['email_to_cc'] . "\r\n"; } /** * Email headers. * * Filters the headers sent when an email is sent from Elementor forms. This * hook allows developers to alter email headers triggered by Elementor forms. * * @since 1.0.0 * * @param string|array $headers Additional headers. */ $headers = apply_filters( 'elementor_pro/forms/wp_mail_headers', $headers ); /** * Email content. * * Filters the content of the email sent by Elementor forms. This hook allows * developers to alter the content of the email sent by Elementor forms. * * @since 1.0.0 * * @param string $email_content Email content. */ $fields['email_content'] = apply_filters( 'elementor_pro/forms/wp_mail_message', $fields['email_content'] ); $attachments_mode_attach = $this->get_file_by_attachment_type( $settings['form_fields'], $record, Upload::MODE_ATTACH ); $attachments_mode_both = $this->get_file_by_attachment_type( $settings['form_fields'], $record, Upload::MODE_BOTH ); $email_sent = wp_mail( $fields['email_to'], $fields['email_subject'], $fields['email_content'], $headers . $cc_header, array_merge( $attachments_mode_attach, $attachments_mode_both ) ); if ( ! empty( $fields['email_to_bcc'] ) ) { $bcc_emails = explode( ',', $fields['email_to_bcc'] ); foreach ( $bcc_emails as $bcc_email ) { wp_mail( trim( $bcc_email ), $fields['email_subject'], $fields['email_content'], $headers, array_merge( $attachments_mode_attach, $attachments_mode_both ) ); } } foreach ( $attachments_mode_attach as $file ) { @unlink( $file ); } /** * Elementor form mail sent. * * Fires when an email was sent successfully by Elementor forms. This * hook allows developers to add functionality after mail sending. * * @since 1.0.0 * * @param array $settings Form settings. * @param Form_Record $record An instance of the form record. */ do_action( 'elementor_pro/forms/mail_sent', $settings, $record ); if ( ! $email_sent ) { $message = Ajax_Handler::get_default_message( Ajax_Handler::SERVER_ERROR, $settings ); $ajax_handler->add_error_message( $message ); throw new \Exception( $message ); } } private function field_formatted( $field ) { $formatted = ''; if ( ! empty( $field['title'] ) ) { $formatted = sprintf( '%s: %s', $field['title'], $field['value'] ); } elseif ( ! empty( $field['value'] ) ) { $formatted = sprintf( '%s', $field['value'] ); } return $formatted; } // Allow overwrite the control_id with a prefix, @see Email2 protected function get_control_id( $control_id ) { return $control_id; } protected function get_reply_to( $record, $fields ) { $email_reply_to = ''; if ( ! empty( $fields['email_reply_to'] ) ) { $sent_data = $record->get( 'sent_data' ); foreach ( $record->get( 'fields' ) as $field_index => $field ) { if ( $field_index === $fields['email_reply_to'] && ! empty( $sent_data[ $field_index ] ) && is_email( $sent_data[ $field_index ] ) ) { $email_reply_to = $sent_data[ $field_index ]; break; } } } return $email_reply_to; } /** * @param string $email_content * @param Form_Record $record * * @return string */ private function replace_content_shortcodes( $email_content, $record, $line_break ) { $email_content = do_shortcode( $email_content ); $all_fields_shortcode = '[all-fields]'; if ( false !== strpos( $email_content, $all_fields_shortcode ) ) { $text = ''; foreach ( $record->get( 'fields' ) as $field ) { // Skip upload fields that only attached to the email if ( isset( $field['attachment_type'] ) && Upload::MODE_ATTACH === $field['attachment_type'] ) { continue; } $formatted = $this->field_formatted( $field ); if ( ( 'textarea' === $field['type'] ) && ( '<br>' === $line_break ) ) { $formatted = str_replace( [ "\r\n", "\n", "\r" ], '<br />', $formatted ); } $text .= $formatted . $line_break; } $email_content = str_replace( $all_fields_shortcode, $text, $email_content ); } return $email_content; } /** * @param array $form_fields * @param Form_Record $record * @param string $type * * @return array */ private function get_file_by_attachment_type( $form_fields, $record, $type ) { return Collection::make( $form_fields ) ->filter( function ( $field ) use ( $type ) { return $type === $field['attachment_type']; } ) ->map( function ( $field ) use ( $record ) { $id = $field['custom_id']; return $record->get( 'files' )[ $id ]['path'] ?? null; } ) ->filter() ->flatten() ->values(); } } actions/mailchimp.php 0000644 00000031652 14720522625 0010674 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Actions; use Elementor\Controls_Manager; use ElementorPro\Modules\Forms\Classes\Ajax_Handler; use ElementorPro\Modules\Forms\Classes\Form_Record; use ElementorPro\Modules\Forms\Classes\Integration_Base; use ElementorPro\Modules\Forms\Classes\Mailchimp_Handler; use Elementor\Settings; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Mailchimp extends Integration_Base { const OPTION_NAME_API_KEY = 'pro_mailchimp_api_key'; /** * @var string - Mailchimp API key. */ private $api_key; private function get_global_api_key() { return get_option( 'elementor_' . self::OPTION_NAME_API_KEY ); } public function get_name() { return 'mailchimp'; } public function get_label() { return esc_html__( 'MailChimp', 'elementor-pro' ); } public function register_settings_section( $widget ) { $widget->start_controls_section( 'section_mailchimp', [ 'label' => esc_html__( 'MailChimp', 'elementor-pro' ), 'condition' => [ 'submit_actions' => $this->get_name(), ], ] ); self::global_api_control( $widget, $this->get_global_api_key(), 'MailChimp API Key', [ 'mailchimp_api_key_source' => 'default', ], $this->get_name() ); $widget->add_control( 'mailchimp_api_key_source', [ 'label' => esc_html__( 'API Key', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'label_block' => false, 'options' => [ 'default' => esc_html__( 'Default', 'elementor-pro' ), 'custom' => esc_html__( 'Custom', 'elementor-pro' ), ], 'default' => 'default', ] ); $widget->add_control( 'mailchimp_api_key', [ 'label' => esc_html__( 'Custom API Key', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'condition' => [ 'mailchimp_api_key_source' => 'custom', ], 'description' => esc_html__( 'Use this field to set a custom API Key for the current form', 'elementor-pro' ), 'ai' => [ 'active' => false, ], ] ); $widget->add_control( 'mailchimp_list', [ 'label' => esc_html__( 'Audience', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'options' => [], 'render_type' => 'none', 'conditions' => [ 'relation' => 'or', 'terms' => [ [ 'name' => 'mailchimp_api_key', 'operator' => '!==', 'value' => '', ], [ 'name' => 'mailchimp_api_key_source', 'operator' => '=', 'value' => 'default', ], ], ], ] ); $widget->add_control( 'mailchimp_groups', [ 'label' => esc_html__( 'Groups', 'elementor-pro' ), 'type' => Controls_Manager::SELECT2, 'options' => [], 'label_block' => true, 'multiple' => true, 'render_type' => 'none', 'condition' => [ 'mailchimp_list!' => '', ], ] ); $widget->add_control( 'mailchimp_tags', [ 'label' => esc_html__( 'Tags', 'elementor-pro' ), 'description' => esc_html__( 'Add comma separated tags', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'render_type' => 'none', 'condition' => [ 'mailchimp_list!' => '', ], 'ai' => [ 'active' => false, ], ] ); $widget->add_control( 'mailchimp_double_opt_in', [ 'label' => esc_html__( 'Double Opt-In', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'default' => '', 'condition' => [ 'mailchimp_list!' => '', ], ] ); $this->register_fields_map_control( $widget ); $widget->end_controls_section(); } public function on_export( $element ) { unset( $element['settings']['mailchimp_api_key_source'], $element['settings']['mailchimp_api_key'], $element['settings']['mailchimp_list'], $element['settings']['mailchimp_groups'], $element['settings']['mailchimp_fields_map'] ); return $element; } public function run( $record, $ajax_handler ) { $form_settings = $record->get( 'form_settings' ); if ( 'default' === $form_settings['mailchimp_api_key_source'] ) { $this->api_key = $this->get_global_api_key(); } else { $this->api_key = $form_settings['mailchimp_api_key']; } // Data from the form in the frontend. $subscriber_data = $this->map_fields( $record ); // Create or update a subscriber. $subscriber = $this->create_or_update_subscriber( $subscriber_data, $form_settings ); // Parse the Mailchimp tags. $tags = $this->parse_tags( $form_settings['mailchimp_tags'] ); // Set the subscriber tags only if he doesn't have them already. if ( ! $this->subscriber_has_tags( $subscriber, $tags ) ) { $this->set_subscriber_tags( $subscriber, $tags ); } } /** * @param string $tags - List of comma separated tags from the forms settings ( i.e. 'tag-1, tag-2' ). * * @return array|string[] - Array of tags that were extracted from the input ( i.e. [ 'tag-1', 'tag-2' ] ). */ private function parse_tags( $tags ) { $parsed_tags = []; if ( ! empty( $tags ) ) { $parsed_tags = explode( ',', trim( $tags ) ); // Remove empty tags. $parsed_tags = array_filter( $parsed_tags ); // Trim tags. $parsed_tags = array_map( 'trim', $parsed_tags ); } return $parsed_tags; } /** * Determine if a subscriber has specific tags, and ONLY those tags. * * @param array $subscriber - Subscriber data from an API response. * @param array $tags - List of tags to check ( i.e. [ 'tag-1', 'tag-2' ] ). * * @return bool */ private function subscriber_has_tags( array $subscriber, array $tags ) { // Extract current tags. $subscriber_tags = []; foreach ( $subscriber['tags'] as $tag ) { $subscriber_tags[] = $tag['name']; } return array_diff( $tags, $subscriber_tags ) === array_diff( $subscriber_tags, $tags ); } /** * Set Mailchimp subscriber tags. * * @param array $subscriber - Subscriber data from a create/update request. * @param array $tags - List of tags to set. * * @return void */ private function set_subscriber_tags( array $subscriber, array $tags ) { // Build the request tags. $request_tags = []; // Set current tags to active. foreach ( $subscriber['tags'] as $tag ) { $request_tags[] = [ 'name' => $tag['name'], 'status' => 'active', ]; } // Set new tags to active. foreach ( $tags as $tag ) { $request_tags[] = [ 'name' => $tag, 'status' => 'active', ]; } // Send the API request. $endpoint = sprintf( 'lists/%s/members/%s/tags', $subscriber['list_id'], md5( strtolower( $subscriber['email_address'] ) ) ); $args = [ 'tags' => $request_tags, ]; $handler = new Mailchimp_Handler( $this->api_key ); $response = $handler->post( $endpoint, $args ); if ( 204 !== $response['code'] ) { $error = ! empty( $response['body']['detail'] ) ? $response['body']['detail'] : ''; $code = $response['code']; throw new \Exception( "HTTP {$code} - {$error}" ); } } /** * Get Mailchimp subscriber data. * * @param string $list - Mailchimp List ID. * @param string $email_hash - Subscriber's email hash (lowercase + MD5). * * @return array|null */ private function get_subscriber_data( $list, $email_hash ) { $handler = new Mailchimp_Handler( $this->api_key ); $end_point = sprintf( 'lists/%s/members/%s', $list, $email_hash ); try { return $handler->query( $end_point ); } catch ( \Exception $e ) { return null; } } /** * Set Mailchimp subscriber data. * * @param string $list - Mailchimp List ID. * @param string $email_hash - Subscriber's email hash (lowercase + MD5). * @param array $data - New subscriber data to set. * * @return array */ private function set_subscriber_data( $list, $email_hash, $data ) { $handler = new Mailchimp_Handler( $this->api_key ); $end_point = sprintf( 'lists/%s/members/%s', $list, $email_hash ); $response = $handler->post( $end_point, $data, [ 'method' => 'PUT', // Add or Update ] ); if ( 200 !== $response['code'] ) { $error = ! empty( $response['body']['detail'] ) ? $response['body']['detail'] : ''; $code = $response['code']; throw new \Exception( "HTTP {$code} - {$error}" ); } return $response['body']; } /** * Create or update a Mailchimp subscriber. * * @param array $subscriber - Subscriber data from the form in the frontend. * @param array $form_settings - Settings from the editor. * * @return array - An array that contains the newly created subscriber's data. */ private function create_or_update_subscriber( array $subscriber, array $form_settings ) { if ( ! empty( $form_settings['mailchimp_groups'] ) ) { $subscriber['interests'] = []; } if ( is_array( $form_settings['mailchimp_groups'] ) ) { foreach ( $form_settings['mailchimp_groups'] as $mailchimp_group ) { $subscriber['interests'][ $mailchimp_group ] = true; } } if ( ! empty( $form_settings['mailchimp_tags'] ) ) { $subscriber['tags'] = explode( ',', trim( $form_settings['mailchimp_tags'] ) ); } $list = $form_settings['mailchimp_list']; $email_hash = md5( strtolower( $subscriber['email_address'] ) ); $double_opt_in = ( 'yes' === $form_settings['mailchimp_double_opt_in'] ); $subscriber['status_if_new'] = $double_opt_in ? 'pending' : 'subscribed'; if ( $double_opt_in ) { $subscriber_data = $this->get_subscriber_data( $list, $email_hash ); // Change the current status only if the user isn't subscribed already. if ( $subscriber_data && 'subscribed' !== $subscriber_data['status'] ) { $subscriber['status'] = 'pending'; } } else { $subscriber['status'] = 'subscribed'; } return $this->set_subscriber_data( $list, $email_hash, $subscriber ); } /** * @param Form_Record $record * * @return array */ private function map_fields( $record ) { $subscriber = []; $fields = $record->get( 'fields' ); // Other form has a field mapping foreach ( $record->get_form_settings( 'mailchimp_fields_map' ) as $map_item ) { if ( empty( $fields[ $map_item['local_id'] ]['value'] ) ) { continue; } $value = $fields[ $map_item['local_id'] ]['value']; if ( 'email' === $map_item['remote_id'] ) { $subscriber['email_address'] = $value; } else { $subscriber['merge_fields'][ $map_item['remote_id'] ] = $value; } } return $subscriber; } public function ajax_validate_api_token() { check_ajax_referer( self::OPTION_NAME_API_KEY, '_nonce' ); if ( ! isset( $_POST['api_key'] ) ) { wp_send_json_error(); } if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( 'Permission denied' ); } try { new Mailchimp_Handler( $_POST['api_key'] ); // phpcs:ignore -- No need to sanitize to support special characters. } catch ( \Exception $exception ) { wp_send_json_error(); } wp_send_json_success(); } /** * @param array $data * * @return array * @throws \Exception */ public function handle_panel_request( array $data ) { if ( ! empty( $data['use_global_api_key'] ) && 'default' === $data['use_global_api_key'] ) { $api_key = $this->get_global_api_key(); } elseif ( ! empty( $data['api_key'] ) ) { $api_key = $data['api_key']; } if ( empty( $api_key ) ) { throw new \Exception( '`api_key` is required.', 400 ); } $handler = new Mailchimp_Handler( $api_key ); switch ( $data['mailchimp_action'] ) { case 'lists': return $handler->get_lists(); case 'fields': return $handler->get_fields( $data['mailchimp_list'] ); case 'groups': return $handler->get_groups( $data['mailchimp_list'] ); default: return $handler->get_list_details( $data['mailchimp_list'] ); } } public function register_admin_fields( Settings $settings ) { $settings->add_section( Settings::TAB_INTEGRATIONS, 'mailchimp', [ 'callback' => function() { echo '<hr><h2>' . esc_html__( 'MailChimp', 'elementor-pro' ) . '</h2>'; }, 'fields' => [ self::OPTION_NAME_API_KEY => [ 'label' => esc_html__( 'API Key', 'elementor-pro' ), 'field_args' => [ 'type' => 'text', 'desc' => sprintf( /* translators: 1: Link opening tag, 2: Link closing tag. */ esc_html__( 'To integrate with our forms you need an %1$sAPI Key%2$s.', 'elementor-pro' ), '<a href="https://kb.mailchimp.com/integrations/api-integrations/about-api-keys" target="_blank">', '</a>' ), ], ], 'validate_api_data' => [ 'field_args' => [ 'type' => 'raw_html', 'html' => sprintf( '<button data-action="%s" data-nonce="%s" class="button elementor-button-spinner" id="elementor_pro_mailchimp_api_key_button">%s</button>', self::OPTION_NAME_API_KEY . '_validate', wp_create_nonce( self::OPTION_NAME_API_KEY ), esc_html__( 'Validate API Key', 'elementor-pro' ) ), ], ], ], ] ); } public function __construct() { if ( is_admin() ) { add_action( 'elementor/admin/after_create_settings/' . Settings::PAGE_ID, [ $this, 'register_admin_fields' ], 14 ); } add_action( 'wp_ajax_' . self::OPTION_NAME_API_KEY . '_validate', [ $this, 'ajax_validate_api_token' ] ); } protected function get_fields_map_control_options() { return [ 'condition' => [ 'mailchimp_list!' => '', ], ]; } } actions/activecampaign.php 0000644 00000022415 14720522625 0011701 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Actions; use Elementor\Controls_Manager; use Elementor\Settings; use ElementorPro\Modules\Forms\Classes\Form_Record; use ElementorPro\Modules\Forms\Classes\Integration_Base; use ElementorPro\Modules\Forms\Classes; use ElementorPro\Core\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Activecampaign extends Integration_Base { const OPTION_NAME_API_KEY = 'pro_activecampaign_api_key'; const OPTION_NAME_API_URL = 'pro_activecampaign_api_url'; private function get_global_api_key() { return get_option( 'elementor_' . self::OPTION_NAME_API_KEY, '' ); } private function get_global_api_url() { return get_option( 'elementor_' . self::OPTION_NAME_API_URL, '' ); } public function get_name() { return 'activecampaign'; } public function get_label() { return esc_html__( 'ActiveCampaign', 'elementor-pro' ); } public function register_settings_section( $widget ) { $widget->start_controls_section( 'section_activecampaign', [ 'label' => esc_html__( 'ActiveCampaign', 'elementor-pro' ), 'condition' => [ 'submit_actions' => $this->get_name(), ], ] ); self::global_api_control( $widget, $this->get_global_api_key(), 'ActiveCampaign API credentials', [ 'activecampaign_api_credentials_source' => 'default', ], $this->get_name() ); $widget->add_control( 'activecampaign_api_credentials_source', [ 'label' => esc_html__( 'API Key', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'label_block' => false, 'options' => [ 'default' => esc_html__( 'Default', 'elementor-pro' ), 'custom' => esc_html__( 'Custom', 'elementor-pro' ), ], 'default' => 'default', ] ); $widget->add_control( 'activecampaign_api_key', [ 'label' => esc_html__( 'Custom API Key', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'description' => esc_html__( 'Use this field to set a custom API Key for the current form', 'elementor-pro' ), 'condition' => [ 'activecampaign_api_credentials_source' => 'custom', ], 'ai' => [ 'active' => false, ], ] ); $widget->add_control( 'activecampaign_api_url', [ 'label' => esc_html__( 'API URL', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'description' => esc_html__( 'Use this field to set a custom API URL for the current form', 'elementor-pro' ), 'condition' => [ 'activecampaign_api_credentials_source' => 'custom', ], 'ai' => [ 'active' => false, ], ] ); $widget->add_control( 'activecampaign_list', [ 'label' => esc_html__( 'List', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'options' => [], 'render_type' => 'none', 'conditions' => [ 'relation' => 'or', 'terms' => [ [ 'name' => 'activecampaign_api_credentials_source', 'operator' => '=', 'value' => 'default', ], [ 'relation' => 'and', 'terms' => [ [ 'name' => 'activecampaign_api_url', 'operator' => '!==', 'value' => '', ], [ 'name' => 'activecampaign_api_key', 'operator' => '!==', 'value' => '', ], ], ], ], ], ] ); $this->register_fields_map_control( $widget ); $widget->add_control( 'activecampaign_tags', [ 'label' => esc_html__( 'Tags', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'description' => esc_html__( 'Add as many tags as you want, comma separated.', 'elementor-pro' ), 'condition' => [ 'activecampaign_list!' => '', ], 'ai' => [ 'active' => false, ], ] ); $widget->end_controls_section(); } public function on_export( $element ) { unset( $element['settings']['activecampaign_api_credentials_source'], $element['settings']['activecampaign_api_key'], $element['settings']['activecampaign_api_url'], $element['settings']['activecampaign_list'], $element['settings']['activecampaign_fields_map'], $element['settings']['activecampaign_tags'] ); return $element; } public function run( $record, $ajax_handler ) { $form_settings = $record->get( 'form_settings' ); $subscriber = $this->create_subscriber_object( $record ); if ( ! $subscriber ) { throw new \Exception( 'Integration requires an email field and a selected list.' ); } if ( 'default' === $form_settings['activecampaign_api_credentials_source'] ) { $api_key = $this->get_global_api_key(); $api_url = $this->get_global_api_url(); } else { $api_key = $form_settings['activecampaign_api_key']; $api_url = $form_settings['activecampaign_api_url']; } $handler = new Classes\Activecampaign_Handler( $api_key, $api_url ); $handler->create_subscriber( $subscriber ); } /** * Create subscriber array from submitted data and form settings * returns a subscriber array or false on error * * @param Form_Record $record * * @return array|bool */ private function create_subscriber_object( Form_Record $record ) { $form_settings = $record->get( 'form_settings' ); $subscriber = $this->map_fields( $record ); if ( ! isset( $subscriber['email'] ) ) { return false; } if ( ! isset( $form_settings['activecampaign_list'] ) ) { return false; } $subscriber['ip4'] = Utils::get_client_ip(); $list_id = $form_settings['activecampaign_list']; $subscriber[ 'p[' . $list_id . ']' ] = $list_id; if ( isset( $form_settings['activecampaign_tags'] ) && ! empty( $form_settings['activecampaign_tags'] ) ) { $subscriber['tags'] = $form_settings['activecampaign_tags']; } if ( isset( $form_settings['form_id'] ) && ! empty( $form_settings['form_id'] ) ) { $subscriber['form'] = $form_settings['form_id']; } return $subscriber; } /** * @param Form_Record $record * * @return array */ private function map_fields( Form_Record $record ) { $subscriber = []; $fields = $record->get( 'fields' ); // Other form has a field mapping foreach ( $record->get_form_settings( 'activecampaign_fields_map' ) as $map_item ) { if ( empty( $fields[ $map_item['local_id'] ]['value'] ) ) { continue; } $value = $fields[ $map_item['local_id'] ]['value']; $subscriber[ $map_item['remote_id'] ] = $value; } return $subscriber; } /** * @param array $data * * @return array * @throws \Exception */ public function handle_panel_request( array $data ) { if ( ! empty( $data['api_cred'] ) && 'default' === $data['api_cred'] ) { $api_key = $this->get_global_api_key(); $api_url = $this->get_global_api_url(); } elseif ( ! empty( $data['api_key'] ) && ! empty( $data['api_url'] ) ) { $api_key = $data['api_key']; $api_url = $data['api_url']; } if ( empty( $api_key ) ) { throw new \Exception( '`api_key` is required.', 400 ); } if ( empty( $api_url ) ) { throw new \Exception( '`api_url` is required.', 400 ); } $handler = new Classes\Activecampaign_Handler( $api_key, $api_url ); return $handler->get_lists(); } public function ajax_validate_api_token() { check_ajax_referer( self::OPTION_NAME_API_KEY, '_nonce' ); if ( ! isset( $_POST['api_key'] ) || ! isset( $_POST['api_url'] ) ) { wp_send_json_error(); } if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( 'Permission denied' ); } try { new Classes\Activecampaign_Handler( Utils::_unstable_get_super_global_value( $_POST, 'api_key' ), Utils::_unstable_get_super_global_value( $_POST, 'api_url' ) ); } catch ( \Exception $exception ) { wp_send_json_error(); } wp_send_json_success(); } public function register_admin_fields( Settings $settings ) { $settings->add_section( Settings::TAB_INTEGRATIONS, 'activecampign', [ 'callback' => function() { echo '<hr><h2>' . esc_html__( 'ActiveCampaign', 'elementor-pro' ) . '</h2>'; }, 'fields' => [ self::OPTION_NAME_API_KEY => [ 'label' => esc_html__( 'API Key', 'elementor-pro' ), 'field_args' => [ 'type' => 'text', ], ], self::OPTION_NAME_API_URL => [ 'label' => esc_html__( 'API URL', 'elementor-pro' ), 'field_args' => [ 'type' => 'url', 'desc' => sprintf( /* translators: 1: Link opening tag, 2: Link closing tag. */ esc_html__( 'To integrate with our forms you need an %1$sAPI Key%2$s.', 'elementor-pro' ), '<a href="https://help.activecampaign.com/hc/en-us/articles/207317590-Getting-started-with-the-API" target="_blank">', '</a>' ), ], ], 'validate_api_data' => [ 'field_args' => [ 'type' => 'raw_html', 'html' => sprintf( '<button data-action="%s" data-nonce="%s" class="button elementor-button-spinner" id="elementor_pro_activecampaign_api_key_button">%s</button>', self::OPTION_NAME_API_KEY . '_validate', wp_create_nonce( self::OPTION_NAME_API_KEY ), esc_html__( 'Validate API Key', 'elementor-pro' ) ), ], ], ], ] ); } public function __construct() { if ( is_admin() ) { add_action( 'elementor/admin/after_create_settings/' . Settings::PAGE_ID, [ $this, 'register_admin_fields' ], 15 ); } add_action( 'wp_ajax_' . self::OPTION_NAME_API_KEY . '_validate', [ $this, 'ajax_validate_api_token' ] ); } protected function get_fields_map_control_options() { return [ 'condition' => [ 'activecampaign_list!' => '', ], ]; } } actions/slack.php 0000644 00000014100 14720522625 0010013 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Actions; use Elementor\Controls_Manager; use ElementorPro\Core\Utils; use ElementorPro\Modules\Forms\Classes\Action_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Slack extends Action_Base { public function get_name() { return 'slack'; } public function get_label() { return esc_html__( 'Slack', 'elementor-pro' ); } public function register_settings_section( $widget ) { $widget->start_controls_section( 'section_slack', [ 'label' => esc_html__( 'Slack', 'elementor-pro' ), 'condition' => [ 'submit_actions' => $this->get_name(), ], ] ); $widget->add_control( 'slack_webhook', [ 'label' => esc_html__( 'Webhook URL', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'placeholder' => 'https://hooks.slack.com/services/', 'ai' => [ 'active' => false, ], 'label_block' => true, 'separator' => 'before', 'description' => esc_html__( 'Enter the webhook URL that will receive the form\'s submitted data.', 'elementor-pro' ) . ' ' . sprintf( '<a href="%s" target="_blank">%s</a>.', 'https://slack.com/apps/A0F7XDUAZ-incoming-webhooks/', esc_html__( 'Click here for Instructions', 'elementor-pro' ) ), 'render_type' => 'none', 'classes' => 'elementor-control-direction-ltr', 'dynamic' => [ 'active' => true, ], ] ); $widget->add_control( 'slack_channel', [ 'label' => esc_html__( 'Channel', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'ai' => [ 'active' => false, ], 'dynamic' => [ 'active' => true, ], ] ); $widget->add_control( 'slack_username', [ 'label' => esc_html__( 'Username', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'ai' => [ 'active' => false, ], 'dynamic' => [ 'active' => true, ], ] ); $widget->add_control( 'slack_pretext', [ 'label' => esc_html__( 'Pre Text', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'ai' => [ 'active' => false, ], 'dynamic' => [ 'active' => true, ], ] ); $widget->add_control( 'slack_title', [ 'label' => esc_html__( 'Title', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'ai' => [ 'active' => false, ], 'dynamic' => [ 'active' => true, ], ] ); $widget->add_control( 'slack_text', [ 'label' => esc_html__( 'Description', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'ai' => [ 'active' => false, ], 'dynamic' => [ 'active' => true, ], ] ); $widget->add_control( 'slack_add_fields', [ 'label' => esc_html__( 'Form Data', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes', ] ); $widget->add_control( 'slack_add_ts', [ 'label' => esc_html__( 'Timestamp', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes', ] ); $widget->add_control( 'slack_webhook_color', [ 'label' => esc_html__( 'Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'alpha' => false, 'default' => '#D30C5C', ] ); $widget->end_controls_section(); } public function on_export( $element ) { unset( $element['slack_add_ts'], $element['slack_add_fields'], $element['slack_webhook_color'], $element['slack_text'], $element['slack_pretext'], $element['slack_title'], $element['slack_username'], $element['slack_webhook'], $element['slack_channel'] ); return $element; } public function run( $record, $ajax_handler ) { $settings = $record->get( 'form_settings' ); if ( empty( $settings['slack_webhook'] ) || false === strpos( $settings['slack_webhook'], 'https://hooks.slack.com/services/' ) ) { return; } // Build slack webhook data $webhook_data = [ 'username' => isset( $settings['slack_username'] ) ? $settings['slack_username'] : '', ]; if ( ! empty( $settings['slack_channel'] ) ) { $webhook_data['channel'] = $settings['slack_channel']; } // The nonce already validated // phpcs:ignore WordPress.Security.NonceVerification.Missing $referrer = Utils::_unstable_get_super_global_value( $_POST, 'referrer' ); $attachment = [ 'text' => esc_html__( 'A new Form Submission has been received', 'elementor-pro' ), 'title' => esc_html__( 'A new Submission', 'elementor-pro' ), 'color' => isset( $settings['slack_webhook_color'] ) ? $settings['slack_webhook_color'] : '#D30C5C', 'title_link' => esc_url( $referrer ?? site_url() ), ]; if ( ! empty( $settings['slack_title'] ) ) { $attachment['title'] = $settings['slack_title']; } if ( ! empty( $settings['slack_text'] ) ) { $attachment['text'] = $settings['slack_text']; } if ( ! empty( $settings['slack_pretext'] ) ) { $attachment['pretext'] = $settings['slack_pretext']; } if ( ! empty( $settings['slack_add_fields'] ) && 'yes' === $settings['slack_add_fields'] ) { // prepare Form Data $raw_fields = $record->get( 'fields' ); $fields = []; foreach ( $raw_fields as $id => $field ) { $fields[] = [ 'title' => $field['title'] ? $field['title'] : $id, 'value' => $field['value'], 'short' => false, ]; } $attachment['fields'] = $fields; } if ( ! empty( $settings['slack_add_ts'] ) && 'yes' === $settings['slack_add_ts'] ) { $attachment = array_merge( $attachment, [ 'footer' => sprintf( /* translators: %s: Elementor. */ esc_html__( 'Powered by %s', 'elementor-pro' ), 'Elementor' ), 'footer_icon' => is_ssl() ? ELEMENTOR_ASSETS_URL . 'images/logo-icon.png' : null, 'ts' => time(), ] ); } $webhook_data['attachments'] = [ $attachment ]; $webhook_data = apply_filters( 'elementor_pro/forms/slack/webhook_args', $webhook_data ); $response = wp_remote_post( $settings['slack_webhook'], [ 'headers' => [ 'Content-Type' => 'application/json', ], 'body' => wp_json_encode( $webhook_data ), ] ); if ( 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { throw new \Exception( 'Webhook error.' ); } } } actions/mailpoet3.php 0000644 00000006770 14720522625 0010631 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Actions; use Elementor\Controls_Manager; use ElementorPro\Modules\Forms\Classes\Form_Record; use ElementorPro\Modules\Forms\Classes\Integration_Base; use MailPoet\API\API; use MailPoet\Models\Subscriber; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly }; class Mailpoet3 extends Integration_Base { public function get_name() { return 'mailpoet3'; } public function get_label() { return 'MailPoet 3'; } public function register_settings_section( $widget ) { $widget->start_controls_section( 'section_mailpoet3', [ 'label' => esc_html__( 'MailPoet 3', 'elementor-pro' ), 'condition' => [ 'submit_actions' => $this->get_name(), ], ] ); $mailpoet3_lists = API::MP( 'v1' )->getLists(); $options = []; foreach ( $mailpoet3_lists as $list ) { $options[ $list['id'] ] = $list['name']; } $widget->add_control( 'mailpoet3_lists', [ 'label' => esc_html__( 'List', 'elementor-pro' ), 'type' => Controls_Manager::SELECT2, 'label_block' => true, 'options' => $options, 'render_type' => 'none', ] ); $this->register_fields_map_control( $widget ); $widget->end_controls_section(); } public function on_export( $element ) { unset( $element['mailpoet3_lists'] ); return $element; } public function run( $record, $ajax_handler ) { $settings = $record->get( 'form_settings' ); $subscriber = $this->map_fields( $record ); $existing_subscriber = false; try { API::MP( 'v1' )->addSubscriber( $subscriber, (array) $settings['mailpoet3_lists'] ); $existing_subscriber = false; } catch ( \Exception $exception ) { $error_string = esc_html__( 'This subscriber already exists.', 'mailpoet' ); // phpcs:ignore WordPress.WP.I18n if ( $error_string === $exception->getMessage() ) { $existing_subscriber = true; } else { throw $exception; } } if ( $existing_subscriber ) { API::MP( 'v1' )->subscribeToLists( $subscriber['email'], (array) $settings['mailpoet3_lists'] ); } } /** * @param Form_Record $record * * @return array */ private function map_fields( $record ) { $settings = $record->get( 'form_settings' ); $fields = $record->get( 'fields' ); $subscriber = []; foreach ( $settings['mailpoet3_fields_map'] as $map_item ) { if ( empty( $fields[ $map_item['local_id'] ]['value'] ) ) { continue; } $subscriber[ $map_item['remote_id'] ] = $fields[ $map_item['local_id'] ]['value']; } return $subscriber; } protected function get_fields_map_control_options() { $mailpoet_fields = [ [ 'remote_id' => 'first_name', 'remote_label' => esc_html__( 'First Name', 'elementor-pro' ), 'remote_type' => 'text', ], [ 'remote_id' => 'last_name', 'remote_label' => esc_html__( 'Last Name', 'elementor-pro' ), 'remote_type' => 'text', ], [ 'remote_id' => 'email', 'remote_label' => esc_html__( 'Email', 'elementor-pro' ), 'remote_type' => 'email', 'remote_required' => true, ], ]; $fields = API::MP( 'v1' )->getSubscriberFields(); if ( ! empty( $fields ) && is_array( $fields ) ) { foreach ( $fields as $index => $remote ) { if ( in_array( $remote['id'], [ 'first_name', 'last_name', 'email' ] ) ) { continue; } $mailpoet_fields[] = [ 'remote_id' => $remote['id'], 'remote_label' => $remote['name'], 'remote_type' => 'text', ]; } } return [ 'default' => $mailpoet_fields, 'condition' => [ 'mailpoet3_lists!' => '', ], ]; } } actions/getresponse.php 0000644 00000021454 14720522625 0011266 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Actions; use Elementor\Controls_Manager; use ElementorPro\Modules\Forms\Classes\Form_Record; use ElementorPro\Modules\Forms\Classes\Integration_Base; use ElementorPro\Modules\Forms\Classes\Getresponse_Handler; use ElementorPro\Core\Utils; use Elementor\Settings; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Getresponse extends Integration_Base { const OPTION_NAME_API_KEY = 'pro_getresponse_api_key'; private function get_global_api_key() { return get_option( 'elementor_' . self::OPTION_NAME_API_KEY, '' ); } public function get_name() { return 'getresponse'; } public function get_label() { return esc_html__( 'GetResponse', 'elementor-pro' ); } public function register_settings_section( $widget ) { $widget->start_controls_section( 'section_getresponse', [ 'label' => esc_html__( 'GetResponse', 'elementor-pro' ), 'condition' => [ 'submit_actions' => $this->get_name(), ], ] ); self::global_api_control( $widget, $this->get_global_api_key(), 'GetResponse API key', [ 'getresponse_api_key_source' => 'default', ], $this->get_name() ); $widget->add_control( 'getresponse_api_key_source', [ 'label' => esc_html__( 'API Key', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'label_block' => false, 'options' => [ 'default' => esc_html__( 'Default', 'elementor-pro' ), 'custom' => esc_html__( 'Custom', 'elementor-pro' ), ], 'default' => 'default', ] ); $widget->add_control( 'getresponse_custom_api_key', [ 'label' => esc_html__( 'Custom API Key', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'description' => esc_html__( 'Use this field to set a custom API Key for the current form', 'elementor-pro' ), 'condition' => [ 'getresponse_api_key_source' => 'custom', ], 'ai' => [ 'active' => false, ], ] ); $widget->add_control( 'getresponse_list', [ 'label' => esc_html__( 'List', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'options' => [], 'render_type' => 'none', 'conditions' => [ 'relation' => 'or', 'terms' => [ [ 'name' => 'getresponse_custom_api_key', 'operator' => '!==', 'value' => '', ], [ 'name' => 'getresponse_api_key_source', 'operator' => '=', 'value' => 'default', ], ], ], ] ); $widget->add_control( 'getresponse_dayofcycle', [ 'label' => esc_html__( 'Day Of Cycle', 'elementor-pro' ), 'type' => Controls_Manager::NUMBER, 'min' => 0, 'condition' => [ 'getresponse_list!' => '', ], ] ); $this->register_fields_map_control( $widget ); $widget->end_controls_section(); } public function on_export( $element ) { unset( $element['settings']['getresponse_api_key_source'], $element['settings']['getresponse_custom_api_key'], $element['settings']['getresponse_list'], $element['settings']['getresponse_fields_map'] ); return $element; } public function run( $record, $ajax_handler ) { $form_settings = $record->get( 'form_settings' ); $subscriber = $this->create_subscriber_object( $record ); if ( ! $subscriber ) { throw new \Exception( 'Integration requires an email field.' ); } if ( 'default' === $form_settings['getresponse_api_key_source'] ) { $api_key = $this->get_global_api_key(); } else { $api_key = $form_settings['getresponse_custom_api_key']; } try { $handler = new Getresponse_Handler( $api_key ); $handler->create_subscriber( $subscriber ); } catch ( \Exception $exception ) { foreach ( (array) $handler->rest_client->request_cache as $response ) { if ( isset( $response['parsed'] ) || ! isset( $response['raw'] ) || ! isset( $response['raw']['response'] ) || ! isset( $response['raw']['response']['code'] ) ) { continue; } if ( ! in_array( $response['raw']['response']['code'], [ 200, 202, 409 ] ) ) { throw new \Exception( $exception->getMessage() ); } } } } /** * Create subscriber array from submitted data and form settings * returns a subscriber array or false on error * * @param Form_Record $record * * @return array|bool */ private function create_subscriber_object( Form_Record $record ) { $form_settings = $record->get( 'form_settings' ); $subscriber = $this->map_fields( $record ); if ( ! isset( $subscriber['email'] ) ) { return false; } if ( isset( $form_settings['getresponse_dayofcycle'] ) ) { $subscriber['dayOfCycle'] = intval( $form_settings['getresponse_dayofcycle'] ); } $subscriber['ipAddress'] = Utils::get_client_ip(); $subscriber['campaign'] = [ 'campaignId' => $form_settings['getresponse_list'] ]; return $subscriber; } /** * @param Form_Record $record * * @return array */ private function get_getresponse_custom_fields( Form_Record $record ) { $local_email_id = ''; $local_name_id = ''; foreach ( $record->get_form_settings( 'getresponse_fields_map' ) as $map_item ) { if ( 'email' === $map_item['remote_id'] ) { $local_email_id = $map_item['local_id']; } if ( 'name' === $map_item['remote_id'] ) { $local_name_id = $map_item['local_id']; } } $custom_fields = []; foreach ( $record->get( 'fields' ) as $id => $field ) { if ( in_array( $id, [ $local_email_id, $local_name_id ] ) ) { continue; } $custom_fields[ $id ] = $field['value']; } return $custom_fields; } /** * @param Form_Record $record * * @return array */ private function map_fields( Form_Record $record ) { $subscriber = []; $custom_fields = []; $fields = $record->get( 'fields' ); // Other form has a field mapping foreach ( $record->get_form_settings( 'getresponse_fields_map' ) as $map_item ) { if ( empty( $fields[ $map_item['local_id'] ]['value'] ) ) { continue; } $value = $fields[ $map_item['local_id'] ]['value']; if ( in_array( $map_item['remote_id'], [ 'name', 'email' ] ) ) { $subscriber[ $map_item['remote_id'] ] = $value; continue; } $custom_fields[] = [ 'customFieldId' => $map_item['remote_id'], 'value' => [ $value ], ]; } $subscriber['customFieldValues'] = $custom_fields; return $subscriber; } /** * @param array $data * * @return array * @throws \Exception */ public function handle_panel_request( array $data ) { if ( ! empty( $data['api_key'] ) && 'default' === $data['api_key'] ) { $api_key = $this->get_global_api_key(); } elseif ( ! empty( $data['custom_api_key'] ) ) { $api_key = $data['custom_api_key']; } if ( empty( $api_key ) ) { throw new \Exception( '`api_key` is required.', 400 ); } $handler = new Getresponse_Handler( $api_key ); if ( 'lists' === $data['getresponse_action'] ) { return $handler->get_lists(); } return $handler->get_fields(); } public function ajax_validate_api_token() { check_ajax_referer( self::OPTION_NAME_API_KEY, '_nonce' ); if ( ! isset( $_POST['api_key'] ) ) { wp_send_json_error(); } if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( 'Permission denied' ); } try { new Getresponse_Handler( $_POST['api_key'] ); // phpcs:ignore -- No need to sanitize to support special characters. } catch ( \Exception $exception ) { wp_send_json_error(); } wp_send_json_success(); } public function register_admin_fields( Settings $settings ) { $settings->add_section( Settings::TAB_INTEGRATIONS, 'getresponse', [ 'callback' => function() { echo '<hr><h2>' . esc_html__( 'GetResponse', 'elementor-pro' ) . '</h2>'; }, 'fields' => [ self::OPTION_NAME_API_KEY => [ 'label' => esc_html__( 'API Key', 'elementor-pro' ), 'field_args' => [ 'type' => 'text', 'desc' => sprintf( /* translators: 1: Link opening tag, 2: Link closing tag. */ esc_html__( 'To integrate with our forms you need an %1$sAPI Key%2$s.', 'elementor-pro' ), '<a href="https://www.getresponse.com" target="_blank">', '</a>' ), ], ], 'validate_api_data' => [ 'field_args' => [ 'type' => 'raw_html', 'html' => sprintf( '<button data-action="%s" data-nonce="%s" class="button elementor-button-spinner" id="elementor_pro_getresponse_api_key_button">%s</button>', self::OPTION_NAME_API_KEY . '_validate', wp_create_nonce( self::OPTION_NAME_API_KEY ), esc_html__( 'Validate API Key', 'elementor-pro' ) ), ], ], ], ] ); } public function __construct() { if ( is_admin() ) { add_action( 'elementor/admin/after_create_settings/' . Settings::PAGE_ID, [ $this, 'register_admin_fields' ], 15 ); } add_action( 'wp_ajax_' . self::OPTION_NAME_API_KEY . '_validate', [ $this, 'ajax_validate_api_token' ] ); } protected function get_fields_map_control_options() { return [ 'condition' => [ 'getresponse_list!' => '', ], ]; } } actions/convertkit.php 0000644 00000016507 14720522625 0011123 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Actions; use Elementor\Controls_Manager; use ElementorPro\Modules\Forms\Classes\Form_Record; use ElementorPro\Modules\Forms\Classes\Integration_Base; use ElementorPro\Modules\Forms\Classes\Convertkit_Handler; use ElementorPro\Core\Utils; use Elementor\Settings; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Convertkit extends Integration_Base { const OPTION_NAME_API_KEY = 'pro_convertkit_api_key'; private function get_global_api_key() { return get_option( 'elementor_' . self::OPTION_NAME_API_KEY ); } public function get_name() { return 'convertkit'; } public function get_label() { return esc_html__( 'ConvertKit', 'elementor-pro' ); } public function register_settings_section( $widget ) { $widget->start_controls_section( 'section_convertkit', [ 'label' => esc_html__( 'ConvertKit', 'elementor-pro' ), 'condition' => [ 'submit_actions' => $this->get_name(), ], ] ); self::global_api_control( $widget, $this->get_global_api_key(), 'ConvertKit API key', [ 'convertkit_api_key_source' => 'default', ], $this->get_name() ); $widget->add_control( 'convertkit_api_key_source', [ 'label' => esc_html__( 'API Key', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'label_block' => false, 'options' => [ 'default' => esc_html__( 'Default', 'elementor-pro' ), 'custom' => esc_html__( 'Custom', 'elementor-pro' ), ], 'default' => 'default', ] ); $widget->add_control( 'convertkit_custom_api_key', [ 'label' => esc_html__( 'Custom API Key', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'description' => esc_html__( 'Use this field to set a custom API Key for the current form', 'elementor-pro' ), 'condition' => [ 'convertkit_api_key_source' => 'custom', ], 'ai' => [ 'active' => false, ], ] ); $widget->add_control( 'convertkit_form', [ 'label' => esc_html__( 'Form', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'options' => [], 'render_type' => 'none', 'conditions' => [ 'relation' => 'or', 'terms' => [ [ 'name' => 'convertkit_custom_api_key', 'operator' => '!==', 'value' => '', ], [ 'name' => 'convertkit_api_key_source', 'operator' => '=', 'value' => 'default', ], ], ], ] ); $this->register_fields_map_control( $widget ); $widget->add_control( 'convertkit_tags', [ 'label' => esc_html__( 'Tags', 'elementor-pro' ), 'type' => Controls_Manager::SELECT2, 'options' => [], 'multiple' => true, 'render_type' => 'none', 'label_block' => true, 'condition' => [ 'convertkit_form!' => '', ], ] ); $widget->end_controls_section(); } public function on_export( $element ) { unset( $element['settings']['convertkit_api_key_source'], $element['settings']['convertkit_custom_api_key'], $element['settings']['convertkit_form'], $element['settings']['convertkit_fields_map'] ); return $element; } public function run( $record, $ajax_handler ) { $form_settings = $record->get( 'form_settings' ); $subscriber = $this->create_subscriber_object( $record ); if ( ! $subscriber ) { throw new \Exception( 'Integration requires an email field.' ); } if ( 'default' === $form_settings['convertkit_api_key_source'] ) { $api_key = $this->get_global_api_key(); } else { $api_key = $form_settings['convertkit_custom_api_key']; } if ( '' !== $form_settings['convertkit_tags'] ) { $subscriber['tags'] = $form_settings['convertkit_tags']; } $handler = new ConvertKit_Handler( $api_key ); $handler->create_subscriber( $form_settings['convertkit_form'], $subscriber ); } /** * Create subscriber array from submitted data and form settings * returns a subscriber array or false on error * * @param Form_Record $record * * @return array|bool */ private function create_subscriber_object( Form_Record $record ) { $subscriber = $this->map_fields( $record ); if ( ! isset( $subscriber['email'] ) ) { return false; } $subscriber['ipAddress'] = Utils::get_client_ip(); return $subscriber; } /** * @param Form_Record $record * * @return array */ private function map_fields( Form_Record $record ) { $subscriber = []; $fields = $record->get( 'fields' ); // Other form has a field mapping foreach ( $record->get_form_settings( 'convertkit_fields_map' ) as $map_item ) { if ( empty( $fields[ $map_item['local_id'] ]['value'] ) ) { continue; } $value = $fields[ $map_item['local_id'] ]['value']; if ( in_array( $map_item['remote_id'], [ 'first_name', 'email' ] ) ) { $subscriber[ $map_item['remote_id'] ] = $value; continue; } } return $subscriber; } /** * @param array $data * * @return array * @throws \Exception */ public function handle_panel_request( array $data ) { if ( ! empty( $data['api_key'] ) && 'default' === $data['api_key'] ) { $api_key = $this->get_global_api_key(); } elseif ( ! empty( $data['custom_api_key'] ) ) { $api_key = $data['custom_api_key']; } if ( empty( $api_key ) ) { throw new \Exception( '`api_key` is required.', 400 ); } $handler = new Convertkit_Handler( $api_key ); return $handler->get_forms_and_tags(); } public function ajax_validate_api_token() { check_ajax_referer( self::OPTION_NAME_API_KEY, '_nonce' ); if ( ! isset( $_POST['api_key'] ) ) { wp_send_json_error(); } if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( 'Permission denied' ); } try { new Convertkit_Handler( $_POST['api_key'] ); // phpcs:ignore -- No need to sanitize to support special characters. } catch ( \Exception $exception ) { wp_send_json_error(); } wp_send_json_success(); } public function register_admin_fields( Settings $settings ) { $settings->add_section( Settings::TAB_INTEGRATIONS, 'convertkit', [ 'callback' => function() { echo '<hr><h2>' . esc_html__( 'ConvertKit', 'elementor-pro' ) . '</h2>'; }, 'fields' => [ self::OPTION_NAME_API_KEY => [ 'label' => esc_html__( 'API Key', 'elementor-pro' ), 'field_args' => [ 'type' => 'text', 'desc' => sprintf( /* translators: 1: Link opening tag, 2: Link closing tag. */ esc_html__( 'To integrate with our forms you need an %1$sAPI Key%2$s.', 'elementor-pro' ), '<a href="https://app.convertkit.com/account/edit" target="_blank">', '</a>' ), ], ], 'validate_api_data' => [ 'field_args' => [ 'type' => 'raw_html', 'html' => sprintf( '<button data-action="%s" data-nonce="%s" class="button elementor-button-spinner" id="elementor_pro_convertkit_api_key_button">%s</button>', self::OPTION_NAME_API_KEY . '_validate', wp_create_nonce( self::OPTION_NAME_API_KEY ), esc_html__( 'Validate API Key', 'elementor-pro' ) ), ], ], ], ] ); } public function __construct() { if ( is_admin() ) { add_action( 'elementor/admin/after_create_settings/' . Settings::PAGE_ID, [ $this, 'register_admin_fields' ], 15 ); } add_action( 'wp_ajax_' . self::OPTION_NAME_API_KEY . '_validate', [ $this, 'ajax_validate_api_token' ] ); } protected function get_fields_map_control_options() { return [ 'condition' => [ 'convertkit_form!' => '', ], ]; } } actions/cf7db.php 0000644 00000001311 14720522625 0007703 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Actions; use ElementorPro\Modules\Forms\Classes\Action_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class CF7DB extends Action_Base { public function get_name() { return 'cf7db'; } public function get_label() { return 'Contact Form to Database'; } public function register_settings_section( $widget ) {} public function on_export( $element ) {} public function run( $record, $ajax_handler ) { $data = (object) [ 'title' => $record->get_form_settings( 'form_name' ), 'posted_data' => $record->get_formatted_data( true ), ]; // Call hook to submit data do_action_ref_array( 'cfdb_submit', [ $data ] ); } } actions/webhook.php 0000644 00000006171 14720522625 0010365 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Actions; use Elementor\Controls_Manager; use ElementorPro\Modules\Forms\Classes\Action_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Webhook extends Action_Base { public function get_name() { return 'webhook'; } public function get_label() { return esc_html__( 'Webhook', 'elementor-pro' ); } public function register_settings_section( $widget ) { $widget->start_controls_section( 'section_webhook', [ 'label' => esc_html__( 'Webhook', 'elementor-pro' ), 'condition' => [ 'submit_actions' => $this->get_name(), ], ] ); $widget->add_control( 'webhooks', [ 'label' => esc_html__( 'Webhook URL', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'placeholder' => esc_html__( 'https://your-webhook-url.com', 'elementor-pro' ), 'ai' => [ 'active' => false, ], 'label_block' => true, 'separator' => 'before', 'description' => esc_html__( 'Enter the integration URL (like Zapier) that will receive the form\'s submitted data.', 'elementor-pro' ), 'render_type' => 'none', 'dynamic' => [ 'active' => true, ], ] ); $widget->add_control( 'webhooks_advanced_data', [ 'label' => esc_html__( 'Advanced Data', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'no', 'render_type' => 'none', ] ); $widget->end_controls_section(); } public function on_export( $element ) {} public function run( $record, $ajax_handler ) { $settings = $record->get( 'form_settings' ); if ( empty( $settings['webhooks'] ) ) { return; } if ( isset( $settings['webhooks_advanced_data'] ) && 'yes' === $settings['webhooks_advanced_data'] ) { $body['form'] = [ 'id' => $settings['id'], 'name' => $settings['form_name'], ]; $body['fields'] = $record->get( 'fields' ); $body['meta'] = $record->get( 'meta' ); } else { $body = $record->get_formatted_data( true ); $body['form_id'] = $settings['id']; $body['form_name'] = $settings['form_name']; } $args = [ 'body' => $body, ]; /** * Forms webhook request arguments. * * Filters the request arguments delivered by the form webhook when executing * an ajax request. * * @since 1.0.0 * * @param array $args Webhook request arguments. * @param Form_Record $record An instance of the form record. */ $args = apply_filters( 'elementor_pro/forms/webhooks/request_args', $args, $record ); $response = wp_remote_post( $settings['webhooks'], $args ); /** * Elementor form webhook response. * * Fires when the webhook response is retrieved by Elementor forms. This hook * allows developers to add functionality after recieving webhook responses. * * @since 1.0.0 * * @param \WP_Error|array $response The response or WP_Error on failure. * @param Form_Record $record An instance of the form record. */ do_action( 'elementor_pro/forms/webhooks/response', $response, $record ); if ( 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { throw new \Exception( 'Webhook error.' ); } } } actions/discord.php 0000644 00000013545 14720522625 0010361 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Actions; use Elementor\Controls_Manager; use ElementorPro\Core\Utils; use ElementorPro\Modules\Forms\Classes\Action_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Discord extends Action_Base { public function get_name() { return 'discord'; } public function get_label() { return esc_html__( 'Discord', 'elementor-pro' ); } public function register_settings_section( $widget ) { $widget->start_controls_section( 'section_discord', [ 'label' => esc_html__( 'Discord', 'elementor-pro' ), 'condition' => [ 'submit_actions' => $this->get_name(), ], ] ); $widget->add_control( 'discord_webhook', [ 'label' => esc_html__( 'Webhook URL', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'placeholder' => 'https://discordapp.com/api/webhooks/', 'ai' => [ 'active' => false, ], 'label_block' => true, 'separator' => 'before', 'description' => esc_html__( 'Enter the webhook URL that will receive the form\'s submitted data.', 'elementor-pro' ) . ' ' . sprintf( '<a href="%s" target="_blank">%s</a>.', 'https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks', esc_html__( 'Click here for Instructions', 'elementor-pro' ) ), 'render_type' => 'none', 'dynamic' => [ 'active' => true, ], ] ); $widget->add_control( 'discord_username', [ 'label' => esc_html__( 'Username', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'ai' => [ 'active' => false, ], 'dynamic' => [ 'active' => true, ], ] ); $widget->add_control( 'discord_avatar_url', [ 'label' => esc_html__( 'Avatar URL', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'ai' => [ 'active' => false, ], 'dynamic' => [ 'active' => true, ], ] ); $widget->add_control( 'discord_title', [ 'label' => esc_html__( 'Title', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'ai' => [ 'active' => false, ], 'dynamic' => [ 'active' => true, ], ] ); $widget->add_control( 'discord_content', [ 'label' => esc_html__( 'Description', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'ai' => [ 'active' => false, ], 'dynamic' => [ 'active' => true, ], ] ); $widget->add_control( 'discord_form_data', [ 'label' => esc_html__( 'Form Data', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes', ] ); $widget->add_control( 'discord_ts', [ 'label' => esc_html__( 'Timestamp', 'elementor-pro' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes', ] ); $widget->add_control( 'discord_webhook_color', [ 'label' => esc_html__( 'Color', 'elementor-pro' ), 'type' => Controls_Manager::COLOR, 'alpha' => false, 'default' => '#D30C5C', ] ); $widget->end_controls_section(); } public function on_export( $element ) { unset( $element['discord_avatar_url'], $element['discord_content'], $element['discord_webhook_color'], $element['discord_username'], $element['discord_form_data'], $element['discord_ts'], $element['discord_title'], $element['discord_webhook'] ); return $element; } public function run( $record, $ajax_handler ) { $settings = $record->get( 'form_settings' ); if ( empty( $settings['discord_webhook'] ) || false === strpos( $settings['discord_webhook'], 'https://discordapp.com/api/webhooks/' ) ) { return; } // PHPCS - The form is a visitor action and doesn't require a nonce. $referrer = Utils::_unstable_get_super_global_value( $_POST, 'referrer' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing $page_url = $referrer ? esc_url( $referrer ) : site_url(); $color = isset( $settings['discord_webhook_color'] ) ? hexdec( ltrim( $settings['discord_webhook_color'], '#' ) ) : hexdec( '9c0244' ); // Build discord webhook data $embeds = [ 'title' => isset( $settings['discord_title'] ) ? $settings['discord_title'] : esc_html__( 'A new Submission', 'elementor-pro' ), 'description' => isset( $settings['discord_content'] ) ? $settings['discord_content'] : esc_html__( 'A new Form Submission has been received', 'elementor-pro' ), 'author' => [ 'name' => isset( $settings['discord_username'] ) ? $settings['discord_username'] : esc_html__( 'Elementor Forms', 'elementor-pro' ), 'url' => $page_url, 'icon_url' => isset( $settings['discord_avatar_url'] ) ? $settings['discord_avatar_url'] : null, ], 'url' => $page_url, 'color' => $color, ]; if ( ! empty( $settings['discord_form_data'] ) && 'yes' === $settings['discord_form_data'] ) { // prepare Form Data $raw_fields = $record->get( 'fields' ); $fields = []; foreach ( $raw_fields as $id => $field ) { $fields[] = [ 'name' => $id, 'value' => $field['value'], 'inline' => false, ]; } $embeds['fields'] = array_values( $fields ); } if ( ! empty( $settings['discord_ts'] ) && 'yes' === $settings['discord_ts'] ) { $embeds['timestamp'] = gmdate( \DateTime::ISO8601 ); $embeds['footer'] = [ 'text' => sprintf( /* translators: %s: Elementor. */ esc_html__( 'Powered by %s', 'elementor-pro' ), 'Elementor' ), 'icon_url' => is_ssl() ? ELEMENTOR_ASSETS_URL . 'images/logo-icon.png' : null, ]; } $webhook_data = [ 'embeds' => array_values( [ $embeds ] ), ]; $webhook_data = apply_filters( 'elementor_pro/forms/discord/webhook_args', $webhook_data ); $response = wp_remote_post( $settings['discord_webhook'], [ 'body' => wp_json_encode( $webhook_data ), 'headers' => [ 'Content-Type' => 'application/json; charset=utf-8' ], ]); if ( 204 !== (int) wp_remote_retrieve_response_code( $response ) ) { throw new \Exception( 'Webhook error.' ); } } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.3-4ubuntu2.24 | Генерация страницы: 0.03 |
proxy
|
phpinfo
|
Настройка