File manager - Edit - /home/premiey/www/wp-includes/images/media/forms.tar
Back
actions/webhook.php 0000666 00000006171 15165314124 0010366 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/activecampaign.php 0000666 00000021742 15165314124 0011704 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' => 'Default', 'custom' => 'Custom', ], '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', ], ] ); $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', ], ] ); $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!' => '', ], ] ); $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(); } 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/discord.php 0000666 00000013521 15165314124 0010354 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'] ); } 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.' ); } } } actions/cf7db.php 0000666 00000001311 15165314124 0007704 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/email.php 0000666 00000026315 15165314124 0010021 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Actions; use Elementor\Controls_Manager; use ElementorPro\Core\Utils; use ElementorPro\Modules\Forms\Classes\Ajax_Handler; use ElementorPro\Modules\Forms\Classes\Action_Base; use ElementorPro\Modules\Forms\Classes\Form_Record; 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' ), ], ] ); $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 send 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'] ); $email_sent = wp_mail( $fields['email_to'], $fields['email_subject'], $fields['email_content'], $headers . $cc_header ); 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 ); } } /** * 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 ) { $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; } } actions/mailchimp.php 0000666 00000031254 15165314124 0010673 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' => 'Default', 'custom' => 'Custom', ], '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' ), ] ); $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!' => '', ], ] ); $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(); } 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/mailpoet.php 0000666 00000005646 15165314124 0010550 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/convertkit.php 0000666 00000016166 15165314124 0011125 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' => 'Default', 'custom' => 'Custom', ], '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', ], ] ); $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(); } 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/activity-log.php 0000666 00000001714 15165314124 0011341 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 0000666 00000017360 15165314124 0011061 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' => 'Default', 'custom' => 'Custom', ], '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' ), ] ); $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(); } 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/getresponse.php 0000666 00000021133 15165314124 0011261 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' => 'Default', 'custom' => 'Custom', ], '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', ], ] ); $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(); } 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/email2.php 0000666 00000001756 15165314124 0010105 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, ] ); $widget->update_control( $this->get_control_id( 'form_metadata' ), [ 'default' => [], ] ); } } actions/slack.php 0000666 00000014054 15165314124 0010024 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'] ); } 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 0000666 00000006770 15165314124 0010632 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/drip.php 0000666 00000021070 15165314124 0007661 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' => 'Default', 'custom' => 'Custom', ], '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' ), ] ); $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!' => '', ], ] ); $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(); } 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/redirect.php 0000666 00000003372 15165314124 0010531 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 ); if ( ! empty( $redirect_to ) && filter_var( $redirect_to, FILTER_VALIDATE_URL ) ) { $ajax_handler->add_response_data( 'redirect_url', $redirect_to ); } } } widgets/login.php 0000666 00000070502 15165314124 0010045 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' ]; } 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, ], '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, 'separator' => false, 'placeholder' => esc_html__( 'https://your-link.com', 'elementor-pro' ), '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, 'separator' => false, 'placeholder' => esc_html__( 'https://your-link.com', 'elementor-pro' ), '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, ], '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, ], ] ); $this->add_control( 'password_label', [ 'label' => esc_html__( 'Password Label', 'elementor-pro' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], '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, ], ] ); $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' => [ 'min' => 0, 'max' => 60, ], ], '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' => [ 'min' => 0, 'max' => 60, ], ], '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->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, ] ); $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, ], ] ); $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, ], ] ); $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', ], 'user_input' => [ 'type' => 'text', 'name' => 'log', 'id' => 'user', 'placeholder' => $settings['user_placeholder'], 'class' => [ 'elementor-field', 'elementor-field-textual', 'elementor-size-' . $settings['input_size'], ], ], 'password_label' => [ 'for' => 'password', ], 'password_input' => [ 'type' => 'password', 'name' => 'pwd', 'id' => 'password', 'placeholder' => $settings['password_placeholder'], 'class' => [ 'elementor-field', 'elementor-field-textual', 'elementor-size-' . $settings['input_size'], ], ], //TODO: add unique ID 'label_user' => [ 'for' => 'user', 'class' => 'elementor-field-label', ], 'label_password' => [ 'for' => 'password', 'class' => 'elementor-field-label', ], ] ); if ( ! $settings['show_labels'] ) { $this->add_render_attribute( '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' ); ?>> <?php if ( $settings['show_labels'] ) { echo '<label '; $this->print_render_attribute_string( 'user_label' ); echo '>'; $this->print_unescaped_setting( 'user_label' ); echo '</label>'; } ?> <input size="1" <?php $this->print_render_attribute_string( 'user_input' ); ?>> </div> <div <?php $this->print_render_attribute_string( 'field-group' ); ?>> <?php if ( $settings['show_labels'] ) : echo '<label '; $this->print_render_attribute_string( 'password_label' ); echo '>'; $this->print_unescaped_setting( 'password_label' ); echo '</label>'; endif; ?> <input size="1" <?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"> <# fieldGroupClasses = 'elementor-field-group elementor-column elementor-col-100 elementor-field-type-text'; #> <div class="{{ fieldGroupClasses }}"> <# if ( settings.show_labels ) { #> <label class="elementor-field-label" for="user" >{{{ settings.user_label }}}</label> <# } #> <input size="1" type="text" id="user" placeholder="{{ settings.user_placeholder }}" class="elementor-field elementor-field-textual elementor-size-{{ settings.input_size }}" /> </div> <div class="{{ fieldGroupClasses }}"> <# if ( settings.show_labels ) { #> <label class="elementor-field-label" for="password" >{{{ settings.password_label }}}</label> <# } #> <input size="1" type="password" id="password" placeholder="{{ settings.password_placeholder }}" class="elementor-field elementor-field-textual elementor-size-{{ settings.input_size }}" /> </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'; } } widgets/form.php 0000666 00000207644 15165314124 0007711 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 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, ], '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' => esc_html__( 'Please make sure the ID is unique and not used elsewhere in this form. This field allows `A-z 0-9` & underscore chars without spaces.', 'elementor-pro' ), 'render_type' => 'none', 'required' => true, 'dynamic' => [ 'active' => true, ], ] ); $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_responsive_control( 'button_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', ], ], 'default' => 'stretch', 'prefix_class' => 'elementor%s-button-align-', ] ); $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, ], '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, ], '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, ], ] ); $this->add_control( 'selected_button_icon', [ 'label' => esc_html__( 'Icon', 'elementor-pro' ), 'type' => Controls_Manager::ICONS, 'skin' => 'inline', 'label_block' => false, ] ); $this->add_control( 'button_icon_align', [ 'label' => esc_html__( 'Icon Position', 'elementor-pro' ), 'type' => Controls_Manager::SELECT, 'default' => 'left', 'options' => [ 'left' => esc_html__( 'Before', 'elementor-pro' ), 'right' => esc_html__( 'After', 'elementor-pro' ), ], 'condition' => [ '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' => 50, ], ], 'condition' => [ 'selected_button_icon[value]!' => '', ], 'selectors' => [ '{{WRAPPER}} .elementor-button .elementor-align-icon-right' => 'margin-left: {{SIZE}}{{UNIT}};', '{{WRAPPER}} .elementor-button .elementor-align-icon-left' => 'margin-right: {{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' => esc_html__( 'Please make sure the ID is unique and not used elsewhere on the page this form is displayed. This field allows `A-z 0-9` & underscore chars without spaces.', 'elementor-pro' ), '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' => esc_html__( 'Please make sure the ID is unique and not used elsewhere on the page this form is displayed. This field allows `A-z 0-9` & underscore chars without spaces.', 'elementor-pro' ), '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' => [ 'min' => 0, 'max' => 60, ], ], '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' => [ 'min' => 0, 'max' => 60, ], ], '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' => [ 'min' => 0, 'max' => 60, ], ], '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' => [ 'min' => 0, 'max' => 60, ], ], '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_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( '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' => [ 'min' => 0, 'max' => 100, ], ], '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' => [ 'min' => 0, 'max' => 100, ], ], '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' => [ 'min' => 0, 'max' => 100, ], ], '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' => [ 'min' => 0, 'max' => 100, ], ], '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' => [ 'min' => 0, 'max' => 100, ], '%' => [ 'min' => 0, 'max' => 100, ], ], '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' => [ 'min' => 0, 'max' => 100, ], ], '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' => [ 'min' => 0, 'max' => 100, ], ], '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', ], 'icon-align' => [ 'class' => [ empty( $instance['button_icon_align'] ) ? '' : 'elementor-align-icon-' . $instance['button_icon_align'], 'elementor-button-icon', ], ], ] ); 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 type="submit" <?php $this->print_render_attribute_string( 'button' ); ?>> <span <?php $this->print_render_attribute_string( 'content-wrapper' ); ?>> <?php if ( ! empty( $instance['button_icon'] ) || ! empty( $instance['selected_button_icon'] ) ) : ?> <span <?php $this->print_render_attribute_string( 'icon-align' ); ?>> <?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 class="elementor-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', { 'id': settings.form_id, 'name': settings.form_name, } ); if ( 'custom' === settings.form_validation ) { view.addRenderAttribute( 'form', 'novalidate' ); } #> <form class="elementor-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 ); 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 + ' ' + 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: 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> <# } } var buttonClasses = 'elementor-field-group elementor-column elementor-field-type-submit e-form__buttons'; buttonClasses += ' elementor-col-' + ( ( '' !== settings.button_width ) ? settings.button_width : '100' ); if ( settings.button_width_tablet ) { buttonClasses += ' elementor-md-' + settings.button_width_tablet; } if ( settings.button_width_mobile ) { buttonClasses += ' elementor-sm-' + settings.button_width_mobile; } var iconHTML = elementor.helpers.renderIcon( view, settings.selected_button_icon, { 'aria-hidden': true }, 'i' , 'object' ), migrated = elementor.helpers.isIconMigrated( settings, 'selected_button_icon' ); #> <div class="{{ buttonClasses }}"> <button id="{{ settings.button_css_id }}" type="submit" class="elementor-button elementor-size-{{ settings.button_size }} elementor-button-{{ settings.button_type }} elementor-animation-{{ settings.button_hover_animation }}"> <span> <# if ( settings.button_icon || settings.selected_button_icon ) { #> <span class="elementor-button-icon elementor-align-icon-{{ settings.button_icon_align }}"> <# 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 class="elementor-button-text">{{{ settings.button_text }}}</span> <# } #> </span> </button> </div> </div> </form> <?php } public function get_group_name() { return 'forms'; } } registrars/form-actions-registrar.php 0000666 00000004634 15165314124 0014060 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Registrars; use ElementorPro\Core\Utils\Registrar; use ElementorPro\Modules\Forms\Actions; use ElementorPro\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Basic form actions registration manager. */ class Form_Actions_Registrar extends Registrar { /** * 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 ) { $actions_registrar->register( new Actions\Email() ); $actions_registrar->register( new Actions\Email2() ); $actions_registrar->register( new Actions\Redirect() ); $actions_registrar->register( new Actions\Webhook() ); $actions_registrar->register( new Actions\Mailchimp() ); $actions_registrar->register( new Actions\Drip() ); $actions_registrar->register( new Actions\Activecampaign() ); $actions_registrar->register( new Actions\Getresponse() ); $actions_registrar->register( new Actions\Convertkit() ); $actions_registrar->register( new Actions\Mailerlite() ); $actions_registrar->register( new Actions\Slack() ); $actions_registrar->register( new Actions\Discord() ); } ); /** * Deprecated actions registration hook. * * @deprecated 3.5.0 */ 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 0000666 00000002325 15165314124 0013661 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 ); } } data/controller.php 0000666 00000000000 15165314124 0010345 0 ustar 00 submissions/export/csv-export.php 0000666 00000011273 15165314124 0013300 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; } } submissions/database/repositories/form-snapshot-repository.php 0000666 00000006756 15165314124 0021167 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 0000666 00000003474 15165314124 0016041 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/referer-extra.php 0000666 00000001146 15165314124 0016336 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/initial.php 0000666 00000007160 15165314124 0015216 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/database/migrations/fix-indexes.php 0000666 00000007441 15165314124 0016012 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/base-migration.php 0000666 00000002067 15165314124 0016467 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/migration.php 0000666 00000003105 15165314124 0013375 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 0000666 00000055330 15165314124 0012560 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/admin-menu-items/submissions-promotion-menu-item.php 0000666 00000002625 15165314124 0021317 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\AdminMenuItems; use Elementor\Modules\Promotions\AdminMenuItems\Base_Promotion_Item; use Elementor\Settings; use ElementorPro\License\API; use ElementorPro\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Submissions_Promotion_Menu_Item extends Base_Promotion_Item { public function get_label() { return esc_html__( 'Submissions', 'elementor-pro' ); } public function get_page_title() { return esc_html__( 'Submissions', 'elementor-pro' ); } public function get_cta_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; } public function get_cta_text() { return API::is_license_expired() ? esc_html__( 'Renew now', 'elementor-pro' ) : esc_html__( 'Connect & Activate', 'elementor-pro' ); } public function get_promotion_title() { return esc_html__( 'Collect Your Form Submissions', 'elementor-pro' ); } public function render_promotion_description() { echo esc_html__( 'Save and manage all of your form submissions in one single place. All within a simple, intuitive place.', 'elementor-pro' ); } } submissions/admin-menu-items/submissions-menu-item.php 0000666 00000001714 15165314124 0017271 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Submissions\AdminMenuItems; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page; use Elementor\Settings; 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() { ?> <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 } } submissions/data/responses/query-failed-response.php 0000666 00000001221 15165314124 0017012 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/data/controller.php 0000666 00000021120 15165314124 0012731 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/forms-controller.php 0000666 00000002211 15165314124 0014055 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/endpoints/forms-index.php 0000666 00000001414 15165314124 0015010 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/endpoints/referer.php 0000666 00000003203 15165314124 0014205 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 0000666 00000006453 15165314124 0014106 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/restore.php 0000666 00000005375 15165314124 0014252 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/index.php 0000666 00000006537 15165314124 0013677 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/actions/save-to-database.php 0000666 00000013234 15165314124 0014424 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::RAW_HTML, 'raw' => sprintf( __( 'Collected Submissions will be saved to Elementor > <a href="%s" target="_blank" rel="noreferrer">Submissions</a>', 'elementor-pro' ), self_admin_url( 'admin.php?page=' . Component::PAGE_ID ) ), 'content_classes' => 'elementor-panel-alert elementor-panel-alert-info', ] ); $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 = $record->get_form_meta( [ 'page_url', 'page_title', 'user_agent', 'remote_ip' ] ); $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' => $meta['remote_ip']['value'], '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/personal-data.php 0000666 00000007726 15165314124 0012407 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/component.php 0000666 00000013203 15165314124 0011642 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::is_license_expired(); } 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(); } ); } } } controls/fields-repeater.php 0000666 00000000500 15165314124 0012174 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; } } controls/fields-map.php 0000666 00000001550 15165314124 0011150 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, ], ], ] ); } } module.php 0000666 00000013437 15165314124 0006560 0 ustar 00 <?php namespace ElementorPro\Modules\Forms; use Elementor\Controls_Manager; use ElementorPro\Core\Utils; use ElementorPro\Modules\Forms\Data\Controller; use Elementor\Core\Experiments\Manager; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use ElementorPro\Base\Module_Base; use ElementorPro\Modules\Forms\Actions; use ElementorPro\Modules\Forms\Classes; 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; 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; public function get_name() { return 'forms'; } public function get_widgets() { return [ 'Form', 'Login', ]; } /** * @deprecated 3.1.0 */ public function localize_settings() { Plugin::elementor()->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0' ); return []; } 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()`. */ 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()`. */ 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()`. */ 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() { $experiments_manager = Plugin::elementor()->experiments; $name = Form_Submissions_Component::NAME; $experiments_manager->add_feature( [ 'name' => $name, 'title' => esc_html__( 'Form Submissions', 'elementor-pro' ), 'description' => esc_html__( 'Never lose another submission! Using “Actions After Submit” you can now choose to save all submissions to an internal database.', 'elementor-pro' ), 'release_status' => Manager::RELEASE_STATUS_STABLE, 'default' => Manager::STATE_ACTIVE, ] ); if ( ! $experiments_manager->is_feature_active( $name ) ) { return; } $this->add_component( $name, new Form_Submissions_Component() ); } /** * Module constructor. */ public function __construct() { parent::__construct(); 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() ); $this->register_submissions_component(); // 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' ) ) { $this->add_component( 'activity_log', new Actions\Activity_Log() ); } // Contact Form to Database if ( function_exists( 'CF7DBPlugin_init' ) ) { $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 ); } } } fields/upload.php 0000666 00000035516 15165314124 0010027 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 { private $fixed_files_indices = false; 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 = [ '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, '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 old 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', ]; /** * 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 = @ 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/acceptance.php 0000666 00000004554 15165314124 0010627 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 } } fields/time.php 0000666 00000005055 15165314124 0007474 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/step.php 0000666 00000007070 15165314124 0007510 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, ], '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, ], '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/date.php 0000666 00000006527 15165314124 0007460 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/field-base.php 0000666 00000005420 15165314124 0010525 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 ) { $form->add_script_depends( $script ); } foreach ( $this->depended_styles as $style ) { $form->add_style_depends( $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/tel.php 0000666 00000002275 15165314124 0007323 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 0000666 00000005537 15165314124 0010033 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 ); } } classes/getresponse-handler.php 0000666 00000006037 15165314124 0012677 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/mailerlite-handler.php 0000666 00000005074 15165314124 0012470 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/form-base.php 0000666 00000020251 15165314124 0010573 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 = $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'] ) . ' ' . $item['inline_list'] . '">'; foreach ( $options as $key => $option ) { $element_id = $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-' . $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/rest-client.php 0000666 00000010524 15165314124 0011153 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/recaptcha-handler.php 0000666 00000021435 15165314124 0012272 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/mailchimp-handler.php 0000666 00000010506 15165314124 0012300 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/convertkit-handler.php 0000666 00000005157 15165314124 0012533 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-v3-handler.php 0000666 00000011067 15165314124 0012620 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/ajax-handler.php 0000666 00000021744 15165314124 0011266 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/drip-handler.php 0000666 00000004017 15165314124 0011273 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 0000666 00000004700 15165314124 0012154 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 ) ) { $html = 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_url() . '#tab-integrations' ), '</a>' ); $content_classes = 'elementor-panel-alert elementor-panel-alert-warning'; } else { $html = 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_url() . '#tab-integrations' ), '</a>' ); $content_classes = 'elementor-panel-alert elementor-panel-alert-info'; } /* translators: %s: Integration label. */ $html .= ' ' . 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::RAW_HTML, 'raw' => $html, 'content_classes' => $content_classes, '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/action-base.php 0000666 00000001454 15165314124 0011111 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/activecampaign-handler.php 0000666 00000006105 15165314124 0013310 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-record.php 0000666 00000022674 15165314124 0011152 0 ustar 00 <?php namespace ElementorPro\Modules\Forms\Classes; use ElementorPro\Core\Utils; 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'] = isset( $form_field['file_sizes'] ) ? $form_field['file_sizes'] : ''; $field['file_types'] = isset( $form_field['file_types'] ) ? $form_field['file_types'] : ''; $field['max_files'] = isset( $form_field['max_files'] ) ? $form_field['max_files'] : ''; } 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' => [], ]; } $this->files[ $id ]['url'][ $index ] = $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(); } } classes/honeypot-handler.php 0000666 00000005354 15165314124 0012207 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( $field['id'], 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' ] ); } } views/modal-editor.php 0000666 00000162135 15165335234 0011015 0 ustar 00 <?php defined('ABSPATH') || exit; ?> <div class="attr-modal attr-fade" id="metform_form_modal" tabindex="-1" role="dialog" aria-labelledby="metform_form_modalLabel" style="display:none;"> <div class="attr-modal-dialog attr-modal-dialog-centered" id="metform-form-modalinput-form" role="document"> <form action="" method="post" id="metform-form-modalinput-settings" data-open-editor="0" data-editor-url="<?php echo esc_url(get_admin_url()); ?>" data-nonce="<?php echo esc_attr(wp_create_nonce('wp_rest')); ?>"> <input type="hidden" name="post_author" value="<?php echo esc_attr(get_current_user_id()); ?>"> <div class="attr-modal-content"> <div class="attr-modal-header"> <button type="button" class="attr-close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="attr-modal-title" id="metform_form_modalLabel"> <?php esc_html_e('Form Settings', 'metform'); ?></h4> <div id="message" style="display:none" class="attr-alert attr-alert-success mf-success-msg"></div> <ul class="attr-nav attr-nav-tabs" role="tablist"> <li role="presentation" class="attr-active"><a href="#mf-general" aria-controls="general" role="tab" data-toggle="tab"><?php esc_html_e('General', 'metform'); ?></a> </li> <li role="presentation"><a href="#mf-confirmation" aria-controls="confirmation" role="tab" data-toggle="tab"><?php esc_html_e('Confirmation', 'metform'); ?></a> </li> <li role="presentation"><a href="#mf-notification" aria-controls="notification" role="tab" data-toggle="tab"><?php esc_html_e('Notification', 'metform'); ?></a> </li> <li role="presentation"><a href="#mf-integration" aria-controls="integration" role="tab" data-toggle="tab"><?php esc_html_e('Integration', 'metform'); ?></a> </li> <?php if (class_exists('MetForm_Pro\Base\Package')) : ?> <li role="presentation"><a href="#mf-payment" aria-controls="payment" role="tab" data-toggle="tab"><?php esc_html_e('Payment', 'metform'); ?></a> </li> <li role="presentation"><a href="#mf-crm" aria-controls="crm" role="tab" data-toggle="tab"><?php esc_html_e('CRM', 'metform'); ?></a></li> <?php endif; ?> <?php do_action('mf_form_settings_tab'); ?> </ul> </div> <div class="attr-tab-content"> <div role="tabpanel" class="attr-tab-pane attr-active" id="mf-general"> <div class="attr-modal-body" id="metform_form_modal_body"> <div class="mf-input-group"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('Title:', 'metform'); ?></label> <input type="text" name="form_title" class="mf-form-modalinput-title attr-form-control" data-default-value="<?php echo esc_html__('New Form # ', 'metform') . esc_attr(time()); ?>"> <span class='mf-input-help'><?php esc_html_e('This is the form title', 'metform'); ?></span> </div> <div class="mf-input-group"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('Success Message:', 'metform'); ?></label> <input type="text" name="success_message" class="mf-form-modalinput-success_message attr-form-control" data-default-value="<?php esc_html_e('Thank you! Form submitted successfully.', 'metform'); ?>"> <span class='mf-input-help'><?php esc_html_e('This message will be shown after a successful submission.', 'metform'); ?></span> </div> <?php if (class_exists('\MetForm_Pro\Core\Features\Quiz\Integration')) : ?> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="quiz_summery" class="mf-admin-control-input mf-form-modalinput-quiz_result_show"> <span><?php esc_html_e('Show Quiz Summary:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Quiz summary will be shown to user after form submission with success message.', 'metform'); ?></span> </div> <?php endif; ?> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="require_login" class="mf-admin-control-input mf-form-modalinput-require_login"> <span><?php esc_html_e('Required Login:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Without login, users can\'t submit the form.', 'metform'); ?></span> </div> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="capture_user_browser_data" class="mf-admin-control-input mf-form-modalinput-capture_user_browser_data"> <span><?php esc_html_e('Capture User Browser Data:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Store user\'s browser data (ip, browser name, etc)', 'metform'); ?></span> </div> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="hide_form_after_submission" class="mf-admin-control-input mf-form-modalinput-hide_form_after_submission"> <span><?php esc_html_e('Hide Form After Submission:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('After submission, hide the form for preventing multiple submission.', 'metform'); ?></span> </div> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="store_entries" class="mf-admin-control-input mf-form-modalinput-store_entries"> <span><?php esc_html_e('Store Entries:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Save submitted form data to database.', 'metform'); ?></span> </div> <div class="mf-input-group mf-entry-title"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('Entry Title', 'metform'); ?></label> <input type="text" name="entry_title" class="mf-entry-title-input attr-form-control" placeholder="Entry Title"> <span class="mf-input-help"><?php esc_html_e('Enter here title of this form entries.', 'metform'); ?></span> </div> <div class="mf-input-group"> <div class="mf-input-group-inner"> <label class="attr-input-label"> <input type="checkbox" value="1" name="limit_total_entries_status" class="mf-admin-control-input mf-form-modalinput-limit_status"> <span><?php esc_html_e('Limit Total Entries:', 'metform'); ?></span> </label> <div class="mf-input-group" id='limit_status'> <input type="number" min="1" name="limit_total_entries" class="mf-form-modalinput-limit_total_entries attr-form-control"> </div> </div> <span class='mf-input-help'><?php esc_html_e('Limit the total number of submissions for this form.', 'metform'); ?></span> </div> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="count_views" class="mf-admin-control-input mf-form-modalinput-count_views"> <span><?php esc_html_e('Count views:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Track form views.', 'metform'); ?></span> </div> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="mf_stop_vertical_scrolling" class="mf-admin-control-input mf-form-modalinput-stop_vertical_scrolling"> <span><?php esc_html_e('Stop Vertical Scrolling:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Stop scrolling effect when submitting the form.', 'metform'); ?></span> </div> <div class="mf-input-group"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('Redirect To:', 'metform'); ?></label> <input type="text" name="redirect_to" class="mf-form-modalinput-redirect_to attr-form-control" placeholder="<?php esc_html_e('Redirection link', 'metform'); ?>"> <span class='mf-input-help'><?php esc_html_e('Users will be redirected to the this link after submission.', 'metform'); ?></span> </div> <?php do_action('mf_add_url_databypass_input'); ?> </div> </div> <div role="tabpanel" class="attr-tab-pane" id="mf-confirmation"> <div class="attr-modal-body" id="metform_form_modal_body"> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="enable_user_notification" class="mf-admin-control-input mf-form-user-enable"> <span><?php esc_html_e('Confirmation mail to user :', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Want to send a submission copy to user by email? Active this one.', 'metform'); ?><strong><?php esc_html_e('The form must have at least one Email widget and it should be required.', 'metform'); ?></strong></span> </div> <div class="mf-input-group mf-form-user-confirmation"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('Email Subject:', 'metform'); ?></label> <input type="text" name="user_email_subject" class="mf-form-user-email-subject attr-form-control" placeholder="<?php esc_html_e('Email subject', 'metform'); ?>"> <span class='mf-input-help'><?php esc_html_e('Enter here email subject.', 'metform'); ?></span> </div> <div class="mf-input-group mf-form-user-confirmation"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('Email From:', 'metform'); ?></label> <input type="email" name="user_email_from" class="mf-form-user-email-from attr-form-control" placeholder="<?php esc_html_e('From email', 'metform'); ?>"> <span class='mf-input-help'><?php esc_html_e('Enter the email by which you want to send email to user.', 'metform'); ?></span> </div> <div class="mf-input-group mf-form-user-confirmation"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('Email Reply To:', 'metform'); ?></label> <input type="email" name="user_email_reply_to" class="mf-form-user-reply-to attr-form-control" placeholder="<?php esc_html_e('Reply to email', 'metform'); ?>"> <span class='mf-input-help'><?php esc_html_e('Enter email where user can reply/ you want to get reply.', 'metform'); ?></span> </div> <div class="mf-input-group mf-form-user-confirmation"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('Thank you message :', 'metform'); ?></label> <textarea name="user_email_body" id="" class="mf-form-user-email-body attr-form-control" cols="30" rows="5" placeholder="<?php esc_html_e('Thank you message!', 'metform'); ?>"></textarea> <span class='mf-input-help'><?php esc_html_e('Enter here your message to include it in email body. Which will be send to user.', 'metform'); ?></span> </div> <div class="mf-input-group mf-form-user-confirmation"> <label class="attr-input-label"> <input type="checkbox" value="1" name="user_email_attach_submission_copy" class="mf-admin-control-input mf-form-user-submission-copy"> <span><?php esc_html_e('Want to send a copy of submitted form to user ?', 'metform'); ?></span> </label> </div> <?php do_action('get_metform_email_verification_settings') ?> </div> </div> <div role="tabpanel" class="attr-tab-pane" id="mf-notification"> <div class="attr-modal-body" id="metform_form_modal_body"> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="enable_admin_notification" class="mf-admin-control-input mf-form-admin-enable"> <span><?php esc_html_e('Notification mail to admin :', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Want to send a submission copy to admin by email? Active this one.', 'metform'); ?></span> </div> <div class="mf-input-group mf-form-admin-notification"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('Email Subject:', 'metform'); ?></label> <input type="text" name="admin_email_subject" class="mf-form-admin-email-subject attr-form-control" placeholder="<?php esc_html_e('Email subject', 'metform'); ?>"> <span class='mf-input-help'><?php esc_html_e('Enter here email subject.', 'metform'); ?></span> </div> <div class="mf-input-group mf-form-admin-notification"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('Email To:', 'metform'); ?></label> <input type="text" name="admin_email_to" class="mf-form-admin-email-to attr-form-control" placeholder="<?php esc_html_e('example@mail.com, example@email.com', 'metform'); ?>"> <span class='mf-input-help'><?php esc_html_e('Enter admin email where you want to send mail.', 'metform'); ?><strong><?php esc_html_e(' for multiple email addresses please use "," separator.', 'metform'); ?></strong></span> </div> <div class="mf-input-group mf-form-admin-notification"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('Email From:', 'metform'); ?></label> <input type="text" name="admin_email_from" class="mf-form-admin-email-from attr-form-control" placeholder="<?php esc_html_e('Email from', 'metform'); ?>"> <span class='mf-input-help'><?php esc_html_e('Enter the email by which you want to send email to admin.', 'metform'); ?></span> </div> <div class="mf-input-group mf-form-admin-notification"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('Email Reply To:', 'metform'); ?></label> <input type="text" name="admin_email_reply_to" class="mf-form-admin-reply-to attr-form-control" placeholder="<?php esc_html_e('Email reply to', 'metform'); ?>"> <span class='mf-input-help'><?php esc_html_e('Enter email where admin can reply/ you want to get reply.', 'metform'); ?></span> </div> <div class="mf-input-group mf-form-admin-notification"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('Admin Note : ', 'metform'); ?></label> <textarea name="admin_email_body" class="mf-form-admin-email-body attr-form-control" cols="30" rows="5" placeholder="<?php esc_html_e('Admin note!', 'metform'); ?>"></textarea> <span class='mf-input-help'><?php esc_html_e('Enter here your email body. Which will be send to admin.', 'metform'); ?></span> </div> </div> </div> <div role="tabpanel" class="attr-tab-pane" id="mf-integration"> <div class="attr-modal-body" id="metform_form_modal_body"> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="mf_hubspot_forms" class="mf-admin-control-input mf-hubspot-forms"> <span><?php esc_html_e('HubSpot Forms:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Integrate hubspot with this form. ', 'metform'); ?><a target="_blank" href="<?php echo esc_url(get_dashboard_url()) . 'admin.php?page=metform-menu-settings#mf_crm'; ?>"><?php esc_html_e('Configure HubSpot.', 'metform'); ?></a></span> <div class="hubspot_forms_section"> <label class="attr-input-label"> <span><?php esc_html_e('Fetch HubSpot Forms', 'metform'); ?><span class="dashicons dashicons-update metfrom-btn-refresh-hubsopt-list"></span></span> </label> <select name='hubspot_forms' class="attr-form-control hubspot_forms"> </select> <input type="hidden" class="mf_hubspot_form_guid" name="mf_hubspot_form_guid"> <input type="hidden" class="mf_hubspot_form_portalId" name="mf_hubspot_form_portalId"> <div id="mf-hubsopt-fileds"></div> </div> </div> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="mf_hubspot" class="mf-admin-control-input mf-hubsopt"> <span><?php esc_html_e('HubSpot Contact:', 'metform'); ?></span> </label> </div> <?php if (class_exists('MetForm_Pro\Core\Integrations\Rest_Api')) : ?> <div class="mf-input-group mf-input-group-inner"> <label class="attr-input-label"> <input type="checkbox" value="1" name="mf_rest_api" class="mf-admin-control-input mf-form-modalinput-rest_api"> <span><?php esc_html_e('REST API:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Send entry data to third party api/webhook', 'metform'); ?></span> </div> <div class="mf-input-group mf-input-rest-api-group"> <div class="mf-rest-api"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('URL/Webhook:', 'metform'); ?></label> <input type="text" name="mf_rest_api_url" class="mf-rest-api-url attr-form-control" placeholder="<?php esc_html_e('Rest api url/webhook', 'metform'); ?>"> <span class='mf-input-help'><?php esc_html_e('Enter rest api url/webhook here.', 'metform'); ?></span> </div> <div class="mf-rest-api-key"> <div id='rest_api_method'> <select name="mf_rest_api_method" class="mf-rest-api-method attr-form-control"> <option value="POST"><?php esc_html_e('POST', 'metform'); ?></option> <option value="GET"><?php esc_html_e('GET', 'metform'); ?></option> </select> </div> </div> </div> <?php endif ?> <?php if (class_exists('\MetForm\Core\Integrations\Mail_Chimp')) : ?> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="mf_mail_chimp" class="mf-admin-control-input mf-form-modalinput-mail_chimp"> <span><?php esc_html_e('Mail Chimp:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Integrate mailchimp with this form.', 'metform'); ?><strong><?php esc_html_e('The form must have at least one Email widget and it should be required. ', 'metform'); ?><a target="_blank" href="<?php echo esc_url(get_dashboard_url()) . 'admin.php?page=metform-menu-settings#mf-newsletter_integration'; ?>"><?php esc_html_e('Configure Mail Chimp.', 'metform'); ?></a></strong></span> </div> <div class="mf-input-group mf-mailchimp"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('MailChimp List ID:', 'metform'); ?> <span class="dashicons dashicons-update metfrom-btn-refresh-mailchimp-list"></span></label> <select class="attr-form-control mailchimp_list"> </select> <input type="hidden" name="mf_mailchimp_list_id" class="mf-mailchimp-list-id attr-form-control" placeholder="<?php esc_html_e('Mailchimp contact list id', 'metform'); ?>"> </div> <?php endif ?> <?php if (class_exists('\MetForm_Pro\Core\Integrations\Google_Sheet\WF_Google_Sheet')) : ?> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="mf_google_sheet" class="mf-admin-control-input mf-form-modal_input-google_sheet"> <span><?php esc_html_e('Google Sheet:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Integrate google sheet with this form.', 'metform'); ?><strong><a target="_blank" href="<?php echo esc_url(get_dashboard_url()) . 'admin.php?page=metform-menu-settings#mf-google_sheet_integration'; ?>"><?php esc_html_e('Configure Google Sheet.', 'metform'); ?></a></strong></span> </div> <div class="mf-google-spreadsheets-selection-div"> <div class="mf-input-group mf-google-spreadsheets-selection"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('Spreadsheets List:', 'metform'); ?> <span class="dashicons dashicons-update metfrom-btn-refresh-google-spreadsheets-list"></span></label> <select class="attr-form-control mf-google-spreadsheets-list"> </select> <input type="hidden" name="mf_google_spreadsheets_list_id" class="mf-google-spreadsheets-list-id attr-form-control" placeholder="<?php esc_html_e('Google Spreadsheet list id', 'metform'); ?>"> </div> <div class="mf-input-group mf-google-sheets-selection"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('Sheets List:', 'metform'); ?> <span class="dashicons dashicons-update metfrom-btn-refresh-google-sheets-list"></span></label> <select class="attr-form-control mf-google-sheets-list"> </select> <input type="hidden" name="mf_google_sheets_list_id" class="mf-google-sheets-list-id attr-form-control" placeholder="<?php esc_html_e('Google Sheet list title', 'metform'); ?>"> </div> </div> <?php endif; ?> <?php if (did_action('xpd_metform_pro/plugin_loaded')) : if (class_exists('\MetForm_Pro\Core\Integrations\Mail_Poet')) : ?> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="mf_mail_poet" class="mf-admin-control-input mf-form-modalinput-mail_poet"> <span><?php esc_html_e('MailPoet:', 'metform'); ?></span> </label> <span class='mf-input-help'> <?php esc_html_e('Integrate MailPoet with this form.', 'metform'); ?> <strong><?php esc_html_e('The form must have at least one Email widget and it should be required. ', 'metform'); ?> <a target="_blank" href="<?php echo esc_url(get_dashboard_url()) . 'admin.php?page=metform-menu-settings#mf-newsletter_integration'; ?>"> <?php esc_html_e('Configure MailPoet.', 'metform'); ?> </a> </strong> </span> </div> <div class="mf-input-group mf-mail-poet"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('MailPoet List ID:', 'metform'); ?></label> <select name="mf_mail_poet_list_id" class="mf-mail-poet-list-id attr-form-control"> <option value=""> None</option> </select> <span class='mf-input-help'><?php esc_html_e('Enter here MailPoet list id. ', 'metform'); ?> <a id="met_form_mail_poet_get_list" href="#"><?php esc_html_e('Refresh List', 'metform'); ?></a> <span id="mf_mail_poet_info"></span> </span> </div> <?php endif; ?> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="mf_mail_aweber" class="mf-admin-control-input mf-form-modalinput-mail_aweber"> <span><?php esc_html_e('Aweber:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Integrate aweber with this form.', 'metform'); ?><strong><?php esc_html_e('The form must have at least one Email widget and it should be required. ', 'metform'); ?><a target="_blank" href="<?php echo esc_url(get_dashboard_url()) . 'admin.php?page=metform-menu-settings#mf-newsletter_integration'; ?>"><?php esc_html_e('Configure aweber.', 'metform'); ?></a></strong></span> </div> <div class="mf-input-group mf-aweber"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('Aweber List ID:', 'metform'); ?></label> <select name="mf_aweber_list_id" class="mf-aweber-list-id attr-form-control"> <option class="mf_aweber_default_option" value=""> None</option> </select> <span class='mf-input-help'><?php esc_html_e('Enter here aweber list id. ', 'metform'); ?> <a id="met_form_aweber_get_list" href="#"><?php esc_html_e('Refresh List', 'metform'); ?></a> <span id="mf_aweber_info"></span> </span> <div id="mf-aweber-fields"></div> </div> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="mf_convert_kit" class="mf-admin-control-input mf-form-modalinput-ckit" /> <span><?php esc_html_e('ConvertKit:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Integrate convertKit with this form.', 'metform'); ?><strong><?php esc_html_e('The form must have at least one Email widget and it should be required. ', 'metform'); ?><a target="_blank" href="<?php echo esc_url(get_dashboard_url()) . 'admin.php?page=metform-menu-settings#mf-newsletter_integration'; ?>"><?php esc_html_e('Configure ConvertKit.', 'metform'); ?></a></strong></span> </div> <div class="mf-input-group mf-ckit"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('ConvertKit Forms ID:', 'metform'); ?></label> <select name="mf_ckit_list_id" class="attr-form-control mf-ckit-list-id"> <option value=""> None</option> </select> <span class='mf-input-help'><?php esc_html_e('Enter here ConvertKit form id. ', 'metform'); ?> <a id="met_form_ckit_get_list" href="#"> <?php esc_html_e('Refresh List', 'metform'); ?> </a> </span> </div> <?php endif ?> <?php do_action('get_automixy_settings_content'); ?> <?php if (class_exists('\MetForm_Pro\Core\Integrations\Email\Getresponse\Get_Response')) : ?> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="mf_get_response" class="mf-admin-control-input mf-form-modalinput-get_response"> <span><?php esc_html_e('GetResponse:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Integrate GetResponse with this form.', 'metform'); ?><strong><?php esc_html_e('The form must have at least one Email widget and it should be required. ', 'metform'); ?><a target="_blank" href="<?php echo esc_url(get_dashboard_url()) . 'admin.php?page=metform-menu-settings#mf-newsletter_integration'; ?>"><?php esc_html_e('Configure GetResponse.', 'metform'); ?></a></strong></span> </div> <div class="mf-input-group mf-get_response"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('GetResponse List ID:', 'metform'); ?> <span class="dashicons dashicons-update metfrom-btn-refresh-get-response-list"></span></label> <select class="attr-form-control get-response-campaign-list"> </select> <input type="hidden" name="mf_get_response_list_id" class="mf-get_response-list-id attr-form-control" placeholder="<?php esc_html_e('GetResponse contact list id', 'metform'); ?>"> <span class='mf-input-help'><?php esc_html_e('Enter here GetResponse list id. ', 'metform'); ?></span> </div> <?php endif; ?> <?php if (class_exists('\MetForm_Pro\Core\Integrations\Email\Activecampaign\Active_Campaign')) : ?> <?php $cached_email_list = get_option(\MetForm_Pro\Core\Integrations\Email\Activecampaign\Active_Campaign::CK_ACT_CAMP_EMAIL_LIST_CACHE_KEY, []); $cached_tag_list = get_option(\MetForm_Pro\Core\Integrations\Email\Activecampaign\Active_Campaign::CK_ACT_CAMP_TAG_LIST_CACHE_KEY, []); ?> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="mf_active_campaign" class="mf-admin-control-input mf-active-campaign"> <span><?php esc_html_e('ActiveCampaign:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Integrate ActiveCampaign with this form.', 'metform'); ?><strong><?php esc_html_e('The form must have at least one Email widget and it should be required. ', 'metform'); ?><a target="_blank" href="<?php echo esc_url(get_dashboard_url()) . 'admin.php?page=metform-menu-settings#mf-newsletter_integration'; ?>"><?php esc_html_e('Configure ActiveCampaign.', 'metform'); ?></a></strong></span> </div> <div class="mf-input-group mf-active-campaign"> <label for="attr-input-label" class="attr-input-label"> <?php esc_html_e('Active campaign List ID:', 'metform'); ?> </label> <select name="mf_active_campaign_list_id" class="mf-active-camp-list-id attr-form-control"> <?php if (!empty($cached_email_list)) { foreach ($cached_email_list as $item) { echo '<option value="' . esc_html($item['sid']) . '">' . esc_html($item['name']) . '</option>'; } } ?> </select> <span class='mf-input-help'><?php esc_html_e('Enter here list id. ', 'metform'); ?> <a id="met_form_act_camp_get_list" href="#"><?php esc_html_e('Refresh List', 'metform'); ?></a> <span id="mf_act_camp_info"> </span> </span> </div> <div class="mf-input-group mf-active-campaign"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('Active campaign Tag ID:', 'metform'); ?></label> <select name="mf_active_campaign_tag_id" class="mf-active-camp-list-id attr-form-control"> <option value=""> None </option> <?php if (!empty($cached_tag_list)) { foreach ($cached_tag_list as $item) { echo '<option value="' . esc_html($item['sid']) . '">' . esc_html($item['name']) . '</option>'; } } ?> </select> <span class='mf-input-help'><?php esc_html_e('Enter here tag id. ', 'metform'); ?> <a id="met_form_act_camp_get_tags" href="#"><?php esc_html_e('Refresh List', 'metform'); ?></a> <span id="mf_act_camp_tag_info"> </span> </span> </div> <?php endif ?> <?php if (function_exists('mailster')) { if (class_exists('\MetForm_Pro\Core\Integrations\Email\Mailster\Mailster')) : ?> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="mf_mailster" class="mf-admin-control-input mf-form-modalinput-mailster"> <span><?php esc_html_e('Mailster:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Integrate Mailster with this form.', 'metform'); ?><strong><?php esc_html_e('The form must have at least one Email widget and it should be required. ', 'metform'); ?></strong></span> </div> <div class="mf-input-group mf-mailster-forms"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('Mailster Forms', 'metform'); ?></label> <select name="mf_mailster_list_id" class="mf-mailster-list-id attr-form-control"> <?php $forms = mailster('forms')->get(); foreach ($forms as $form) : ?> <option value="<?php echo esc_attr($form->ID); ?>"><?php echo esc_html($form->name); ?></option> <?php endforeach; ?> </select> </div> <div class="mf-input-group mf-mailster-settings-section"> </div> <?php endif; } ?> <?php if (class_exists('\MetForm_Pro\Core\Integrations\Zapier')) : ?> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="mf_zapier" class="mf-admin-control-input mf-form-modalinput-zapier"> <span><?php esc_html_e('Zapier:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Integrate zapier with this form.', 'metform'); ?><strong><?php esc_html_e('The form must have at least one Email widget and it should be required.', 'metform'); ?></strong></span> </div> <div class="mf-input-group mf-zapier"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('Zapier webhook:', 'metform'); ?></label> <input type="text" name="mf_zapier_webhook" class="mf-zapier-web-hook attr-form-control" placeholder="<?php esc_html_e('Zapier webhook', 'metform'); ?>"> <span class='mf-input-help'><?php esc_html_e('Enter here zapier web hook.', 'metform'); ?></span> </div> <?php endif ?> <?php if (class_exists('\MetForm\Core\Integrations\Slack')) : ?> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="mf_slack" class="mf-admin-control-input mf-form-modalinput-slack"> <span><?php esc_html_e('Slack:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Integrate slack with this form.', 'metform'); ?><strong><?php esc_html_e('slack info.', 'metform'); ?></strong></span> </div> <div class="mf-input-group mf-slack"> <label for="attr-input-label" class="attr-input-label"><?php esc_html_e('Slack webhook:', 'metform'); ?></label> <input type="text" name="mf_slack_webhook" class="mf-slack-web-hook attr-form-control" placeholder="<?php esc_html_e('Slack webhook', 'metform'); ?>"> <span class='mf-input-help'><?php esc_html_e('Enter here slack web hook.', 'metform'); ?><a href="http://slack.com/apps/A0F7XDUAZ-incoming-webhooks"><?php esc_html_e('create from here', 'metform'); ?></a></span> </div> <?php endif ?> <?php do_action('metform_sms_integration_editor_markup') ?> </div> </div> <?php if (class_exists('MetForm_Pro\Base\Package')) : ?> <div role="tabpanel" class="attr-tab-pane" id="mf-payment"> <div class="attr-modal-body" id="metform_form_modal_body"> <?php $currencies = [ 'AUD' => 'Australian dollar', 'BRL' => 'Brazilian real', 'CAD' => 'Canadian dollar', 'CNY' => 'Chinese Renmenbi', 'CZK' => 'Czech koruna', 'DKK' => 'Danish krone', 'EUR' => 'Euro', 'HKD' => 'Hong Kong dollar', 'HUF' => 'Hungarian forint', 'ILS' => 'Israeli new shekel', 'JPY' => 'Japanese yen', 'MYR' => 'Malaysian ringgit', 'MXN' => 'Mexican peso', 'TWD' => 'New Taiwan dollar', 'NZD' => 'New Zealand dollar', 'NOK' => 'Norwegian krone', 'PHP' => 'Philippine peso', 'PLN' => 'Polish złoty', 'GBP' => 'Pound sterling', 'RUB' => 'Russian ruble', 'SGD' => 'Singapore dollar', 'SEK' => 'Swedish krona', 'CHF' => 'Swiss franc', 'THB' => 'Thai baht', 'USD' => 'United States dollar', ] ?> <div class="mf-input-group"> <label class="attr-input-label"> Default currency </label> <select name="mf_payment_currency" id="" class="mf_payment_currency attr-form-control"> <?php foreach ($currencies as $key => $value) { ?> <option value="<?php echo esc_attr($key); ?>" <?php echo esc_attr($key == 'USD' ? 'selected' : ''); ?>><?php echo esc_html($value) . ' (' . esc_html($key) . ')' ?></option> <?php } ?> </select> </div> <?php if (class_exists('\MetForm_Pro\Core\Integrations\Payment\Paypal')) : ?> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="mf_paypal" class="mf-admin-control-input mf-form-modalinput-paypal"> <span><?php esc_html_e('Paypal:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Integrate paypal payment with this form.', 'metform'); ?><a target="_blank" href="<?php echo esc_url(get_dashboard_url()) . 'admin.php?page=metform-menu-settings#mf-payment_options'; ?>"><?php esc_html_e('Configure paypal payment.', 'metform'); ?></a></span> </div> <?php endif ?> <?php if (class_exists('\MetForm_Pro\Core\Integrations\Payment\Stripe')) : ?> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="mf_stripe" class="mf-admin-control-input mf-form-modalinput-stripe"> <span><?php esc_html_e('Stripe:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Integrate stripe payment with this form. ', 'metform'); ?><a target="_blank" href="<?php echo esc_url(get_dashboard_url()) . 'admin.php?page=metform-menu-settings#mf-payment_options'; ?>"><?php esc_html_e('Configure stripe payment.', 'metform'); ?></a></span> </div> <?php endif ?> </div> </div> <?php endif; ?> <div role="tabpanel" class="attr-tab-pane" id="mf-crm"> <div class="attr-modal-body" id="metform_form_modal_body"> <?php if (class_exists('\MetForm_Pro\Core\Integrations\Crm\Zoho\Integration')) : ?> <!-- Zoho integration --> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="mf_zoho" class="mf-admin-control-input mf-zoho"> <span><?php esc_html_e('Zoho Contact:', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Integrate Zoho contacts with this form. ', 'metform'); ?><a target="_blank" href="<?php echo esc_url(get_dashboard_url()) . 'admin.php?page=metform-menu-settings#mf_crm'; ?>"><?php esc_html_e('Configure Zoho.', 'metform'); ?></a></span> <div style="display: none;" class="mf_zoho_forms_section"> <div class="mf-input-group"><label class="attr-input-label"><?php esc_html_e('Form Fields', 'metform') ?></label> <p style="display:none" class="mf-zoho-error-msg"></p> <div class="mf-inputs mf-cf-fields"> <div id="mf-zoho-all-form-fields"> <div id="mf-zoho-single-field" class="mf-cf-single-field mf-zoho-crm-single-field"> <div class="mf-cf-single-field-input"> <label><?php esc_html_e('Select Metform Field', 'metform') ?></label> <select name="mf-zoho-custom-fields[]" class="mf-zoho-custom-fields attr-form-control "> </select> </div> <div class="mf-cf-single-field-input"> <label><?php esc_html_e('Select Zoho Form Field', 'metform') ?></label> <select name="mf-zoho-form-fields[]" class="attr-form-control mf-zoho-form-fields mf-zoho-form-fields-url-copier" > </select> </div> <a href="#" class="mf-btn-del-single-field mf-btn-del-single-field-zoho"><?php esc_html_e('Delete', 'metform') ?></a> </div> </div> <button class="mf-add-zoho_fields mf-add-cf" type="button"><span>+</span></button> </div> </div> </div> </div> <?php endif; ?> <!-- Helpscout integration --> <?php if (class_exists('\MetForm_Pro\Core\Integrations\Crm\Helpscout\Helpscout')) : ?> <div class="mf-input-group"> <label class="attr-input-label"> <input type="checkbox" value="1" name="mf_helpscout" class="mf-admin-control-input mf-helpscout"> <span><?php esc_html_e('Helpscout', 'metform'); ?></span> </label> <span class='mf-input-help'><?php esc_html_e('Integrate Helpscout with this form. ', 'metform'); ?><a target="_blank" href="<?php echo esc_url(get_dashboard_url()) . 'admin.php?page=metform-menu-settings#mf_crm'; ?>"><?php esc_html_e('Configure Helpscout.', 'metform'); ?></a></span> <div style="display: none;" class="helpscout_forms_section"> <label class="attr-input-label"> <span><?php esc_html_e('Available Mailboxes', 'metform'); ?></span> </label> <?php if (get_option('mf_helpscout_mailboxes') && is_array(get_option('mf_helpscout_mailboxes'))) : ?> <select id="mf_helpscout_mailbox" name='mf_helpscout_mailbox' class="attr-form-control helpscout_mailboxes"> <?php foreach (get_option('mf_helpscout_mailboxes') as $mailbox) : ?> <option value="<?php echo esc_html($mailbox['id'], 'metform') ?>"><?php echo esc_html($mailbox['name'], 'metform') ?></option> <?php endforeach; ?> </select> <?php else : ?> <span>No mailbox found</span> <?php endif; ?> <br><br> <div id="mf-helpscout-fileds"></div> </div> </div> <?php endif; ?> <?php do_action('metform_fluent_crm_editor_markup') ?> </div> </div> <?php do_action('mf_form_settings_tab_content'); ?> </div> <div class="attr-modal-footer"> <button type="button" class="attr-btn attr-btn-default metform-form-save-btn-editor"><img class="form-editor-icon" src="<?php echo esc_url(\MetForm\Plugin::instance()->public_url()) . 'assets/img/elementor-edit-logo.png'; ?>"><?php esc_html_e('Edit content', 'metform'); ?> </button> <button type="submit" class="attr-btn attr-btn-primary metform-form-save-btn"><?php esc_html_e('Save changes', 'metform'); ?></button> </div> <div class="mf-spinner"></div> </div> </form> </div> </div> views/modal-form-template-item.php 0000666 00000004500 15165335234 0013226 0 ustar 00 <li class="metform-template-item<?php echo isset($template['package']) ? ' metform-template-item--' . esc_attr($template['package']) : ''; ?> <?php echo (isset($template['package']) && $template['file'] === '') ? ' metform-template-item--go_pro' : ''; ?>"> <label> <input class="metform-template-radio" name="metform-editor-template" type="radio" value="<?php echo ($template['file'] != '') ? esc_attr($template['id']) : ''; ?>" <?php echo $template['file'] === '' ? 'disabled=disabled' : '' ?>> <div class="metform-template-radio-data"> <img src="<?php echo esc_url($template['preview-thumb']); ?>" alt="<?php echo esc_attr($template['title']) ?>"> <?php if(isset($template['package']) && $template['package'] === 'pro') : ?> <div class="metform-template-radio-data--tag"> <span class="metform-template-radio-data--pro_tag"><?php echo esc_html(ucfirst($template['package'])); ?></span> </div> <?php endif; ?> <div class="metform-template-footer-content"> <?php if(isset($template['title']) && $template['title'] != '') : ?> <div class="metform-template-footer-title"> <h2><?php echo esc_html($template['title']); ?></h2> </div> <?php endif; ?> <div class="metform-template-footer-links"> <?php if(isset($template['package']) && $template['package'] === 'pro' && isset($template['file']) && $template['file'] == '') : ?> <a target="_blank" href="https://wpmet.com/metform-pricing/" class="metform-template-footer-links--pro_tag"><i class="metform-template-footer-links--icon fas fa-external-link-square-alt"></i><?php echo esc_html__('Buy Pro', 'metform'); ?></a> <?php endif; ?> <?php if(isset($template['demo-url']) && $template['demo-url'] != '') : ?> <a target="_blank" class="metform-template-footer-links--demo_link" href="<?php echo esc_attr(ucfirst($template['demo-url'])); ?>"><i class="metform-template-footer-links--icon far fa-eye"></i><?php echo esc_html__('Demo', 'metform'); ?></a> <?php endif; ?> </div> </div> </div> </label> </li>